mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-24 07:12:18 +10:00
feat: add semantic CSS stylesheets (#3274)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor Agent
parent
4ac19f81b3
commit
d2ffbf9618
@@ -0,0 +1,535 @@
|
||||
import type { PdfPreflightResult } from "@reactive-resume/pdf/server";
|
||||
import type { CompileStylesheetResult } from "@reactive-resume/resume/stylesheet";
|
||||
import type { ResumeData } from "@reactive-resume/schema/resume/data";
|
||||
import type { SemanticStylesheet, StylesheetSource } from "@reactive-resume/schema/resume/stylesheet";
|
||||
import type { SemanticCssEventInput } from "./stylesheet-observability";
|
||||
import type { StylesheetSnapshot } from "./stylesheet-service";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
|
||||
import { createStylesheetService } from "./stylesheet-service";
|
||||
|
||||
vi.mock("@reactive-resume/db/client", () => ({ db: {}, getPool: vi.fn() }));
|
||||
|
||||
const validSource = {
|
||||
languageVersion: 1,
|
||||
text: "@version 1;\nsection { color: #112233; }\n",
|
||||
} satisfies StylesheetSource;
|
||||
const invalidSource = {
|
||||
languageVersion: 1,
|
||||
text: "@version 1;\nsection {",
|
||||
} satisfies StylesheetSource;
|
||||
const previousSource = {
|
||||
languageVersion: 1,
|
||||
text: "@version 1;\nsection { color: #445566; }\n",
|
||||
} satisfies StylesheetSource;
|
||||
const previousApplied = {
|
||||
languageVersion: 1,
|
||||
text: "@version 1;\nsection { color: #778899; }\n",
|
||||
} satisfies StylesheetSource;
|
||||
const previousStylesheet = {
|
||||
mode: "semantic",
|
||||
source: previousSource,
|
||||
applied: previousApplied,
|
||||
} satisfies SemanticStylesheet;
|
||||
const diagnostic = {
|
||||
code: "PARSE_ERROR",
|
||||
severity: "error" as const,
|
||||
message: "Invalid stylesheet.",
|
||||
range: {
|
||||
start: { line: 1, column: 1, offset: 0 },
|
||||
end: { line: 1, column: 1, offset: 0 },
|
||||
},
|
||||
};
|
||||
const successfulCompile = {
|
||||
program: { languageVersion: 1, rules: [] },
|
||||
diagnostics: [],
|
||||
} satisfies CompileStylesheetResult;
|
||||
const failedCompile = { program: null, diagnostics: [diagnostic] } satisfies CompileStylesheetResult;
|
||||
const successfulPreflight = {
|
||||
ok: true,
|
||||
pageCount: 1,
|
||||
byteCount: 256,
|
||||
diagnostics: [],
|
||||
} satisfies PdfPreflightResult;
|
||||
|
||||
const commonMutationInput = {
|
||||
id: "resume-1",
|
||||
userId: "user-1",
|
||||
expectedRevision: 3,
|
||||
expectedRenderDataVersion: 8,
|
||||
editGeneration: 11,
|
||||
} as const;
|
||||
|
||||
const snapshot = (stylesheet: SemanticStylesheet = previousStylesheet): StylesheetSnapshot => {
|
||||
const data: ResumeData = structuredClone(defaultResumeData);
|
||||
data.metadata.stylesheet = stylesheet;
|
||||
data.metadata.styleRules = [
|
||||
{
|
||||
id: "legacy-rule",
|
||||
label: "Legacy",
|
||||
enabled: true,
|
||||
target: { scope: "global" },
|
||||
slots: { heading: { color: "#123456" } },
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
id: "resume-1",
|
||||
userId: "user-1",
|
||||
data,
|
||||
isLocked: false,
|
||||
stylesheetRevision: 3,
|
||||
renderDataVersion: 8,
|
||||
updatedAt: new Date("2026-07-28T12:00:00.000Z"),
|
||||
};
|
||||
};
|
||||
|
||||
type HarnessOptions = {
|
||||
initial?: StylesheetSnapshot;
|
||||
compile?: (source: StylesheetSource) => CompileStylesheetResult;
|
||||
preflight?: ((input: { data: ResumeData; stylesheet: StylesheetSource }) => Promise<PdfPreflightResult>) | undefined;
|
||||
parityMismatches?: readonly string[];
|
||||
parity?: () => Promise<{ mismatches: readonly string[] }>;
|
||||
locked?: StylesheetSnapshot;
|
||||
useDefaultObserver?: boolean;
|
||||
};
|
||||
|
||||
const createHarness = (options: HarnessOptions = {}) => {
|
||||
const callOrder: string[] = [];
|
||||
const events: SemanticCssEventInput[] = [];
|
||||
let persisted = structuredClone(options.initial ?? snapshot());
|
||||
const compile = vi.fn((source: StylesheetSource) => {
|
||||
callOrder.push("compile");
|
||||
return options.compile?.(source) ?? successfulCompile;
|
||||
});
|
||||
const preflight = options.preflight
|
||||
? vi.fn(options.preflight)
|
||||
: vi.fn(() => {
|
||||
callOrder.push("preflight");
|
||||
return Promise.resolve(successfulPreflight);
|
||||
});
|
||||
const parity = vi.fn(() => {
|
||||
callOrder.push("parity");
|
||||
return options.parity?.() ?? Promise.resolve({ mismatches: options.parityMismatches ?? [] });
|
||||
});
|
||||
const publish = vi.fn(() => {
|
||||
callOrder.push("publish");
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const service = createStylesheetService({
|
||||
readSnapshot: () => {
|
||||
callOrder.push("readSnapshot");
|
||||
return Promise.resolve(structuredClone(persisted));
|
||||
},
|
||||
convertLegacy: () => {
|
||||
callOrder.push("convertLegacy");
|
||||
return { languageVersion: 1, text: "@version 1;\n" };
|
||||
},
|
||||
compile,
|
||||
preflight:
|
||||
options.preflight === undefined
|
||||
? preflight
|
||||
: (input) => {
|
||||
callOrder.push("preflight");
|
||||
return preflight(input);
|
||||
},
|
||||
parity,
|
||||
transaction: async (run) => {
|
||||
callOrder.push("begin");
|
||||
try {
|
||||
const result = await run({
|
||||
lock: () => {
|
||||
callOrder.push("lock");
|
||||
return Promise.resolve(structuredClone(options.locked ?? persisted));
|
||||
},
|
||||
update: ({ data }) => {
|
||||
callOrder.push("update");
|
||||
persisted = {
|
||||
...persisted,
|
||||
data,
|
||||
stylesheetRevision: persisted.stylesheetRevision + 1,
|
||||
};
|
||||
return Promise.resolve(structuredClone(persisted));
|
||||
},
|
||||
});
|
||||
callOrder.push("commit");
|
||||
return result;
|
||||
} catch (error) {
|
||||
callOrder.push("rollback");
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
compare: (locked, input) => {
|
||||
callOrder.push("compare");
|
||||
return (
|
||||
locked.stylesheetRevision === input.expectedRevision &&
|
||||
locked.renderDataVersion === input.expectedRenderDataVersion
|
||||
);
|
||||
},
|
||||
publish,
|
||||
...(options.useDefaultObserver ? {} : { observe: (event: SemanticCssEventInput) => events.push(event) }),
|
||||
});
|
||||
|
||||
return { callOrder, compile, events, parity, persisted: () => persisted, preflight, publish, service };
|
||||
};
|
||||
|
||||
describe("stylesheet service", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("stores invalid editable source while preserving the last-valid applied source and render version", async () => {
|
||||
const harness = createHarness({ compile: () => failedCompile });
|
||||
|
||||
const result = await harness.service.mutate({
|
||||
...commonMutationInput,
|
||||
transition: "edit_source",
|
||||
source: invalidSource,
|
||||
});
|
||||
|
||||
expect(result.stylesheet.source).toEqual(invalidSource);
|
||||
expect(result.stylesheet.applied).toEqual(previousApplied);
|
||||
expect(result.revision).toBe(4);
|
||||
expect(result.renderDataVersion).toBe(8);
|
||||
expect(result.editGeneration).toBe(11);
|
||||
expect(result.diagnostics).toEqual([diagnostic]);
|
||||
expect(harness.preflight).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("promotes a valid edit only in semantic mode after preflight outside the short transaction", async () => {
|
||||
const harness = createHarness();
|
||||
|
||||
const result = await harness.service.mutate({
|
||||
...commonMutationInput,
|
||||
transition: "edit_source",
|
||||
source: validSource,
|
||||
});
|
||||
|
||||
expect(result.stylesheet.source).toEqual(validSource);
|
||||
expect(result.stylesheet.applied).toEqual(validSource);
|
||||
expect(harness.callOrder).toEqual([
|
||||
"readSnapshot",
|
||||
"compile",
|
||||
"preflight",
|
||||
"begin",
|
||||
"lock",
|
||||
"compare",
|
||||
"update",
|
||||
"commit",
|
||||
"publish",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps a valid legacy-mode edit dormant without preflight", async () => {
|
||||
const initial = snapshot({ ...previousStylesheet, mode: "legacy" });
|
||||
const harness = createHarness({ initial });
|
||||
|
||||
const result = await harness.service.mutate({
|
||||
...commonMutationInput,
|
||||
transition: "edit_source",
|
||||
source: validSource,
|
||||
});
|
||||
|
||||
expect(result.stylesheet).toEqual({
|
||||
mode: "legacy",
|
||||
source: validSource,
|
||||
applied: previousApplied,
|
||||
});
|
||||
expect(harness.preflight).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("preserves editable source but not applied source when semantic render preflight fails", async () => {
|
||||
const harness = createHarness({
|
||||
preflight: async () => ({
|
||||
ok: false,
|
||||
code: "STYLESHEET_PREFLIGHT_RENDER_FAILED",
|
||||
message: "PDF rendering failed.",
|
||||
diagnostics: [],
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await harness.service.mutate({
|
||||
...commonMutationInput,
|
||||
transition: "edit_source",
|
||||
source: validSource,
|
||||
});
|
||||
|
||||
expect(result.stylesheet.source).toEqual(validSource);
|
||||
expect(result.stylesheet.applied).toEqual(previousApplied);
|
||||
expect(result.diagnostics).toEqual([
|
||||
expect.objectContaining({ code: "STYLESHEET_PREFLIGHT_RENDER_FAILED", severity: "error" }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("requires parity and preflight before explicit activation", async () => {
|
||||
const initial = snapshot({ ...previousStylesheet, mode: "legacy" });
|
||||
const harness = createHarness({ initial });
|
||||
|
||||
const result = await harness.service.mutate({
|
||||
...commonMutationInput,
|
||||
transition: "activate",
|
||||
source: validSource,
|
||||
});
|
||||
|
||||
expect(result.stylesheet).toEqual({ mode: "semantic", source: validSource, applied: validSource });
|
||||
expect(harness.parity).toHaveBeenCalledOnce();
|
||||
expect(harness.preflight).toHaveBeenCalledOnce();
|
||||
expect(result.revision).toBe(4);
|
||||
expect(result.renderDataVersion).toBe(8);
|
||||
expect(harness.events).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "semantic_css.activate",
|
||||
durationMs: expect.any(Number),
|
||||
pageCount: 1,
|
||||
revision: 4,
|
||||
success: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("records a sanitized compile failure metric when the compiler throws", async () => {
|
||||
const privateText = "private compiler failure john.doe@example.com";
|
||||
const log = vi.spyOn(console, "info").mockImplementation(() => undefined);
|
||||
const harness = createHarness({
|
||||
compile: () => {
|
||||
throw new Error(privateText);
|
||||
},
|
||||
useDefaultObserver: true,
|
||||
});
|
||||
|
||||
await expect(
|
||||
harness.service.mutate({ ...commonMutationInput, transition: "edit_source", source: validSource }),
|
||||
).rejects.toThrow(privateText);
|
||||
|
||||
expect(log.mock.calls.map(([event]) => event)).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "semantic_css.compile",
|
||||
durationMs: expect.any(Number),
|
||||
diagnosticCodes: [],
|
||||
success: false,
|
||||
}),
|
||||
);
|
||||
const serialized = JSON.stringify(log.mock.calls);
|
||||
expect(serialized).not.toContain(privateText);
|
||||
expect(serialized).not.toContain(validSource.text);
|
||||
expect(serialized).not.toContain("resume-1");
|
||||
});
|
||||
|
||||
it("records a sanitized preflight failure metric when the runner throws", async () => {
|
||||
const privateText = "private preflight failure john.doe@example.com";
|
||||
const log = vi.spyOn(console, "info").mockImplementation(() => undefined);
|
||||
const harness = createHarness({
|
||||
preflight: () => Promise.reject(new Error(privateText)),
|
||||
useDefaultObserver: true,
|
||||
});
|
||||
|
||||
await expect(
|
||||
harness.service.mutate({ ...commonMutationInput, transition: "edit_source", source: validSource }),
|
||||
).rejects.toThrow(privateText);
|
||||
|
||||
expect(log.mock.calls.map(([event]) => event)).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "semantic_css.preflight",
|
||||
durationMs: expect.any(Number),
|
||||
diagnosticCodes: [],
|
||||
success: false,
|
||||
}),
|
||||
);
|
||||
const serialized = JSON.stringify(log.mock.calls);
|
||||
expect(serialized).not.toContain(privateText);
|
||||
expect(serialized).not.toContain(validSource.text);
|
||||
expect(serialized).not.toContain("resume-1");
|
||||
});
|
||||
|
||||
it("records a sanitized activation failure metric when parity throws", async () => {
|
||||
const privateText = "private parity failure john.doe@example.com";
|
||||
const log = vi.spyOn(console, "info").mockImplementation(() => undefined);
|
||||
const harness = createHarness({
|
||||
initial: snapshot({ ...previousStylesheet, mode: "legacy" }),
|
||||
parity: () => Promise.reject(new Error(privateText)),
|
||||
useDefaultObserver: true,
|
||||
});
|
||||
|
||||
await expect(
|
||||
harness.service.mutate({ ...commonMutationInput, transition: "activate", source: validSource }),
|
||||
).rejects.toThrow(privateText);
|
||||
|
||||
expect(log.mock.calls.map(([event]) => event)).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "semantic_css.activate",
|
||||
durationMs: expect.any(Number),
|
||||
diagnosticCodes: [],
|
||||
pageCount: null,
|
||||
revision: 3,
|
||||
success: false,
|
||||
}),
|
||||
);
|
||||
const serialized = JSON.stringify(log.mock.calls);
|
||||
expect(serialized).not.toContain(privateText);
|
||||
expect(serialized).not.toContain(validSource.text);
|
||||
expect(serialized).not.toContain("resume-1");
|
||||
});
|
||||
|
||||
it("deactivates without compiling or deleting either source", async () => {
|
||||
const harness = createHarness();
|
||||
|
||||
const result = await harness.service.mutate({ ...commonMutationInput, transition: "deactivate" });
|
||||
|
||||
expect(result.stylesheet).toEqual({ ...previousStylesheet, mode: "legacy" });
|
||||
expect(harness.compile).not.toHaveBeenCalled();
|
||||
expect(harness.parity).not.toHaveBeenCalled();
|
||||
expect(harness.preflight).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not rewrite invalid stored resume data during deactivation", async () => {
|
||||
const initial = snapshot();
|
||||
initial.data.customSections = [
|
||||
{
|
||||
id: "custom-experience",
|
||||
type: "experience",
|
||||
title: "Experience",
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [{ id: "summary-item", hidden: false, content: "<p>Missing company</p>" }],
|
||||
} as never,
|
||||
];
|
||||
const harness = createHarness({ initial });
|
||||
|
||||
const error = await harness.service
|
||||
.mutate({ ...commonMutationInput, transition: "deactivate" })
|
||||
.catch((caught: unknown) => caught);
|
||||
|
||||
expect(error).toMatchObject({ code: "INTERNAL_SERVER_ERROR", status: 500 });
|
||||
expect(error).toHaveProperty("cause.issues.0.path", ["customSections", 0, "items", 0, "company"]);
|
||||
expect(harness.callOrder).not.toContain("update");
|
||||
expect(harness.publish).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("normalizes valid stored resume data during deactivation", async () => {
|
||||
const initial = snapshot();
|
||||
initial.data.customSections = [
|
||||
{
|
||||
id: "custom-experience",
|
||||
type: "experience",
|
||||
title: "Experience",
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
{
|
||||
id: "experience-item",
|
||||
hidden: false,
|
||||
company: "Analytical Engines",
|
||||
position: "Programmer",
|
||||
location: "London",
|
||||
period: "1842–1843",
|
||||
description: "<p>Wrote the first algorithm.</p>",
|
||||
content: "<p>Compatible overlap</p>",
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
];
|
||||
const harness = createHarness({ initial });
|
||||
|
||||
await harness.service.mutate({ ...commonMutationInput, transition: "deactivate" });
|
||||
|
||||
expect(harness.persisted().data.customSections[0]?.items[0]).toMatchObject({
|
||||
content: "<p>Compatible overlap</p>",
|
||||
roles: [],
|
||||
website: { url: "", label: "", inlineLink: false },
|
||||
});
|
||||
});
|
||||
|
||||
it("validates and preflights restored applied source independently from invalid editable source", async () => {
|
||||
const restoredApplied = { ...validSource, text: "@version 1;\nresume { color: #abcdef; }\n" };
|
||||
const compile = vi.fn((source: StylesheetSource) =>
|
||||
source === restoredApplied ? successfulCompile : failedCompile,
|
||||
);
|
||||
const harness = createHarness({ compile });
|
||||
|
||||
const result = await harness.service.mutate({
|
||||
...commonMutationInput,
|
||||
transition: "restore_history",
|
||||
restore: {
|
||||
mode: "semantic",
|
||||
source: invalidSource,
|
||||
applied: restoredApplied,
|
||||
},
|
||||
});
|
||||
|
||||
expect(compile).toHaveBeenCalledTimes(1);
|
||||
expect(compile).toHaveBeenCalledWith(restoredApplied);
|
||||
expect(harness.preflight).toHaveBeenCalledWith({
|
||||
data: expect.any(Object),
|
||||
stylesheet: restoredApplied,
|
||||
});
|
||||
expect(result.stylesheet).toEqual({
|
||||
mode: "semantic",
|
||||
source: invalidSource,
|
||||
applied: restoredApplied,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects locked resumes before compiler or preflight work", async () => {
|
||||
const initial = { ...snapshot(), isLocked: true };
|
||||
const harness = createHarness({ initial });
|
||||
|
||||
await expect(
|
||||
harness.service.mutate({ ...commonMutationInput, transition: "edit_source", source: validSource }),
|
||||
).rejects.toMatchObject({ code: "RESUME_LOCKED" });
|
||||
expect(harness.compile).not.toHaveBeenCalled();
|
||||
expect(harness.preflight).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns the locked canonical snapshot on a two-version conflict without writing", async () => {
|
||||
const locked = {
|
||||
...snapshot(),
|
||||
stylesheetRevision: 4,
|
||||
renderDataVersion: 9,
|
||||
};
|
||||
delete locked.data.metadata.stylesheet;
|
||||
const harness = createHarness({ locked });
|
||||
|
||||
const error = await harness.service
|
||||
.mutate({ ...commonMutationInput, transition: "edit_source", source: validSource })
|
||||
.catch((caught: unknown) => caught);
|
||||
|
||||
expect(error).toMatchObject({
|
||||
code: "STYLESHEET_REVISION_CONFLICT",
|
||||
data: {
|
||||
state: expect.objectContaining({
|
||||
revision: 4,
|
||||
renderDataVersion: 9,
|
||||
stylesheet: expect.objectContaining({
|
||||
mode: "legacy",
|
||||
source: { languageVersion: 1, text: "@version 1;\n" },
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
expect(harness.callOrder).not.toContain("update");
|
||||
expect(harness.publish).not.toHaveBeenCalled();
|
||||
expect(harness.callOrder.indexOf("rollback")).toBeLessThan(harness.callOrder.indexOf("convertLegacy"));
|
||||
});
|
||||
|
||||
it("reloads the exact invalid source, last-valid applied source, and untouched legacy rules", async () => {
|
||||
const harness = createHarness({ compile: () => failedCompile });
|
||||
await harness.service.mutate({
|
||||
...commonMutationInput,
|
||||
transition: "edit_source",
|
||||
source: invalidSource,
|
||||
});
|
||||
|
||||
const reloaded = await harness.service.getState({ id: "resume-1", userId: "user-1" });
|
||||
|
||||
expect(reloaded.stylesheet.source).toEqual(invalidSource);
|
||||
expect(reloaded.stylesheet.applied).toEqual(previousApplied);
|
||||
expect(harness.persisted().data.metadata.styleRules).toEqual(snapshot().data.metadata.styleRules);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user