Skip to content
quarry Artifact [2745c1aeed]
Login

Artifact [2745c1aeed]

Artifact 2745c1aeedb8a41e51265ad97e5c7f447d275a4374ff977dddbe3d180c44d0f7:


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&ndash;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")
}