Skip to content
quarry Artifact [b7c972751f]
Login

Artifact [b7c972751f]

Artifact b7c972751fd8346da2547d48bb16adb20253b88afd5bdd66c7a1146cf5c86f52:


package web

import (
	"bytes"
	"strings"
	"testing"
)

// The merge page says each verdict in its own words, draws the view for
// a conflict, and links the pair's open landing.
func TestMergePageSaysEachVerdict(t *testing.T) {
	render := func(mp *mergePage) string {
		var b bytes.Buffer
		if err := noHooks.template(mergePageTmpl).ExecuteTemplate(&b, "content", pageData{Repo: "alpha", Merge: mp}); err != nil {
			t.Fatal(err)
		}
		return b.String()
	}
	for verdict, hook := range map[string]string{"clean": "merge-clean", "up-to-date": "merge-uptodate", "unrelated": "merge-unrelated"} {
		h := render(&mergePage{Branch: "feat", Onto: "trunk", Verdict: verdict, Branches: []string{"trunk", "feat"}})
		if !strings.Contains(h, `data-hook="`+hook+`"`) || strings.Contains(h, `data-hook="meld"`) {
			t.Errorf("FAIL: %s did not say so on its own", verdict)
		}
	}
	mv := buildMeld(meldFixture(), "/r/alpha")
	h := render(&mergePage{Branch: "feat", Onto: "trunk", Verdict: "conflict", Meld: mv, Landing: "7", LandingNumber: "7"})
	if !strings.Contains(h, `data-hook="meld"`) || !strings.Contains(h, `data-verdict="conflict"`) {
		t.Error("FAIL: a conflicting pair does not draw the merge view")
	}
	if !strings.Contains(h, `data-hook="merge-landing"`) || !strings.Contains(h, `/r/alpha/landing/7`) {
		t.Error("FAIL: the pair's open landing is not linked")
	}
	if !strings.Contains(h, `name="branch"`) || !strings.Contains(h, `name="onto"`) {
		t.Error("FAIL: no way to pick another pair")
	}
}

// The branches list gives each open branch its standing against trunk,
// linking the branch's own merge page; the default branch has none.
func TestBranchesListShowsHowEachBranchMerges(t *testing.T) {
	rows := []branchRow{{Name: "trunk", Tip: "t1"}, {Name: "feat", Tip: "f1"}, {Name: "docs", Tip: "d1"}, {Name: "new", Tip: "n1"}}
	type m = branchMerge
	rows[1].Merge = &m{Onto: "trunk", State: "conflict", Files: 2}
	rows[2].Merge = &m{Onto: "trunk", State: "clean", Behind: 3}
	rows[3].Merge = &m{Onto: "trunk"}
	var b bytes.Buffer
	if err := noHooks.template(branchesPage).ExecuteTemplate(&b, "content", pageData{Repo: "alpha", Branches: rows}); err != nil {
		t.Fatal(err)
	}
	h := b.String()
	for _, want := range []string{`data-state="conflict"`, `data-state="clean"`, `data-state=""`,
		`/r/alpha/merge?branch=feat&onto=trunk`, `/r/alpha/merge?branch=docs&onto=trunk`} {
		if !strings.Contains(h, want) {
			t.Errorf("FAIL: the list lacks %s", want)
		}
	}
	if strings.Count(h, `data-hook="branch-merge"`) != 3 {
		t.Error("FAIL: the default branch, or a branch, has the wrong number of merge chips")
	}
}

