diff --git a/docs/VISUAL-REGRESSION-TESTING.md b/docs/VISUAL-REGRESSION-TESTING.md index afb8e75..260e8b1 100644 --- a/docs/VISUAL-REGRESSION-TESTING.md +++ b/docs/VISUAL-REGRESSION-TESTING.md @@ -108,6 +108,10 @@ The test runner automatically creates a dark mode variant for any fixture named | `opacity` | Elements at 100%, 60%, 30% opacity; semi-transparent text | | `all-fonts` | All 7 supported font families (Excalifont, Nunito, Lilita One, Comic Shanns, Virgil, Cascadia, Liberation Sans) | | `colored-arrows` | Elbow/curved/straight arrows with non-transparent backgroundColor; verifies arrow paths are not filled | +| `combine-horizontal` | Combine command: horizontal layout of basic-shapes + arrows-lines | +| `combine-vertical` | Combine command: vertical layout of basic-shapes + arrows-lines | +| `combine-labels` | Combine command: horizontal layout with --labels flag | +| `combine-gap0` | Combine command: horizontal layout with --gap 0 (no gap) | ## Updating Baselines After Rendering Changes diff --git a/tests/visual/baselines/combine-gap0.png b/tests/visual/baselines/combine-gap0.png new file mode 100644 index 0000000..296a56b Binary files /dev/null and b/tests/visual/baselines/combine-gap0.png differ diff --git a/tests/visual/baselines/combine-horizontal.png b/tests/visual/baselines/combine-horizontal.png new file mode 100644 index 0000000..84e85c7 Binary files /dev/null and b/tests/visual/baselines/combine-horizontal.png differ diff --git a/tests/visual/baselines/combine-labels.png b/tests/visual/baselines/combine-labels.png new file mode 100644 index 0000000..a01e35e Binary files /dev/null and b/tests/visual/baselines/combine-labels.png differ diff --git a/tests/visual/baselines/combine-vertical.png b/tests/visual/baselines/combine-vertical.png new file mode 100644 index 0000000..cbbd638 Binary files /dev/null and b/tests/visual/baselines/combine-vertical.png differ diff --git a/tests/visual/baselines/diff-test--dark.png b/tests/visual/baselines/diff-test--dark.png index 1324468..a35ffe6 100644 Binary files a/tests/visual/baselines/diff-test--dark.png and b/tests/visual/baselines/diff-test--dark.png differ diff --git a/tests/visual/run.ts b/tests/visual/run.ts index fb4db3a..f273fb7 100644 --- a/tests/visual/run.ts +++ b/tests/visual/run.ts @@ -41,7 +41,7 @@ const MAX_DIFF_PERCENT = 1.0; const MAX_SVG_DIFF_PERCENT = 5.0; type TestFormat = "png" | "svg"; -type TestType = "export" | "diff"; +type TestType = "export" | "diff" | "combine"; interface TestCase { name: string; @@ -53,6 +53,10 @@ interface TestCase { format: TestFormat; type: TestType; diffNewFixture?: string; // For diff tests: the "new" file + combineFixtures?: string[]; // For combine tests: array of fixture paths + layout?: "horizontal" | "vertical"; + labels?: boolean; + gap?: number; } function discoverTests(): TestCase[] { @@ -214,6 +218,62 @@ function discoverTests(): TestCase[] { }); } + // Add combine tests + const combineFixtureA = join(FIXTURES_DIR, "basic-shapes.excalidraw"); + const combineFixtureB = join(FIXTURES_DIR, "arrows-lines.excalidraw"); + if (existsSync(combineFixtureA) && existsSync(combineFixtureB)) { + tests.push({ + name: "combine-horizontal", + fixture: combineFixtureA, + combineFixtures: [combineFixtureA, combineFixtureB], + outputName: "combine-horizontal.png", + baselineName: "combine-horizontal.png", + darkMode: false, + scale: 1, + format: "png", + type: "combine", + }); + + tests.push({ + name: "combine-vertical", + fixture: combineFixtureA, + combineFixtures: [combineFixtureA, combineFixtureB], + outputName: "combine-vertical.png", + baselineName: "combine-vertical.png", + darkMode: false, + scale: 1, + format: "png", + type: "combine", + layout: "vertical", + }); + + tests.push({ + name: "combine-labels", + fixture: combineFixtureA, + combineFixtures: [combineFixtureA, combineFixtureB], + outputName: "combine-labels.png", + baselineName: "combine-labels.png", + darkMode: false, + scale: 1, + format: "png", + type: "combine", + labels: true, + }); + + tests.push({ + name: "combine-gap0", + fixture: combineFixtureA, + combineFixtures: [combineFixtureA, combineFixtureB], + outputName: "combine-gap0.png", + baselineName: "combine-gap0.png", + darkMode: false, + scale: 1, + format: "png", + type: "combine", + gap: 0, + }); + } + return tests; } @@ -312,6 +372,48 @@ async function runDiffCli( return { success: exitCode === 0, stderr }; } +async function runCombineCli( + fixtures: string[], + outputPath: string, + options: { + layout?: "horizontal" | "vertical"; + labels?: boolean; + gap?: number; + } = {}, +): Promise<{ success: boolean; stderr: string }> { + const relOutput = relative(PROJECT_ROOT, outputPath); + const relFixtures = fixtures.map((f) => relative(PROJECT_ROOT, f)); + + const args = [ + "docker", + "run", + "--rm", + "-v", + `${PROJECT_ROOT}:/data`, + "-w", + "/data", + "excalirender:test", + "combine", + ...relFixtures, + "-o", + relOutput, + ]; + if (options.layout) args.push("--layout", options.layout); + if (options.labels) args.push("--labels"); + if (options.gap !== undefined) args.push("--gap", String(options.gap)); + + const proc = Bun.spawn(args, { + cwd: PROJECT_ROOT, + stdout: "pipe", + stderr: "pipe", + }); + + const exitCode = await proc.exited; + const stderr = await new Response(proc.stderr).text(); + + return { success: exitCode === 0, stderr }; +} + /** * Render an SVG file to a PNG buffer using resvg-js. */ @@ -488,7 +590,13 @@ async function main() { // Generate output via Docker let result: { success: boolean; stderr: string }; - if (test.type === "diff" && test.diffNewFixture) { + if (test.type === "combine" && test.combineFixtures) { + result = await runCombineCli(test.combineFixtures, outputPath, { + layout: test.layout, + labels: test.labels, + gap: test.gap, + }); + } else if (test.type === "diff" && test.diffNewFixture) { result = await runDiffCli(test.fixture, test.diffNewFixture, outputPath, { darkMode: test.darkMode, });