diagram_test.go
package display
import (
"bytes"
"strings"
"sync"
"testing"
"github.com/yuin/goldmark/parser"
)
func TestDiagramDotProducesSVG(t *testing.T) {
got := renderMD(t, "```dot\ndigraph { a -> b }\n```\n")
if !strings.Contains(got, `<div class="diagram">`) {
t.Errorf("missing diagram wrapper; got:\n%s", got)
}
if !strings.Contains(got, "<svg") {
t.Errorf("expected inline <svg>; got:\n%s", got)
}
// The block must be consumed as a diagram, not left as a code block.
if strings.Contains(got, "<pre><code") {
t.Errorf("dot block leaked through as a code block; got:\n%s", got)
}
}
func TestDiagramGraphvizAlias(t *testing.T) {
// The graphviz tag is an alias for dot; both feed the same engine.
got := renderMD(t, "```graphviz\ndigraph { a -> b }\n```\n")
if !strings.Contains(got, `<div class="diagram">`) || !strings.Contains(got, "<svg") {
t.Errorf("graphviz alias did not render a diagram; got:\n%s", got)
}
}
func TestDiagramBrokenDotFallsBackVisibly(t *testing.T) {
// A diagram that does not compile must not panic or 500; it echoes its
// source in a failure-tagged box so the author still sees their input.
got := renderMD(t, "```dot\ndigraph { a -> \n```\n")
if strings.Contains(got, "<svg") {
t.Errorf("broken dot unexpectedly produced an svg; got:\n%s", got)
}
if !strings.Contains(got, "diagram-error") {
t.Errorf("broken dot lacked the failure marker; got:\n%s", got)
}
if !strings.Contains(got, "digraph { a ->") {
t.Errorf("broken dot did not echo its source; got:\n%s", got)
}
}
func TestDiagramPlainCodeBlockUntouched(t *testing.T) {
// A non-diagram language must render exactly as before: a plain code block.
got := renderMD(t, "```go\nfunc main() {}\n```\n")
if strings.Contains(got, "diagram") || strings.Contains(got, "<svg") {
t.Errorf("a go block was treated as a diagram; got:\n%s", got)
}
if !strings.Contains(got, "<pre><code") {
t.Errorf("a go block should render as a code block; got:\n%s", got)
}
}
func TestDiagramLabelIsEscaped(t *testing.T) {
// vitrine serves trees it may not have authored, so a hostile label must not
// break out of the SVG into live HTML. Graphviz escapes text content, so the
// inline SVG carries no injected <script> and no premature </svg>.
got := renderMD(t, "```dot\ndigraph { a [label=\"</text></svg><script>x</script>\"] }\n```\n")
if strings.Contains(got, "<script>") {
t.Errorf("hostile label injected a live script; got:\n%s", got)
}
if strings.Count(got, "</svg>") != 1 {
t.Errorf("hostile label produced a stray </svg>; got:\n%s", got)
}
}
func TestDiagramConcurrentRenders(t *testing.T) {
// go-graphviz drives one process-global wasm module with no internal locking,
// so renders must be serialized. This exercises that: many diagrams rendered
// at once must all succeed. Without the serializing lock the shared module
// corrupts and the process aborts with "concurrent map writes".
const src = "```dot\ndigraph { a -> b -> c }\n```\n"
var wg sync.WaitGroup
fails := make(chan string, 16)
for i := 0; i < 16; i++ {
wg.Add(1)
go func() {
defer wg.Done()
var buf bytes.Buffer
ctx := parser.NewContext(parser.WithIDs(newSlugIDs()))
if err := markdown.Convert([]byte(src), &buf, parser.WithContext(ctx)); err != nil {
fails <- err.Error()
return
}
if !strings.Contains(buf.String(), "<svg") {
fails <- "no svg in output"
}
}()
}
wg.Wait()
close(fails)
for f := range fails {
t.Errorf("concurrent render failed: %s", f)
}
}
display/diagram_test.go · 3.4 KiB · c714b15828