package markup
import "testing"
func closing(t *testing.T, text string) []string {
t.Helper()
var out []string
for _, tok := range ClosingRefs(text) {
out = append(out, tok.Canonical())
}
return out
}
// The three verbs, in the spellings a landing message actually uses.
func TestAClosingPhraseNamesItsTicket(t *testing.T) {
// A bare hash canonicalizes bracketed — [hash] is the one spelling
// every reference to it collapses to, so a pasted URL and a typed
// hash never look like two references.
for _, tc := range []struct{ text, want string }{
{"zitcli: record every store subprocess. closes cafb1f21be", "[cafb1f21be]"},
{"fixes cafb1f21be", "[cafb1f21be]"},
{"resolves cafb1f21be", "[cafb1f21be]"},
{"Closes cafb1f21be", "[cafb1f21be]"},
{"CLOSES cafb1f21be", "[cafb1f21be]"},
{"closes: cafb1f21be", "[cafb1f21be]"},
{"closes cafb1f21be", "[cafb1f21be]"},
// Trailing punctuation is the sentence's, not the token's.
{"closes cafb1f21be.", "[cafb1f21be]"},
{"closes cafb1f21be, and tidies up", "[cafb1f21be]"},
} {
got := closing(t, tc.text)
if len(got) != 1 || got[0] != tc.want {
t.Errorf("FAIL: %q gave %v, want [%s]", tc.text, got, tc.want)
}
}
}
// The verb must govern the token. Prose that contains both words but
// points the reference somewhere else is not an instruction — and an
// accident here closes somebody's ticket.
func TestProseIsNotAnInstruction(t *testing.T) {
for _, text := range []string{
"this closes the gap that cafb1f21be describes",
"closes the door",
"a landing that closes",
"see cafb1f21be for why this closes badly",
"the ticket cafb1f21be says it closes on merge",
} {
if got := closing(t, text); len(got) != 0 {
t.Errorf("FAIL: %q closed %v", text, got)
}
}
}
// Every spelling of a reference works, because the grammar is the same
// one the renderer links with: a reference that acts is one a reader
// can click.
func TestEverySpellingOfAReferenceCloses(t *testing.T) {
for _, text := range []string{
"closes cafb1f21be",
"closes ticket/cafb1f21be",
"closes [cafb1f21be]",
"closes /r/quarry/ticket/cafb1f21be",
"closes https://zeitforge.app/r/quarry/ticket/cafb1f21be",
} {
if got := closing(t, text); len(got) != 1 {
t.Errorf("FAIL: %q gave %v, want one reference", text, got)
}
}
}
// Several tickets in one landing, each once however often it is named.
func TestManyTicketsAndNoDuplicates(t *testing.T) {
got := closing(t, "closes cafb1f21be, fixes 8d5aa739f8 and closes cafb1f21be again")
if len(got) != 2 {
t.Fatalf("FAIL: got %v, want two distinct references", got)
}
}
// A person parses as a token and is never a thing to close. Refused
// here rather than left for the caller to notice.
func TestAPersonIsNotClosable(t *testing.T) {
if got := closing(t, "closes @dmestas"); len(got) != 0 {
t.Errorf("FAIL: %q closed %v", "closes @dmestas", got)
}
}
// A bare "#412" after a closing verb is the ticket numbered 412, as on a
// pull request, trailing punctuation and all. Elsewhere it reads as a
// chat line; a chat line is never a thing to close.
func TestAHashNumberClosesThatTicket(t *testing.T) {
for _, text := range []string{"closes #412", "Closes #412.", "fixes: #412,"} {
got := closing(t, text)
if len(got) != 1 || got[0] != "ticket/412" {
t.Errorf("FAIL: %q gave %v, want ticket/412", text, got)
}
}
}
// ^N is a chat message (#309), and a message is never a thing to close.
func TestACaretNumberClosesNothing(t *testing.T) {
for _, text := range []string{"closes ^412", "fixes sqlite:^412."} {
if got := closing(t, text); len(got) != 0 {
t.Errorf("FAIL: %q closed %v", text, got)
}
}
}
// Saying nothing closes nothing — the ordinary case for most landings.
func TestAnOrdinaryMessageClosesNothing(t *testing.T) {
if got := closing(t, "csp: allow the page to state its own address"); len(got) != 0 {
t.Errorf("FAIL: an ordinary message closed %v", got)
}
if got := closing(t, ""); len(got) != 0 {
t.Errorf("FAIL: an empty message closed %v", got)
}
}