package web
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/diff"
"craftdesign.group/quarry/internal/index"
)
func noteDiff() *diffDetail {
return &diffDetail{Structured: []diff.FileDiff{{Path: "a.go", Change: "change", Hunks: []diff.Hunk{{
OldStart: 1, NewStart: 1, Lines: []diff.Line{
{Kind: "context", OldNum: 1, NewNum: 1, Tokens: []diff.Token{{Text: "package a"}}},
{Kind: "deletion", OldNum: 2, Tokens: []diff.Token{{Text: "old()"}}},
{Kind: "addition", NewNum: 2, Tokens: []diff.Token{{Text: "new()"}}},
}}}}}}
}
// LINE NOTES LAND UNDER THEIR LINE, AND ONLY WHILE THE LINE IS THE SAME.
//
// A note written at the branch's current tip sits under its line; one
// written at an earlier tip is marked stale and stays in the discussion
// only, because its number may now name another line. A reader who may
// speak gets line links, and ?line= opens the composer only on a line
// the diff actually shows.
func TestLineNotesSitUnderTheirLine(t *testing.T) {
det := &landingDetail{CanSpeak: true, Diff: noteDiff()}
det.State, det.ID = "open", "abcdef123456"
det.Comments = []forumPostRow{
{Hash: "n1aaaaaaaaaa", User: "ada", HTML: "on the new line", Line: &index.LineNote{File: "a.go", Side: "new", Line: 2, At: "cafe12"}},
{Hash: "n2bbbbbbbbbb", User: "bob", HTML: "on the removed line", Line: &index.LineNote{File: "a.go", Side: "old", Line: 2, At: "cafe12"}},
{Hash: "n3cccccccccc", User: "cy", HTML: "written before the branch moved", Line: &index.LineNote{File: "a.go", Side: "new", Line: 1, At: "0ld000"}},
}
data := pageData{Repo: "r", Diff: *det.Diff, Landing: det}
lineNotesFor(&data, det, "cafe12deadbeef", "a.go:new:2")
if !data.LineLinks || data.LineCompose == nil || data.LineCompose.Line != 2 || data.LineCompose.At != "cafe12deadbeef" {
t.Fatalf("FAIL: links %v, composer %+v", data.LineLinks, data.LineCompose)
}
if !det.Comments[2].LineStale || det.Comments[0].LineStale {
t.Error("FAIL: staleness is wrong: only the note from before the branch moved is stale")
}
var buf strings.Builder
if err := noHooks.template(diffPage).ExecuteTemplate(&buf, "diff-more", data); err != nil {
t.Fatal(err)
}
page := buf.String()
for _, want := range []string{"on the new line", "on the removed line", `data-hook="line-compose"`, `name="side" value="new"`, `href="?line=a.go%3aold%3a2#lc"`} {
if !strings.Contains(page, want) {
t.Errorf("FAIL: the diff is missing %q", want)
}
}
if strings.Contains(page, "written before the branch moved") {
t.Error("FAIL: a stale note was drawn under a line it may no longer name")
}
other := pageData{Repo: "r", Diff: *det.Diff, Landing: det}
lineNotesFor(&other, det, "cafe12deadbeef", "a.go:new:99")
if other.LineCompose != nil {
t.Error("FAIL: ?line= opened a composer on a line the diff does not show")
}
reader := &landingDetail{CanSpeak: false, Diff: noteDiff()}
reader.State = "open"
quiet := pageData{Repo: "r", Diff: *reader.Diff, Landing: reader}
lineNotesFor(&quiet, reader, "cafe12", "a.go:new:2")
if quiet.LineLinks || quiet.LineCompose != nil {
t.Error("FAIL: a reader who may not speak got line links or a composer")
}
}
func rangeDiff() *diffDetail {
var lines []diff.Line
for i := 1; i <= 6; i++ {
lines = append(lines, diff.Line{Kind: "addition", NewNum: i, Tokens: []diff.Token{{Text: "x"}}})
}
return &diffDetail{Structured: []diff.FileDiff{{Path: "a.go", Change: "change", Hunks: []diff.Hunk{{NewStart: 1, Lines: lines}}}}}
}
// A COMMENT COVERS A RANGE OF LINES (dmestas: "the more accurate the
// better"). ?line=a.go:new:2-4 opens the composer on lines 2 to 4: those
// rows are marked, the composer sits under the last, it posts lineTo,
// and with it open another line number extends the range to it. A
// range note is drawn under its last line and says which lines.
func TestALineNoteCoversARange(t *testing.T) {
det := &landingDetail{CanSpeak: true, Diff: rangeDiff()}
det.State, det.ID = "open", "abcdef123456"
det.Comments = []forumPostRow{{Hash: "r1aaaaaaaaaa", User: "ada", HTML: "this block",
Line: &index.LineNote{File: "a.go", Side: "new", Line: 2, To: 4, At: "cafe12"}}}
data := pageData{Repo: "r", Diff: *det.Diff, Landing: det}
lineNotesFor(&data, det, "cafe12deadbeef", "a.go:new:2-4")
if data.LineCompose == nil || data.LineCompose.Line != 2 || data.LineCompose.To != 4 {
t.Fatalf("FAIL: composer %+v, want lines 2 to 4", data.LineCompose)
}
if _, ok := data.LineNotes[lineKey("a.go", "new", 4)]; !ok {
t.Error("FAIL: the range note is not filed under its last line")
}
var buf strings.Builder
if err := noHooks.template(diffPage).ExecuteTemplate(&buf, "diff-more", data); err != nil {
t.Fatal(err)
}
page := buf.String()
if n := strings.Count(page, "d-sel"); n != 3 {
t.Errorf("FAIL: %d rows marked, want lines 2, 3 and 4", n)
}
if !strings.Contains(page, `name="lineTo" value="4"`) || !strings.Contains(page, `data-from="2" data-to="4"`) {
t.Error("FAIL: the composer does not carry the range")
}
if !strings.Contains(page, `href="?line=a.go%3anew%3a2-6#lc"`) {
t.Error("FAIL: line 6 does not extend the range from line 2")
}
if !strings.Contains(page, "on lines 2–4") {
t.Error("FAIL: the range note does not say which lines")
}
// The end must be a line the diff shows, and the range runs forward.
for _, q := range []string{"a.go:new:2-99", "a.go:new:4-2"} {
bad := pageData{Repo: "r", Diff: *det.Diff, Landing: det}
lineNotesFor(&bad, det, "cafe12deadbeef", q)
if bad.LineCompose != nil {
t.Errorf("FAIL: ?line=%s opened a composer", q)
}
}
t.Log("ok: a range note")
}