Skip to content
quarry Artifact [9e773f4407]
Login

Artifact [9e773f4407]

Artifact 9e773f440713df8952f9bdb5063871a3298e66284fe980ed34c4ad1ff8ea271b:


package markup

import (
	"strings"
	"testing"
)

func TestRenderPikchrSanitized(t *testing.T) {
	out, err := RenderPikchr([]byte(`box "hello" fit; arrow right; circle "world" fit`))
	if err != nil {
		t.Fatal(err)
	}
	svg := string(out)
	if !strings.Contains(svg, "<svg") || !strings.Contains(svg, "hello") {
		t.Fatalf("svg missing content:\n%s", svg[:min(300, len(svg))])
	}
	// Geometry must survive the narrow policy — a diagram stripped of its
	// paths would render blank while still "passing".
	if !strings.Contains(svg, "<path") && !strings.Contains(svg, "<polygon") {
		t.Fatalf("geometry stripped by policy:\n%s", svg[:min(400, len(svg))])
	}
	if !strings.Contains(svg, "style=") {
		t.Fatalf("styling stripped, diagram would be invisible:\n%s", svg[:min(400, len(svg))])
	}
}

func TestPikchrLabelCannotInject(t *testing.T) {
	out, err := RenderPikchr([]byte(`box "</svg><script>alert(1)</script>" fit`))
	if err != nil {
		return // refusing to render is also an acceptable outcome
	}
	// The payload must survive only as escaped display text. Checking
	// for the raw substring would false-positive on "&lt;script&gt;",
	// which is exactly what correct escaping looks like.
	if strings.Contains(string(out), "<script") || strings.Contains(string(out), "javascript:") {
		t.Fatalf("unescaped script survived:\n%s", out)
	}
	if !strings.Contains(string(out), "&lt;script&gt;") {
		t.Fatalf("payload neither escaped nor removed:\n%s", out)
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// A ```pikchr fence inside markdown becomes an inline diagram, and the
// surrounding document still renders normally.
func TestPikchrFenceInMarkdown(t *testing.T) {
	src := "Before.\n\n```pikchr\nbox \"fenced\" fit\n```\n\nAfter.\n"
	out, err := Render([]byte(src), nil)
	if err != nil {
		t.Fatal(err)
	}
	got := string(out)
	if !strings.Contains(got, "<svg") || !strings.Contains(got, "fenced") {
		t.Fatalf("fence not rendered as svg:\n%s", got)
	}
	if !strings.Contains(got, "Before.") || !strings.Contains(got, "After.") {
		t.Fatalf("surrounding markdown lost:\n%s", got)
	}
	// An ordinary code fence must still be a code block, not a diagram.
	out, _ = Render([]byte("```go\nfmt.Println()\n```\n"), nil)
	if strings.Contains(string(out), "<svg") {
		t.Fatalf("non-pikchr fence rendered as a diagram:\n%s", out)
	}
}

// A fence that will not compile shows its source plus the diagnostic,
// never a broken page or a silent blank.
func TestPikchrFenceErrorShowsSource(t *testing.T) {
	out, err := Render([]byte("```pikchr\nnot ( valid\n```\n"), nil)
	if err != nil {
		t.Fatal(err)
	}
	got := string(out)
	if !strings.Contains(got, "not ( valid") || !strings.Contains(got, "pikchr:") {
		t.Fatalf("bad diagram did not degrade to source + message:\n%s", got)
	}
}

// A diagram must be legible on both themes: quarry's pages follow
// prefers-color-scheme, so Pikchr's hardcoded black ink has to become
// currentColor or the whole diagram vanishes on the dark theme.
func TestPikchrInheritsThemeColor(t *testing.T) {
	out, err := RenderPikchr([]byte(`box "visible" fit; arrow right; circle "here" fit`))
	if err != nil {
		t.Fatal(err)
	}
	got := string(out)
	if strings.Contains(got, "rgb(0,0,0)") || strings.Contains(got, "rgb(0, 0, 0)") {
		t.Fatalf("hardcoded black ink survives — invisible on the dark theme:\n%s", got[:min(400, len(got))])
	}
	if !strings.Contains(got, "currentColor") {
		t.Fatalf("no currentColor: diagram will not follow the page theme:\n%s", got[:min(400, len(got))])
	}
}