Download
64 lines 2.1 KiBGoModified 2026-10-18 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
package render

import (
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"slices"
	"strings"
	"testing"
)

// svgTokens is how svgRootEnd's decoder reads an SVG: every token up to
// the first error, and whether there was one besides the end of input.
func svgTokens(data []byte) (toks []string, broken bool) {
	d := xml.NewDecoder(bytes.NewReader(data))
	d.Strict = true
	d.Entity = xml.HTMLEntity
	for {
		tok, err := d.RawToken()
		if err != nil {
			return toks, !errors.Is(err, io.EOF)
		}
		toks = append(toks, fmt.Sprintf("%#v", xml.CopyToken(tok)))
	}
}

// Pushed SVGs are themed by splicing a <style> in after the root start tag.
// Whatever the file holds, the splice must land between tags and leave the
// rest of the document reading exactly as before.
func FuzzThemeSVG(f *testing.F) {
	for _, s := range []string{
		`<svg><rect/></svg>`, `<?xml version="1.0"?><!-- c --><!DOCTYPE svg><svg a="1"><g/></svg>`,
		`<svg title="a>b" x='/'><g/></svg>`, `<svg a="1"/>`, `<html><svg/></html>`, `<svg a=1>`,
		`<svg><![CDATA[</svg>]]></svg>`, `<SVG xmlns="http://www.w3.org/2000/svg">&nbsp;</SVG>`, `<s:svg>`, "",
	} {
		f.Add([]byte(s))
	}
	f.Fuzz(func(t *testing.T, data []byte) {
		end := svgRootEnd(data)
		themed := themeSVG(data, end, "#fff", "#000")
		if end < 0 {
			if !bytes.Equal(themed, data) {
				t.Fatalf("%q changed with no root found", data)
			}
			return
		}
		if end < 2 || data[end-1] != '>' || data[end-2] == '/' {
			t.Fatalf("%q: root ends at %d", data, end)
		}
		want, wantBroken := svgTokens(data)
		got, gotBroken := svgTokens(themed)
		if len(got) != len(want)+3 || gotBroken != wantBroken {
			t.Fatalf("%q themed as %q reads differently", data, themed)
		}
		// The root start tag, then the injected <style>, its text and </style>.
		root := slices.IndexFunc(want, func(s string) bool { return strings.HasPrefix(s, "xml.StartElement") })
		spliced := slices.Concat(got[:root+1], got[root+4:])
		if !slices.Equal(spliced, want) || !strings.Contains(got[root+1], `Local:"style"`) {
			t.Fatalf("%q themed as %q reads differently:\n%v\n%v", data, themed, want, got)
		}
	})
}