// The page offers what the reader's push would be allowed: a choice of
// side to commit onto, a refusal that says who owns the target, and a
// landing for the pair; a check-in's merge goes onto the target only.
func TestMergePageOffersWhatAPushWouldAllow(t *testing.T) {
	render := func(mp *mergePage) string {
		var b bytes.Buffer
		if err := noHooks.template(mergePageTmpl).ExecuteTemplate(&b, "content", pageData{Repo: "alpha", Merge: mp}); err != nil {
			t.Fatal(err)
		}
		return b.String()
	}
	mv := buildMeld(meldFixture(), "/r/alpha")
	mv.CanResolve = true
	mv.Into = []meldInto{{Value: "onto", Name: "trunk", Checked: true}, {Value: "branch", Name: "feat"}}
	h := render(&mergePage{Branch: "feat", Onto: "trunk", Verdict: "conflict", Meld: mv, MayMerge: true, MayPropose: true})
	if strings.Count(h, `name="into"`) != 2 || !strings.Contains(h, `data-hook="merge-into"`) {
		t.Error("FAIL: a reader who may push to both sides is not offered the choice")
	}
	if !strings.Contains(h, `data-hook="merge-propose"`) || !strings.Contains(h, `landing-new?branch=feat&target=trunk`) {
		t.Error("FAIL: no landing is offered for the pair")
	}
	if strings.Contains(h, `data-hook="merge-refused"`) {
		t.Error("FAIL: a reader who may push is told they are refused")
	}

	h = render(&mergePage{Branch: "feat", Onto: "trunk", Verdict: "clean", Refused: true, Owners: "alice", MayPropose: true})
	if !strings.Contains(h, `data-hook="merge-refused"`) || !strings.Contains(h, "alice") {
		t.Error("FAIL: a protected target does not say so, or by whom")
	}
	if !strings.Contains(h, `class="btn btn-primary" data-hook="merge-propose"`) {
		t.Error("FAIL: refused, the landing is not the page's one action")
	}

	h = render(&mergePage{Branch: "feat", Onto: "trunk", Verdict: "clean", MayPropose: true, Landing: "3", LandingNumber: "3"})
	if strings.Contains(h, `data-hook="merge-propose"`) {
		t.Error("FAIL: a second landing is offered for a pair that has one")
	}

	one := buildMeld(meldFixture(), "/r/alpha")
	one.CanResolve = true
	one.Into = []meldInto{{Value: "onto", Name: "trunk", Checked: true}}
	h = render(&mergePage{Branch: "0123456789ab", Onto: "trunk", Verdict: "conflict", Meld: one, Checkin: true, MayMerge: true})
	if strings.Count(h, `name="into"`) != 1 || strings.Contains(h, `type="radio" name="into"`) {
		t.Error("FAIL: a check-in's merge offers a side other than the target")
	}
	if strings.Contains(h, `data-hook="merge-propose"`) {
		t.Error("FAIL: a landing is offered for a check-in")
	}
	if !strings.Contains(h, `<option selected>0123456789ab</option>`) {
		t.Error("FAIL: the picker does not show the check-in being merged")
	}

	for _, v := range []string{"up-to-date", "unrelated"} {
		h = render(&mergePage{Branch: "feat", Onto: "trunk", Verdict: v, Refused: true, MayPropose: true})
		if strings.Contains(h, `data-hook="merge-refused"`) || strings.Contains(h, `data-hook="merge-propose"`) {
			t.Errorf("FAIL: %s offers a merge that has nothing to do", v)
		}
	}
}

// A branch's own timeline says how it merges onto the default branch,
// linking its merge page, where a reader thinking "merge this" stands.
func TestBranchTimelineLinksItsMerge(t *testing.T) {
	var b bytes.Buffer
	data := pageData{Repo: "alpha", Branch: "feat", BranchMerge: &branchMerge{Onto: "trunk", State: "conflict", Files: 1}}
	if err := noHooks.template(timelinePage).ExecuteTemplate(&b, "content", data); err != nil {
		t.Fatal(err)
	}
	h := b.String()
	if !strings.Contains(h, `data-hook="branch-merge-line"`) || !strings.Contains(h, `/r/alpha/merge?branch=feat&amp;onto=trunk`) {
		t.Error("FAIL: the branch's timeline does not link its merge")
	}
	b.Reset()
	if err := noHooks.template(timelinePage).ExecuteTemplate(&b, "content", pageData{Repo: "alpha"}); err != nil {
		t.Fatal(err)
	}
	if strings.Contains(b.String(), `data-hook="branch-merge-line"`) {
		t.Error("FAIL: the whole timeline claims a branch's merge")
	}
}

// A mirror takes no local writes, so its merge page offers neither a
// merge nor a landing, says where merges happen, and still links what
// the branch would bring.
func TestMergePageOnAMirrorOffersNoWrites(t *testing.T) {
	var p posture
	p.Mirror.Tracking = true
	p.Mirror.URL = "https://fossil.example.net/r/lib"
	var b bytes.Buffer
	mp := &mergePage{Branch: "feat", Onto: "trunk", Verdict: "clean", DiffURL: "/r/lib/checkin/abc/diff?from=def"}
	if err := noHooks.template(mergePageTmpl).ExecuteTemplate(&b, "content", pageData{Repo: "lib", Merge: mp, Posture: p}); err != nil {
		t.Fatal(err)
	}
	h := b.String()
	for _, banned := range []string{`data-hook="merge-commit"`, `data-hook="merge-propose"`, `data-hook="merge-refused"`} {
		if strings.Contains(h, banned) {
			t.Errorf("FAIL: a mirror's merge page offers %s", banned)
		}
	}
	if !strings.Contains(h, `data-hook="merge-mirror"`) || !strings.Contains(h, "fossil.example.net") {
		t.Error("FAIL: the page does not say the repository mirrors its upstream")
	}
	if !strings.Contains(h, `data-hook="merge-diff"`) || !strings.Contains(h, `/r/lib/checkin/abc/diff?from=def`) {
		t.Error("FAIL: the branch's changes are not linked")
	}
}