Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,7 @@ const stacked = stackGraphicsVertically([

If you're using Bun for testing, you can use the `toMatchGraphicsSvg` matcher to compare graphics objects against saved snapshots.

First, install the required peer dependencies:

```bash
bun add -d bun-match-svg looksSame
```

Then use the matcher in your tests:
Import the matcher in your tests:

```tsx
import { expect, test } from "bun:test"
Expand Down Expand Up @@ -313,7 +307,8 @@ Snapshots are stored as SVG files in an `__snapshots__` directory next to your t
bun test -u
```

This is powered by the same technology as bun-match-svg but integrated specifically for GraphicsObject testing.
The matcher rasterizes SVG snapshots before comparing them, so visually identical
SVGs match even when their markup differs.

### Example Graphics JSON

Expand Down
184 changes: 47 additions & 137 deletions bun.lock

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions lib/compare-svg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Resvg } from "@resvg/resvg-js"
import looksSame from "@tscircuit/image-utils/looks-same"

type CompareSvgOptions = {
strict?: boolean
tolerance?: number
ignoreCaret?: boolean
ignoreAntialiasing?: boolean
antialiasingTolerance?: number
pixelRatio?: number
percentThreshold?: number
}

const renderSvgToPng = (svg: string | Uint8Array) => {
return new Resvg(typeof svg === "string" ? svg : Buffer.from(svg))
.render()
.asPng()
}

export const compareSvg = async (
referenceSvg: string | Uint8Array,
currentSvg: string | Uint8Array,
options: CompareSvgOptions = {},
) => {
return looksSame(
renderSvgToPng(referenceSvg),
renderSvgToPng(currentSvg),
options,
)
}

export const createSvgDiff = async ({
referenceSvg,
currentSvg,
highlightColor,
...options
}: CompareSvgOptions & {
referenceSvg: string | Uint8Array
currentSvg: string | Uint8Array
highlightColor?: string
}) => {
return looksSame.createDiff({
reference: renderSvgToPng(referenceSvg),
current: renderSvgToPng(currentSvg),
highlightColor,
...options,
})
}
22 changes: 9 additions & 13 deletions lib/matcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, type MatcherResult } from "bun:test"
import * as fs from "node:fs"
import * as path from "node:path"
import looksSame from "looks-same"
import { compareSvg, createSvgDiff } from "./compare-svg"
import { GraphicsObject } from "./types"
import { getSvgFromGraphicsObject } from "./getSvgFromGraphicsObject"

