Files
svg-tools/tests/domain/bake-transform.test.ts
T

115 lines
4.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { applySourcePatches } from "../../src/document/source-patcher";
import { parseSvgSource } from "../../src/document/source-parser";
import {
composeTransformList,
parseTransformList,
} from "../../src/domain/affine";
import { bakeElementTransform } from "../../src/domain/bake-transform";
function bake(source: string, id: string) {
const parsed = parseSvgSource(source, 1);
expect(parsed.semantic).not.toBeNull();
const semantic = parsed.semantic!;
const node = [...semantic.nodes.values()].find(
(candidate) => candidate.id === id,
)!;
const matrix = composeTransformList(
parseTransformList(node.attributes.transform ?? ""),
);
const result = bakeElementTransform(
source,
node,
matrix,
semantic.preferences,
);
return { result, source: applySourcePatches(source, result.patches) };
}
describe("element transform baking", () => {
it("retains lines, polygons, and safe rectangles as native elements", () => {
const line = bake(
'<svg xmlns="http://www.w3.org/2000/svg"><line id="shape" x1="1" y1="2" x2="3" y2="4" transform="translate(10 20)"/></svg>',
"shape",
);
expect(line.source).toContain(
'<line id="shape" x1="11" y1="22" x2="13" y2="24"',
);
expect(line.source).not.toContain("transform=");
expect(line.result.warnings).toEqual([]);
const polygon = bake(
'<svg xmlns="http://www.w3.org/2000/svg"><polygon id="shape" points="0,0 2,0 2,2" transform="scale(2 3)"/></svg>',
"shape",
);
expect(polygon.source).toContain('points="0,0 4,0 4,6"');
const rectangle = bake(
'<svg xmlns="http://www.w3.org/2000/svg"><rect id="shape" x="2" y="3" width="4" height="5" rx="1" transform="translate(1 2) scale(2 3)"/></svg>',
"shape",
);
expect(rectangle.result.convertedToPath).toBe(false);
expect(rectangle.source).toContain(
'<rect id="shape" x="5" y="11" width="8" height="15" rx="2"',
);
expect(rectangle.source).toContain('ry="3"');
});
it("converts general rectangles to paths and preserves ordinary attributes", () => {
const baked = bake(
'<svg xmlns="http://www.w3.org/2000/svg"><rect id="shape" class="important" x="0" y="0" width="10" height="5" rx="2" transform="rotate(30)"/></svg>',
"shape",
);
expect(baked.result).toMatchObject({
outputElement: "path",
convertedToPath: true,
});
expect(baked.source).toContain('<path id="shape" class="important"');
expect(baked.source).toContain(' d="M ');
expect(baked.source).not.toMatch(/\s(?:x|y|width|height|rx|transform)=/u);
});
it("retains uniform circles and converts axis-scaled circles to ellipses", () => {
const circle = bake(
'<svg xmlns="http://www.w3.org/2000/svg"><circle id="shape" cx="4" cy="5" r="3" transform="rotate(40) scale(2)"/></svg>',
"shape",
);
expect(circle.result.outputElement).toBe("circle");
expect(circle.source).toContain('r="6"');
const ellipse = bake(
'<svg xmlns="http://www.w3.org/2000/svg"><circle id="shape" cx="4" cy="5" r="3" transform="scale(2 4)"/></svg>',
"shape",
);
expect(ellipse.result.outputElement).toBe("ellipse");
expect(ellipse.source).toContain('<ellipse id="shape"');
expect(ellipse.source).toContain('rx="6"');
expect(ellipse.source).toContain('ry="12"');
expect(ellipse.source).not.toContain(' r="');
});
it("bakes paths with high precision and reports stroke consequences", () => {
const baked = bake(
'<svg xmlns="http://www.w3.org/2000/svg"><path id="shape" d="M0 0A10 4 20 0 1 20 0" stroke="red" transform="matrix(-2 .5 .25 3 4 8)"/></svg>',
"shape",
);
expect(baked.source).toContain('<path id="shape" d="M 4 8 A ');
expect(baked.source).not.toContain("transform=");
expect(baked.result.warnings).toContainEqual(
expect.stringContaining("stroke-width"),
);
});
it("refuses unsupported text baking instead of partially applying", () => {
const source =
'<svg xmlns="http://www.w3.org/2000/svg"><text id="shape" transform="rotate(2)">Text</text></svg>';
expect(() => bake(source, "shape")).toThrow(/not deterministic/u);
});
it("refuses non-SVG numeric spellings instead of coercing geometry", () => {
const source =
'<svg xmlns="http://www.w3.org/2000/svg"><rect id="shape" width="0x10" height="4" transform="translate(1)"/></svg>';
expect(() => bake(source, "shape")).toThrow(/finite width/u);
});
});