add inline notation, and take the tilde back from strikethrough
~sub~, ^sup^, ==mark==, and ~~strike~~ moved in from goldmark. Not a preference: goldmark's strikethrough claims a single tilde as well as a double, so with it enabled H~2~O rendered as H<del>2</del>O — measured before the change. Two features cannot share a byte and both be correct, so notation owns it and the authored syntax stays exactly as ADR-0058 documented. The second failure was worse and only showed up under test. Under delimiter rules `x^2 + y^2 = z^2` pairs its carets across the whole expression and renders x<sup>2 + y</sup>2 — prose silently becoming markup, in exactly the content this engine is for. So a single run is scanned rather than paired, and may not cross whitespace: a subscript holds a formula, never a phrase. Pandoc draws the same line. The cost is that a single run takes its content literally, so there is no emphasis inside a subscript, which the ADR states rather than leaving to be discovered. New package under internal/ext, which is a stop condition and was asked. It takes the extensions counter to 4, past its threshold, and the answer is still no: four features attach in three unrelated ways, and two goldmark extenders compose in goldmark's own extender list, which is already the registry for that shape. The example site's hand-copied extender list drifted, exactly as the latent row added last loop predicted — the demo case failed and named it. Both are now in step again. core 2794/2800, ext 1236/2000, 34 gates green, 0 warnings.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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", "H<sub>2</sub>O"},
|
||||
{"10^6^ of them", "10<sup>6</sup> of them"},
|
||||
{"==marked==", "<mark>marked</mark>"},
|
||||
{"~~struck~~", "<del>struck</del>"},
|
||||
// Nested and adjacent marks are ordinary inline text, so emphasis still works around them.
|
||||
{"*a ~1~ b*", "<em>a <sub>1</sub> b</em>"},
|
||||
{"CO~2~ and H~2~O", "CO<sub>2</sub> and H<sub>2</sub>O"},
|
||||
} {
|
||||
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, "H<sub>2</sub>O") {
|
||||
t.Errorf("a single tilde must subscript, not strike:\n%s", got)
|
||||
}
|
||||
if !strings.Contains(got, "<del>struck</del>") {
|
||||
t.Errorf("a double tilde must still strike:\n%s", got)
|
||||
}
|
||||
if strings.Contains(got, "<del>2</del>") {
|
||||
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{"<sub>", "<sup>", "<mark>", "<del>"} {
|
||||
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, "<mark>") {
|
||||
t.Errorf("a single = is not a highlight: %s", strings.TrimSpace(got))
|
||||
}
|
||||
if got := html(t, "^^up^^"); strings.Contains(got, "<sup>") {
|
||||
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, "<sub>") || strings.Contains(got, "<mark>") {
|
||||
t.Errorf("a code span must survive verbatim:\n%s", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user