vitrine

callout_test.go

package main

import (
	"bytes"
	"strings"
	"testing"

	"github.com/yuin/goldmark/parser"
)

// renderMD runs source through the real markdown pipeline (the same package
// global used to serve pages), so these tests exercise the wired-in extension.
func renderMD(t *testing.T, source string) string {
	t.Helper()
	var buf bytes.Buffer
	ctx := parser.NewContext(parser.WithIDs(newSlugIDs()))
	if err := markdown.Convert([]byte(source), &buf, parser.WithContext(ctx)); err != nil {
		t.Fatalf("convert: %v", err)
	}
	return buf.String()
}

func TestCalloutBareType(t *testing.T) {
	got := renderMD(t, ":::note\nheads up\n:::\n")
	want := "<div class=\"callout callout-note\">\n<p>heads up</p>\n</div>\n"
	if got != want {
		t.Errorf("got:\n%s\nwant:\n%s", got, want)
	}
}

func TestCalloutTypeAndTitle(t *testing.T) {
	got := renderMD(t, ":::warning  Be careful\nbody here\n:::\n")
	want := "<div class=\"callout callout-warning\">\n" +
		"<p class=\"callout-title\">Be careful</p>\n" +
		"<p>body here</p>\n" +
		"</div>\n"
	if got != want {
		t.Errorf("got:\n%s\nwant:\n%s", got, want)
	}
}

func TestCalloutMultiParagraphAndMarkdownBody(t *testing.T) {
	got := renderMD(t, ":::tip Handy\nfirst *para* here\n\nsecond para\n\n- one\n- two\n:::\n")
	for _, want := range []string{
		"<div class=\"callout callout-tip\">\n",
		"<p class=\"callout-title\">Handy</p>\n",
		"<em>para</em>",
		"<p>second para</p>",
		"<ul>\n<li>one</li>\n<li>two</li>\n</ul>",
	} {
		if !strings.Contains(got, want) {
			t.Errorf("missing %q in:\n%s", want, got)
		}
	}
}

func TestCalloutQuadColonCloser(t *testing.T) {
	// Opener and closer may both be three-OR-MORE colons.
	got := renderMD(t, "::::info\nboxed\n::::\n")
	want := "<div class=\"callout callout-info\">\n<p>boxed</p>\n</div>\n"
	if got != want {
		t.Errorf("got:\n%s\nwant:\n%s", got, want)
	}
}

func TestCalloutSurroundingProse(t *testing.T) {
	got := renderMD(t, "before text\n\n:::note\ninside\n:::\n\nafter text\n")
	for _, want := range []string{
		"<p>before text</p>",
		"<div class=\"callout callout-note\">",
		"<p>inside</p>",
		"<p>after text</p>",
	} {
		if !strings.Contains(got, want) {
			t.Errorf("missing %q in:\n%s", want, got)
		}
	}
}

func TestCalloutUnterminatedRunsToEnd(t *testing.T) {
	// An opener with no closer should still render, running to end of document.
	got := renderMD(t, ":::warning No closer\nstill inside\nmore body\n")
	for _, want := range []string{
		"<div class=\"callout callout-warning\">",
		"<p class=\"callout-title\">No closer</p>",
		"still inside",
		"</div>\n",
	} {
		if !strings.Contains(got, want) {
			t.Errorf("missing %q in:\n%s", want, got)
		}
	}
}

func TestCalloutLoneOpenerFallsBackToParagraph(t *testing.T) {
	// A lone ":::" is a closer token, not an opener: with no open callout it is
	// just ordinary paragraph text, never a callout div.
	got := renderMD(t, ":::\n")
	if strings.Contains(got, "callout") {
		t.Errorf("lone ::: became a callout; got:\n%s", got)
	}
	if !strings.Contains(got, "<p>:::</p>") {
		t.Errorf("lone ::: should be a plain paragraph; got:\n%s", got)
	}
}

func TestCalloutTypeIsEscaped(t *testing.T) {
	// The type only appears as a class suffix and must be HTML-escaped, so an
	// author string cannot inject structure into our chrome.
	got := renderMD(t, ":::a\"><script>\nx\n:::\n")
	if strings.Contains(got, "<script>") {
		t.Errorf("type escaped incorrectly; got:\n%s", got)
	}
}

func TestCalloutTypeNormalized(t *testing.T) {
	// warn folds onto the canonical warning type that style.css styles.
	got := renderMD(t, ":::warn Heads up\nx\n:::\n")
	if !strings.Contains(got, "callout-warning") {
		t.Errorf("warn not normalized to warning; got:\n%s", got)
	}
}

func TestCalloutUnknownTypePassesThrough(t *testing.T) {
	// An unknown type is left as-is on the class, for the neutral fallback box.
	got := renderMD(t, ":::obscure\nx\n:::\n")
	if !strings.Contains(got, "callout callout-obscure") {
		t.Errorf("unknown type not preserved; got:\n%s", got)
	}
}

func TestCalloutNoNesting(t *testing.T) {
	// An opener inside a callout is left as plain content, not a nested box.
	got := renderMD(t, ":::note\n:::warning\ninner\n:::\n")
	if strings.Count(got, "callout callout-") != 1 {
		t.Errorf("expected exactly one callout div; got:\n%s", got)
	}
}

callout_test.go · 4.2 KiB · db16d53845