package notation import ( "bytes" "strings" "testing" "github.com/yuin/goldmark" ) func html(t *testing.T, markdown string) string { t.Helper() md := goldmark.New(goldmark.WithExtensions(New())) var out bytes.Buffer if err := md.Convert([]byte(markdown), &out); err != nil { t.Fatal(err) } return out.String() } func TestEachMarkBecomesItsElement(t *testing.T) { for _, c := range []struct{ in, want string }{ {"H~2~O", "H2O"}, {"10^6^ of them", "106 of them"}, {"==marked==", "marked"}, {"~~struck~~", "struck"}, // Nested and adjacent marks are ordinary inline text, so emphasis still works around them. {"*a ~1~ b*", "a 1 b"}, {"CO~2~ and H~2~O", "CO2 and H2O"}, } { if got := html(t, c.in); !strings.Contains(got, c.want) { t.Errorf("%q rendered %s, want it to contain %q", c.in, strings.TrimSpace(got), c.want) } } } // The reason this package owns the tilde. goldmark's own strikethrough treats one tilde as a strike, which // turns a chemical formula into struck text — measured on the real binary before ADR-0061. func TestOneTildeIsSubscriptAndTwoIsStrikethrough(t *testing.T) { got := html(t, "H~2~O is not ~~struck~~") if !strings.Contains(got, "H2O") { t.Errorf("a single tilde must subscript, not strike:\n%s", got) } if !strings.Contains(got, "struck") { t.Errorf("a double tilde must still strike:\n%s", got) } if strings.Contains(got, "2") { t.Errorf("the formula was struck instead of subscripted:\n%s", got) } } // Prose is full of these bytes. An unpaired or meaningless run has to stay exactly as typed. func TestProseKeepsItsPunctuation(t *testing.T) { for _, c := range []struct{ in, keep string }{ {"a = b and c == d", "=="}, // `==` needs no space to open; `c == d` has one either side {"x^2 + y^2 = z^2", "x^2 + y^2"}, // unpaired carets {"the range 10~20 is wide", "10~20"}, {"a ~ b", "~"}, } { got := html(t, c.in) if !strings.Contains(got, c.keep) { t.Errorf("%q lost its punctuation: %s", c.in, strings.TrimSpace(got)) } for _, tag := range []string{"", "", "", ""} { if strings.Contains(got, tag) { t.Errorf("%q produced %s: %s", c.in, tag, strings.TrimSpace(got)) } } } } // A run length the table has no meaning for must not match at a shorter one. func TestAnUndefinedRunLengthDoesNotMatch(t *testing.T) { if got := html(t, "=marked="); strings.Contains(got, "") { t.Errorf("a single = is not a highlight: %s", strings.TrimSpace(got)) } if got := html(t, "^^up^^"); strings.Contains(got, "") { t.Errorf("a double ^ is not a superscript: %s", strings.TrimSpace(got)) } } // Code spans are the author's literal text, whatever bytes are in them. func TestCodeSpansAreUntouched(t *testing.T) { got := html(t, "`H~2~O` and `a==b`") if strings.Contains(got, "") || strings.Contains(got, "") { t.Errorf("a code span must survive verbatim:\n%s", got) } } func TestAbbreviationsExpandWhereverTheyAppear(t *testing.T) { got := html(t, "The HTML spec.\n\n*[HTML]: HyperText Markup Language\n\nHTML again.\n") if n := strings.Count(got, `HTML`); n != 2 { t.Errorf("both uses should expand, got %d:\n%s", n, got) } // A definition may follow the use it explains, which is why this is a transformer and not a parser. if strings.Contains(got, "*[HTML]") || strings.Contains(got, "HyperText Markup Language<") { t.Errorf("the definition line is a note to the parser, not content:\n%s", got) } } func TestAnAbbreviationIsAWholeWord(t *testing.T) { got := html(t, "HTML and HTMLish and xHTML.\n\n*[HTML]: HyperText Markup Language\n") if strings.Count(got, "HTML5`) { t.Errorf("HTML5 should not be shadowed by HTML:\n%s", got) } if !strings.Contains(got, `HTML`) { t.Errorf("the shorter term should still expand on its own:\n%s", got) } } func TestCodeAndUrlsAreNotExpanded(t *testing.T) { got := html(t, "Use `HTML` here and there, but HTML in prose.\n\n*[HTML]: Markup\n") if strings.Count(got, "HTML") { t.Errorf("a code span is literal text:\n%s", got) } } func TestAnExpansionCannotBreakItsAttribute(t *testing.T) { got := html(t, `Term T here.`+"\n\n"+`*[T]: a "quoted" thing`+"\n") if strings.Contains(got, `title="a "quoted" thing"`) { t.Errorf("the attribute was broken by the expansion:\n%s", got) } if !strings.Contains(got, ""quoted"") { t.Errorf("the expansion should be escaped into the attribute:\n%s", got) } } // A list item starts with an asterisk too, so the definition form must not eat one. func TestAListItemIsNotADefinition(t *testing.T) { got := html(t, "* [a link](/x/): still a list\n") if !strings.Contains(got, "
  • ") { t.Errorf("a list item must survive:\n%s", got) } }