Files
oh-my-pi/packages/utils/test/mermaid/golden.test.ts
T
2026-06-18 18:52:38 +02:00

254 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Golden-file tests for the ASCII/Unicode renderer.
*
* Ported from AlexanderGrooff/mermaid-ascii cmd/graph_test.go.
* Each .txt file contains mermaid input above a `---` separator
* and the expected ASCII/Unicode output below it.
*
* Test data: 44 ASCII files + 22 Unicode files = 66 total.
*/
import { describe, expect, it } from "bun:test";
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { renderMermaidAscii } from "../../src/vendor/mermaid-ascii/ascii/index";
import { DIAGONAL_CHARS, hasDiagonalLines } from "../../src/vendor/mermaid-ascii/ascii/validate";
// ============================================================================
// Test case parser — matches Go's testutil.ReadTestCase format
// ============================================================================
interface TestCase {
mermaid: string;
expected: string;
paddingX: number;
paddingY: number;
}
/**
* Parse a golden test file into its components.
* Format:
* [paddingX=N] (optional)
* [paddingY=N] (optional)
* <mermaid code>
* ---
* <expected output>
*/
function parseTestCase(content: string): TestCase {
const tc: TestCase = { mermaid: "", expected: "", paddingX: 5, paddingY: 5 };
const lines = content.split("\n");
const paddingRegex = /^(?:padding([xy]))\s*=\s*(\d+)\s*$/i;
let inMermaid = true;
let mermaidStarted = false;
const mermaidLines: string[] = [];
const expectedLines: string[] = [];
for (const line of lines) {
if (line === "---") {
inMermaid = false;
continue;
}
if (inMermaid) {
const trimmed = line.trim();
// Before mermaid code starts, parse padding directives and skip blanks
if (!mermaidStarted) {
if (trimmed === "") continue;
const match = trimmed.match(paddingRegex);
if (match) {
const value = parseInt(match[2]!, 10);
if (match[1]!.toLowerCase() === "x") {
tc.paddingX = value;
} else {
tc.paddingY = value;
}
continue;
}
}
mermaidStarted = true;
mermaidLines.push(line);
} else {
expectedLines.push(line);
}
}
tc.mermaid = `${mermaidLines.join("\n")}\n`;
// Strip final trailing newline (matches Go's strings.TrimSuffix(expected, "\n"))
let expected = expectedLines.join("\n");
if (expected.endsWith("\n")) {
expected = expected.slice(0, -1);
}
tc.expected = expected;
return tc;
}
// ============================================================================
// Whitespace normalization — matches Go's testutil.NormalizeWhitespace
// ============================================================================
/**
* Normalize whitespace for comparison:
* - Trim trailing spaces from each line
* - Remove leading/trailing blank lines
*/
function normalizeWhitespace(s: string): string {
const lines = s.split("\n");
const normalized = lines.map(l => l.trimEnd());
// Remove leading blank lines
while (normalized.length > 0 && normalized[0] === "") {
normalized.shift();
}
// Remove trailing blank lines
while (normalized.length > 0 && normalized[normalized.length - 1] === "") {
normalized.pop();
}
return normalized.join("\n");
}
/** Replace spaces with middle dots for clearer diff output. */
function visualizeWhitespace(s: string): string {
return s.replaceAll(" ", "·");
}
// ============================================================================
// Test runner — dynamically loads all golden files from testdata directories
// ============================================================================
function runGoldenTests(dir: string, useAscii: boolean): void {
const files = readdirSync(dir)
.filter(f => f.endsWith(".txt"))
.sort();
for (const file of files) {
const testName = file.replace(".txt", "");
it(testName, () => {
const content = readFileSync(join(dir, file), "utf-8");
const tc = parseTestCase(content);
const actual = renderMermaidAscii(tc.mermaid, {
useAscii,
paddingX: tc.paddingX,
paddingY: tc.paddingY,
});
const normalizedExpected = normalizeWhitespace(tc.expected);
const normalizedActual = normalizeWhitespace(actual);
if (normalizedExpected !== normalizedActual) {
const expectedVis = visualizeWhitespace(normalizedExpected);
const actualVis = visualizeWhitespace(normalizedActual);
expect(actualVis).toBe(expectedVis);
}
});
}
}
// ============================================================================
// Test suites
// ============================================================================
const testdataDir = join(import.meta.dir, "testdata");
describe("ASCII rendering", () => {
runGoldenTests(join(testdataDir, "ascii"), true);
});
describe("Unicode rendering", () => {
runGoldenTests(join(testdataDir, "unicode"), false);
});
// ============================================================================
// Config behavior tests — ported from Go's TestGraphUseAsciiConfig
// ============================================================================
describe("Config behavior", () => {
const mermaidInput = "graph LR\nA --> B";
it("ASCII and Unicode outputs should differ", () => {
const asciiOutput = renderMermaidAscii(mermaidInput, { useAscii: true });
const unicodeOutput = renderMermaidAscii(mermaidInput, { useAscii: false });
expect(asciiOutput).not.toBe(unicodeOutput);
});
it("ASCII output should not contain Unicode box-drawing characters", () => {
const output = renderMermaidAscii(mermaidInput, { useAscii: true });
expect(output).not.toContain("┌");
expect(output).not.toContain("─");
expect(output).not.toContain("│");
});
it("Unicode output should contain Unicode box-drawing characters", () => {
const output = renderMermaidAscii(mermaidInput, { useAscii: false });
const hasUnicode = output.includes("┌") || output.includes("─") || output.includes("│");
expect(hasUnicode).toBe(true);
});
});
// ============================================================================
// Diagonal validation — ensures all edges use orthogonal Manhattan routing
// ============================================================================
describe("Diagonal validation", () => {
const asciiDir = join(testdataDir, "ascii");
const unicodeDir = join(testdataDir, "unicode");
it("ASCII output should never contain diagonal characters", () => {
// Test all ASCII golden files
const files = readdirSync(asciiDir).filter(f => f.endsWith(".txt"));
for (const file of files) {
const content = readFileSync(join(asciiDir, file), "utf-8");
const { mermaid, paddingX, paddingY } = parseTestCase(content);
const output = renderMermaidAscii(mermaid, {
useAscii: true,
boxBorderPadding: paddingX,
paddingY: paddingY,
});
// Check for diagonal characters
for (const char of DIAGONAL_CHARS.ascii) {
expect(output).not.toContain(char);
}
}
});
it("Unicode output should never contain diagonal characters", () => {
// Test all Unicode golden files
const files = readdirSync(unicodeDir).filter(f => f.endsWith(".txt"));
for (const file of files) {
const content = readFileSync(join(unicodeDir, file), "utf-8");
const { mermaid, paddingX, paddingY } = parseTestCase(content);
const output = renderMermaidAscii(mermaid, {
useAscii: false,
boxBorderPadding: paddingX,
paddingY: paddingY,
});
// Check for diagonal characters
for (const char of DIAGONAL_CHARS.unicode) {
expect(output).not.toContain(char);
}
}
});
it("hasDiagonalLines utility correctly detects diagonal characters", () => {
// Should detect ASCII diagonals
expect(hasDiagonalLines("A / B")).toBe(true);
expect(hasDiagonalLines("A \\ B")).toBe(true);
// Should detect Unicode diagonals
expect(hasDiagonalLines("A ╱ B")).toBe(true);
expect(hasDiagonalLines("A ╲ B")).toBe(true);
// Should not flag clean output
expect(hasDiagonalLines("┌───┐\n│ A │\n└───┘")).toBe(false);
expect(hasDiagonalLines("+---+\n| A |\n+---+")).toBe(false);
});
});