Expand Down Expand Up @@ -54,14 +54,10 @@ async function toMatchGraphicsSvg(

const existingSnapshot = fs.readFileSync(filePath, "utf-8")

const result: any = await looksSame(
Buffer.from(receivedSvg),
Buffer.from(existingSnapshot),
{
strict: false,
tolerance: 2,
},
)
const result = await compareSvg(existingSnapshot, receivedSvg, {
strict: false,
tolerance: 2,
})

if (result.equal) {
return {
Expand All @@ -71,12 +67,12 @@ async function toMatchGraphicsSvg(
}

const diffPath = filePath.replace(".snap.svg", ".diff.png")
await looksSame.createDiff({
reference: Buffer.from(existingSnapshot),
current: Buffer.from(receivedSvg),
diff: diffPath,
const diff = await createSvgDiff({
referenceSvg: existingSnapshot,
currentSvg: receivedSvg,
highlightColor: "#ff00ff",
})
fs.writeFileSync(diffPath, diff)

return {
message: () => `Snapshot does not match. Diff saved at ${diffPath}`,
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tscircuit/image-utils": "^0.0.3",
"@resvg/resvg-js": "^2.6.2",
"@types/bun": "^1.2.17",
"@types/debug": "^4.1.12",
"@types/jsdom": "^21.1.7",
Expand All @@ -53,9 +53,9 @@
"vite-tsconfig-paths": "^5.1.4"
},
"peerDependencies": {
"typescript": "^5.0.0",
"bun-match-svg": "*",
"looks-same": "^9.0.1"
"@resvg/resvg-js": "*",
"@tscircuit/image-utils": "*",
"typescript": "^5.0.0"
},
"dependencies": {
"@react-hook/resize-observer": "^2.0.2",
Expand Down
1 change: 0 additions & 1 deletion tests/SVGRenderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { describe, expect, test, beforeAll } from "bun:test"
import { act } from "react"
import { createRoot } from "react-dom/client"
import SVGRenderer from "../site/components/SVGRenderer"
import "bun-match-svg"
import * as jsdom from "jsdom"
import { getSvgsFromLogString } from "../lib"

Expand Down
32 changes: 32 additions & 0 deletions tests/compare-svg.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, test } from "bun:test"
import { compareSvg, createSvgDiff } from "../lib/compare-svg"

const referenceSvg = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<rect width="16" height="16" fill="#000" />
</svg>
`

describe("compareSvg", () => {
test("compares rendered pixels instead of SVG markup", async () => {
const equivalentSvg =
'<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><rect fill="#000000" height="16" width="16"/></svg>'

const result = await compareSvg(referenceSvg, equivalentSvg)

expect(result.equal).toBe(true)
})

test("creates a PNG diff for visually different SVGs", async () => {
const differentSvg = referenceSvg.replace("#000", "#fff")

const result = await compareSvg(referenceSvg, differentSvg)
const diff = await createSvgDiff({
referenceSvg,
currentSvg: differentSvg,
})

expect(result.equal).toBe(false)
expect(Array.from(diff.slice(1, 4))).toEqual([80, 78, 71])
})
})
70 changes: 70 additions & 0 deletions tests/fixtures/extend-expect-svg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { expect, type MatcherResult } from "bun:test"
import * as fs from "node:fs"
import * as path from "node:path"
import { compareSvg, createSvgDiff } from "../../lib/compare-svg"

async function toMatchSvgSnapshot(
this: unknown,
receivedMaybePromise: string | Promise<string>,
testPathOriginal: string,
svgName?: string,
): Promise<MatcherResult> {
const received = await receivedMaybePromise
const testPath = testPathOriginal.replace(/\.test\.tsx?$/, "")
const snapshotDir = path.join(path.dirname(testPath), "__snapshots__")
const snapshotName = svgName
? `${path.basename(testPath)}-${svgName}.snap.svg`
: `${path.basename(testPath)}.snap.svg`
const filePath = path.join(snapshotDir, snapshotName)

fs.mkdirSync(snapshotDir, { recursive: true })

const updateSnapshot =
process.argv.includes("--update-snapshots") ||
process.argv.includes("-u") ||
Boolean(process.env.BUN_UPDATE_SNAPSHOTS)

if (!fs.existsSync(filePath) || updateSnapshot) {
fs.writeFileSync(filePath, received)
return {
message: () => `Snapshot written at ${filePath}`,
pass: true,
}
}

const existingSnapshot = fs.readFileSync(filePath, "utf8")
const result = await compareSvg(existingSnapshot, received, {
strict: false,
tolerance: 2,
})

if (result.equal) {
return { message: () => "Snapshot matches", pass: true }
}

const diffPath = filePath.replace(".snap.svg", ".diff.png")
const diff = await createSvgDiff({
referenceSvg: existingSnapshot,
currentSvg: received,
highlightColor: "#ff00ff",
})
fs.writeFileSync(diffPath, diff)

return {
message: () => `Snapshot does not match. Diff saved at ${diffPath}`,
pass: false,
}
}

expect.extend({
toMatchSvgSnapshot: toMatchSvgSnapshot as never,
})

declare module "bun:test" {
interface Matchers<T = unknown> {
toMatchSvgSnapshot(
testPath: string,
svgName?: string,
): Promise<MatcherResult>
}
}
2 changes: 1 addition & 1 deletion tests/fixtures/preload.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import "bun-match-svg"
import "./extend-expect-svg"
11 changes: 6 additions & 5 deletions tests/pngSnapshotHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from "bun:test"
import { existsSync } from "node:fs"
import { mkdir, writeFile } from "node:fs/promises"
import { mkdir, readFile, writeFile } from "node:fs/promises"
import * as path from "node:path"
import looksSame from "@tscircuit/image-utils/looks-same"

Expand Down Expand Up @@ -28,7 +28,8 @@ export async function expectPngToMatchSnapshot(
return
}

const result = await looksSame(snapshotPath, Buffer.from(received), {
const reference = await readFile(snapshotPath)
const result = await looksSame(reference, Buffer.from(received), {
tolerance: 2.3,
})

Expand All @@ -37,13 +38,13 @@ export async function expectPngToMatchSnapshot(
}

const diffPath = snapshotPath.replace(".snap.png", ".diff.png")
await looksSame.createDiff({
reference: snapshotPath,
const diff = await looksSame.createDiff({
reference,
current: Buffer.from(received),
diff: diffPath,
highlightColor: "#ff00ff",
tolerance: 2.3,
})
await writeFile(diffPath, diff)

throw new Error(`PNG snapshot does not match. Diff saved at ${diffPath}`)
}
Loading