mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-23 14:52:18 +10:00
319 lines
9.4 KiB
TypeScript
319 lines
9.4 KiB
TypeScript
import type { ResumeData, StyleRule } from "@reactive-resume/schema/resume/data";
|
|
import type { Template } from "@reactive-resume/schema/templates";
|
|
import type { LegacyParityHostNode } from "./legacy-parity";
|
|
import { readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
import { styleRulesSchema } from "@reactive-resume/schema/resume/data";
|
|
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
|
|
import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
|
|
import { convertLegacyStyleRules } from "./legacy-converter";
|
|
import { compareLegacyParityHostNodes, compareLegacySemanticPresentation } from "./legacy-parity";
|
|
|
|
const fixtureNames = [
|
|
"all-templates-smoke",
|
|
"array-order-tie",
|
|
"award-unbold",
|
|
"clamped-spacing",
|
|
"combined-text-host",
|
|
"custom-section-type",
|
|
"disabled-rules",
|
|
"icon-level-size",
|
|
"link-underline-3134",
|
|
"merge-specificity",
|
|
"primary-text-bold-3146",
|
|
"rich-text-all-slots",
|
|
"sanitized-intent-3199",
|
|
"section-id-uuid",
|
|
] as const;
|
|
|
|
const templates = [
|
|
"azurill",
|
|
"bronzor",
|
|
"chikorita",
|
|
"ditgar",
|
|
"ditto",
|
|
"gengar",
|
|
"glalie",
|
|
"kakuna",
|
|
"lapras",
|
|
"leafish",
|
|
"meowth",
|
|
"onyx",
|
|
"pikachu",
|
|
"rhyhorn",
|
|
"scizor",
|
|
] as const satisfies readonly Template[];
|
|
|
|
const readRules = (name: string): StyleRule[] =>
|
|
styleRulesSchema.parse(
|
|
JSON.parse(readFileSync(new URL(`./__fixtures__/legacy/${name}.json`, import.meta.url), "utf8")),
|
|
);
|
|
|
|
const buildFixture = (rules: StyleRule[]): ResumeData => {
|
|
const data = structuredClone(defaultResumeData);
|
|
data.picture.hidden = true;
|
|
data.basics = {
|
|
name: "Ada Lovelace",
|
|
headline: "Engineer",
|
|
email: "ada@example.com",
|
|
phone: "",
|
|
location: "London",
|
|
website: { url: "", label: "" },
|
|
customFields: [],
|
|
};
|
|
data.summary.hidden = false;
|
|
data.summary.content =
|
|
'<p>Paragraph <strong>bold</strong> <mark>mark</mark> <a href="https://example.com">link</a></p><ul><li>List item</li></ul>';
|
|
data.sections.experience.items = [
|
|
{
|
|
id: "experience-1",
|
|
hidden: false,
|
|
company: "Analytical Engines",
|
|
position: "Engineer",
|
|
location: "London",
|
|
period: "1842",
|
|
website: { url: "https://example.com/work", label: "Work", inlineLink: true },
|
|
description: "<p>Built engines.</p>",
|
|
roles: [
|
|
{
|
|
id: "role-1",
|
|
position: "Senior Engineer",
|
|
period: "1843",
|
|
description: "<p>Led the engine team.</p>",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
data.sections.education.items = [
|
|
{
|
|
id: "education-1",
|
|
hidden: false,
|
|
school: "University of London",
|
|
degree: "BSc",
|
|
area: "Mathematics",
|
|
grade: "First",
|
|
location: "London",
|
|
period: "1835",
|
|
website: { url: "", label: "", inlineLink: false },
|
|
description: "<p>Studied analytical engines.</p>",
|
|
},
|
|
];
|
|
data.sections.skills.items = [
|
|
{
|
|
id: "skill-1",
|
|
hidden: false,
|
|
icon: "code",
|
|
iconColor: "",
|
|
name: "Mathematics",
|
|
proficiency: "Expert",
|
|
level: 3,
|
|
keywords: ["Analysis"],
|
|
},
|
|
];
|
|
data.sections.awards.items = [
|
|
{
|
|
id: "award-1",
|
|
hidden: false,
|
|
title: "Prize",
|
|
awarder: "Society",
|
|
date: "1843",
|
|
website: { url: "", label: "", inlineLink: false },
|
|
description: "<p>First programmer.</p>",
|
|
},
|
|
];
|
|
data.customSections = [
|
|
{
|
|
id: "1d7312cb-9ba2-4d42-9ca8-2a9ca05f9f37",
|
|
type: "experience",
|
|
title: "Consulting",
|
|
icon: "briefcase",
|
|
columns: 1,
|
|
hidden: false,
|
|
keepTogether: false,
|
|
startOnNewPage: false,
|
|
items: [
|
|
{
|
|
id: "custom-experience-1",
|
|
hidden: false,
|
|
company: "Difference Engines",
|
|
position: "Consultant",
|
|
location: "London",
|
|
period: "1844",
|
|
website: { url: "", label: "", inlineLink: false },
|
|
description: "<p>Advised builders.</p>",
|
|
roles: [],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
data.metadata.layout.pages = [
|
|
{
|
|
fullWidth: true,
|
|
main: ["summary", "experience", "education", "skills", "awards", "1d7312cb-9ba2-4d42-9ca8-2a9ca05f9f37"],
|
|
sidebar: [],
|
|
},
|
|
];
|
|
data.metadata.styleRules = rules;
|
|
return data;
|
|
};
|
|
|
|
describe("compareLegacySemanticPresentation", () => {
|
|
it("renders the mandatory target shapes in the shared parity document", () => {
|
|
const data = buildFixture([]);
|
|
|
|
expect(data.sections.experience.items[0]?.roles[0]?.position).toBe("Senior Engineer");
|
|
expect(data.customSections).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
id: "1d7312cb-9ba2-4d42-9ca8-2a9ca05f9f37",
|
|
type: "experience",
|
|
}),
|
|
]),
|
|
);
|
|
expect(data.metadata.layout.pages[0]?.main).toContain("1d7312cb-9ba2-4d42-9ca8-2a9ca05f9f37");
|
|
});
|
|
|
|
it.each(fixtureNames)("matches final primitive props for %s", async (fixture) => {
|
|
const data = buildFixture(readRules(fixture));
|
|
const conversion = convertLegacyStyleRules(data);
|
|
const comparison = await compareLegacySemanticPresentation({
|
|
data,
|
|
convertedSource: conversion.source,
|
|
templates: ["onyx"],
|
|
});
|
|
|
|
expect(comparison.pageCountMismatches).toEqual([]);
|
|
expect(comparison.primitivePropMismatches).toEqual([]);
|
|
expect(comparison.mismatches).toEqual([]);
|
|
});
|
|
|
|
it("matches final primitive props on every template", async () => {
|
|
const data = buildFixture(readRules("all-templates-smoke"));
|
|
const conversion = convertLegacyStyleRules(data);
|
|
const comparison = await compareLegacySemanticPresentation({
|
|
data,
|
|
convertedSource: conversion.source,
|
|
templates,
|
|
});
|
|
|
|
expect(comparison.pageCountMismatches).toEqual([]);
|
|
expect(comparison.primitivePropMismatches).toEqual([]);
|
|
expect(comparison.mismatches).toEqual([]);
|
|
}, 30_000);
|
|
|
|
it("matches the real sample on every template for a substantive custom-section rule", async () => {
|
|
const data = structuredClone(sampleResumeData);
|
|
data.metadata.styleRules = readRules("custom-section-type");
|
|
const conversion = convertLegacyStyleRules(data);
|
|
const comparison = await compareLegacySemanticPresentation({
|
|
data,
|
|
convertedSource: conversion.source,
|
|
templates,
|
|
});
|
|
|
|
expect(comparison.pageCountMismatches).toEqual([]);
|
|
expect(comparison.primitivePropMismatches).toEqual([]);
|
|
expect(comparison.mismatches).toEqual([]);
|
|
}, 30_000);
|
|
|
|
it("preserves Scizor template-after Bold color for converted legacy text rules", async () => {
|
|
const data = buildFixture(readRules("primary-text-bold-3146"));
|
|
data.metadata.template = "scizor";
|
|
const conversion = convertLegacyStyleRules(data);
|
|
const comparison = await compareLegacySemanticPresentation({
|
|
data,
|
|
convertedSource: conversion.source,
|
|
templates: ["scizor"],
|
|
});
|
|
|
|
expect(comparison.pageCountMismatches).toEqual([]);
|
|
expect(comparison.primitivePropMismatches).toEqual([]);
|
|
expect(comparison.mismatches).toEqual([]);
|
|
});
|
|
|
|
it.each(["onyx", "meowth"] as const)(
|
|
"matches combined separator and box-style primitives on %s",
|
|
async (template) => {
|
|
const data = buildFixture(readRules("combined-text-host"));
|
|
const conversion = convertLegacyStyleRules(data);
|
|
const comparison = await compareLegacySemanticPresentation({
|
|
data,
|
|
convertedSource: conversion.source,
|
|
templates: [template],
|
|
});
|
|
|
|
expect(comparison.mismatches).toEqual([]);
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
["text", { type: "TEXT", value: "legacy" }, { type: "TEXT", value: "semantic" }],
|
|
[
|
|
"props",
|
|
{ type: "LINK", props: { src: "https://legacy.example" } },
|
|
{ type: "LINK", props: { src: "https://semantic.example" } },
|
|
],
|
|
["alpha", { type: "TEXT", style: { color: "#11223380" } }, { type: "TEXT", style: { color: "#112233" } }],
|
|
["transparent", { type: "TEXT", style: { color: "transparent" } }, { type: "TEXT", style: { color: "black" } }],
|
|
["unsupported color", { type: "TEXT", style: { color: "brand-ink" } }, { type: "TEXT", style: { color: "black" } }],
|
|
["units", { type: "TEXT", style: { fontSize: "12pt" } }, { type: "TEXT", style: { fontSize: "12px" } }],
|
|
["hierarchy", { type: "VIEW", children: [{ type: "TEXT", value: "same" }] }, { type: "TEXT", value: "same" }],
|
|
["geometry", { type: "VIEW", style: { width: 10 } }, { type: "VIEW", style: { width: 11 } }],
|
|
[
|
|
"split wrapper inherited style",
|
|
{
|
|
type: "TEXT",
|
|
style: { color: "#112233" },
|
|
children: [{ type: "TEXT", style: { color: "#112233" }, value: "A" }],
|
|
},
|
|
{
|
|
type: "TEXT",
|
|
style: { color: "#000000" },
|
|
children: [{ type: "TEXT", style: { color: "#112233" }, value: "A" }],
|
|
},
|
|
],
|
|
] as const)("detects %s tampering", (_name, legacy, semantic) => {
|
|
expect(compareLegacyParityHostNodes(legacy, semantic)).not.toEqual([]);
|
|
});
|
|
|
|
it("normalizes only presentation-neutral empty Text artifacts", () => {
|
|
expect(
|
|
compareLegacyParityHostNodes(
|
|
{
|
|
type: "VIEW",
|
|
style: { color: "#112233" },
|
|
children: [{ type: "TEXT" }, { type: "TEXT", value: "same" }],
|
|
},
|
|
{
|
|
type: "VIEW",
|
|
style: { color: "#112233" },
|
|
children: [{ type: "TEXT", value: "same" }],
|
|
},
|
|
),
|
|
).toEqual([]);
|
|
|
|
for (const meaningfulEmptyText of [
|
|
{ type: "TEXT", style: { backgroundColor: "#112233" } },
|
|
{ type: "TEXT", style: { width: 1 } },
|
|
{ type: "TEXT", props: { id: "semantic-payload" } },
|
|
] satisfies LegacyParityHostNode[]) {
|
|
expect(
|
|
compareLegacyParityHostNodes({ type: "VIEW", children: [meaningfulEmptyText] }, { type: "VIEW", children: [] }),
|
|
).not.toEqual([]);
|
|
}
|
|
});
|
|
|
|
it("retains legitimate inheritance and renderer-default equivalence", () => {
|
|
expect(
|
|
compareLegacyParityHostNodes(
|
|
{
|
|
type: "TEXT",
|
|
style: { color: "rgb(18, 52, 86)", fontWeight: 400 },
|
|
children: [{ type: "TEXT", value: "same" }],
|
|
},
|
|
{ type: "TEXT", style: { color: "#123456" }, children: [{ type: "TEXT", value: "same" }] },
|
|
),
|
|
).toEqual([]);
|
|
});
|
|
});
|