Skip to content
quarry Artifact [40fbdbd0c6]
Login

Artifact [40fbdbd0c6]

Artifact 40fbdbd0c684104e90f23a3d3c36b598e8aaa5b8529d3823f3d7424b84bb47b9:


package markup

import (
	"bytes"
	"compress/flate"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"html"
	"io"
	"regexp"
	"strings"
	"testing"
)

// A DIAGRAM CARRIES ITS SOURCE, A COPY AND A WAY INTO THE STUDIO.
//
// A ```pikchr fence renders with a bar under it: a switch between the
// diagram and its source (radio inputs and labels, so it works with
// scripts off), the source itself, escaped, a Copy button, and a link
// that opens the same source in pikchr-studio.
func TestAFencedDiagramCarriesItsSourceAndStudioLink(t *testing.T) {
	src := "box \"a</code><script>x()</script>\"; arrow; box \"b\""
	out, err := Render([]byte("```pikchr\n"+src+"\n```\n"), nil)
	if err != nil {
		t.Fatal(err)
	}
	got := string(out)
	for _, hook := range []string{`class="pikchr-v pikchr-vd"`, `class="pikchr-v pikchr-vs"`,
		`class="pikchr-src"`, `class="pikchr-bar"`, `class="pikchr-act pikchr-copy"`, `class="pikchr-act pikchr-studio"`,
		`data-on:click="`} {
		if !strings.Contains(got, hook) {
			t.Errorf("FAIL: the frame lacks %s", hook)
		}
	}
	if strings.Contains(got, "<script>") {
		t.Fatal("FAIL: the source was written into the page unescaped")
	}
	code := regexp.MustCompile(`(?s)<pre class="pikchr-src"><code>(.*?)</code></pre>`).FindStringSubmatch(got)
	if code == nil || html.UnescapeString(code[1]) != src {
		t.Fatalf("FAIL: the source shown is %q, want %q", code, src)
	}
	link := regexp.MustCompile(`class="pikchr-act pikchr-studio" href="([^"]+)"`).FindStringSubmatch(got)
	if link == nil {
		t.Fatal("FAIL: no studio link")
	}
	if back := studioSource(t, html.UnescapeString(link[1])); back != src {
		t.Fatalf("FAIL: the studio link carries %q, want the source %q", back, src)
	}
	t.Log("ok: switch, escaped source, Copy, and a studio link that decodes to the source")
}

// A diagram that arrived pre-rendered has no source to show, so it has
// no bar; and a source too large for a URL keeps Copy but not the link.
func TestNoSourceNoBarAndTooLargeNoLink(t *testing.T) {
	svg := []byte(`<svg class="pikchr" viewBox="0 0 100 50"><path d="M0,0"/></svg>`)
	if strings.Contains(string(Figure(svg, nil)), "pikchr-bar") {
		t.Error("FAIL: a diagram with no source grew a bar")
	}
	// Hashes do not compress, so this is a source no URL can carry.
	var big strings.Builder
	h := sha256.Sum256([]byte("seed"))
	for big.Len() < 40000 {
		h = sha256.Sum256(h[:])
		big.WriteString("box \"" + hex.EncodeToString(h[:]) + "\"\n")
	}
	fig := string(Figure(svg, []byte(big.String())))
	if !strings.Contains(fig, "pikchr-copy") || strings.Contains(fig, "pikchr-studio") {
		t.Error("FAIL: a source too large for a URL should keep Copy and drop the studio link")
	}
	t.Log("ok: no source, no bar; too large, Copy only")
}

// studioSource decodes a studio link's #z= fragment back to the source.
func studioSource(t *testing.T, link string) string {
	t.Helper()
	if !strings.HasPrefix(link, StudioURL+"#z=") {
		t.Fatalf("FAIL: the link %q is not the studio with a z= fragment", link)
	}
	raw, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(link, StudioURL+"#z="))
	if err != nil {
		t.Fatal(err)
	}
	b, err := io.ReadAll(flate.NewReader(bytes.NewReader(raw)))
	if err != nil {
		t.Fatal(err)
	}
	return string(b)
}