refactor(stylesheet): move Semantic CSS to the browser (#3329)

This commit is contained in:
Amruth Pillai
2026-08-16 16:50:27 +02:00
committed by GitHub
parent f848e57436
commit 9509b5bc2e
203 changed files with 11838 additions and 14842 deletions
@@ -6,5 +6,5 @@ test("@semantic-css starts new resumes in semantic mode", async ({ authPage: pag
await expect(page.getByText("Converted stylesheet draft", { exact: true })).toHaveCount(0);
await expect.poll(() => readStylesheetSource(page)).toBe("@version 1;\n");
await expect(page.getByText("Applied", { exact: true }).filter({ visible: true })).toBeVisible();
await expect(page.getByText("Valid", { exact: true }).filter({ visible: true })).toBeVisible();
});
@@ -1,66 +0,0 @@
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { readSemanticCssFixture } from "../../fixtures/db";
import {
createSemanticCssResume,
downloadPdf,
replaceStylesheet,
seedSemanticCssResume,
waitForStablePreview,
waitForStylesheetStatus,
} from "../../fixtures/semantic-css";
import { expect, test } from "../../fixtures/test";
test.setTimeout(120_000);
const renderedPdfFingerprint = (pdf: Buffer) => {
const stablePdf = pdf
.toString("latin1")
.replace(/D:\d{14}Z/g, "D:00000000000000Z")
.replace(/[A-Z]{6}\+/g, "SUBSET+")
.replace(/\/ID \[<[\da-f]+> <[\da-f]+>\]/gi, "/ID [<ID> <ID>]");
return createHash("sha256").update(stablePdf, "latin1").digest("hex");
};
test("@semantic-css keeps preview and export on the last valid source", async ({ authPage: page }, testInfo) => {
const resumeId = await createSemanticCssResume(page, testInfo);
await seedSemanticCssResume(page, resumeId);
await replaceStylesheet(page, "@version 1;\nname { color: #dc2626; }\n");
await waitForStylesheetStatus(page, "Applied");
const canvas = await waitForStablePreview(page);
const validPreview = await canvas.evaluate((element) => (element as HTMLCanvasElement).toDataURL());
const validDownload = await downloadPdf(page);
const validPdf = await readFile(await validDownload.path());
expect(validPdf.subarray(0, 4).toString()).toBe("%PDF");
await replaceStylesheet(page, "@version 1;\nname { color: ; }\n");
await waitForStylesheetStatus(page, "Error");
await expect(page.getByText("Preview and export use the last valid version.", { exact: true })).toBeVisible();
await expect.poll(() => canvas.evaluate((element) => (element as HTMLCanvasElement).toDataURL())).toBe(validPreview);
const invalidDownload = await downloadPdf(page);
const invalidPdf = await readFile(await invalidDownload.path());
expect(invalidPdf.subarray(0, 4).toString()).toBe("%PDF");
expect(renderedPdfFingerprint(invalidPdf)).toBe(renderedPdfFingerprint(validPdf));
await expect
.poll(async () => (await readSemanticCssFixture(resumeId)).stylesheet)
.toMatchObject({
source: { text: "@version 1;\nname { color: ; }\n" },
applied: { text: "@version 1;\nname { color: #dc2626; }\n" },
});
await replaceStylesheet(page, "@version 1;\nname { color: #2563eb; }\n");
await waitForStylesheetStatus(page, "Applied");
await expect
.poll(() => canvas.evaluate((element) => (element as HTMLCanvasElement).toDataURL()))
.not.toBe(validPreview);
await waitForStablePreview(page);
const fixedDownload = await downloadPdf(page);
const fixedPdf = await readFile(await fixedDownload.path());
expect(fixedPdf.subarray(0, 4).toString()).toBe("%PDF");
expect(renderedPdfFingerprint(fixedPdf)).not.toBe(renderedPdfFingerprint(validPdf));
await expect
.poll(async () => (await readSemanticCssFixture(resumeId)).stylesheet?.applied.text)
.toBe("@version 1;\nname { color: #2563eb; }\n");
});
@@ -7,13 +7,6 @@ test.setTimeout(120_000);
test("@semantic-css converts legacy rules into an inactive draft before activation", async ({
authPage: page,
}, testInfo) => {
const preflightWorkerErrors: string[] = [];
page.on("console", (message) => {
if (message.location().url.includes("preflight.worker") && message.text().includes("Buffer is not defined")) {
preflightWorkerErrors.push(message.text());
}
});
const resumeId = await createSemanticCssResume(page, testInfo);
await updateSemanticCssFixture(resumeId, { legacyStyleRule: true });
await page.reload();
@@ -28,8 +21,7 @@ test("@semantic-css converts legacy rules into an inactive draft before activati
await expect(page.getByText(/^Ready to activate(?: with warnings)?$/)).toBeVisible({ timeout: 30_000 });
await activateStylesheet(page);
expect(preflightWorkerErrors).toEqual([]);
await page.reload();
await expect(page.getByText("Converted stylesheet draft", { exact: true })).toHaveCount(0);
await expect(page.getByText("Applied", { exact: true }).filter({ visible: true })).toBeVisible();
await expect(page.getByText("Valid", { exact: true }).filter({ visible: true })).toBeVisible();
});
@@ -1,4 +1,3 @@
import type { Page } from "@playwright/test";
import { readSemanticCssFixture } from "../../fixtures/db";
import {
createSemanticCssResume,
@@ -81,7 +80,6 @@ const WITHOUT_PAGINATION_DIRECTIVES = PORTABLE_ACCEPTANCE_STYLESHEET.replace(BRE
"",
);
const BREAK_INSIDE_ONLY_STYLESHEET = PORTABLE_ACCEPTANCE_STYLESHEET.replace(MIN_PRESENCE_AHEAD_DIRECTIVE, "");
const MIN_PRESENCE_AHEAD_ONLY_STYLESHEET = PORTABLE_ACCEPTANCE_STYLESHEET.replace(BREAK_INSIDE_DIRECTIVE, "");
const GENERIC_PORTABLE_MARKERS = [
"exactItemField",
@@ -126,10 +124,10 @@ async function countPortableMarkersByPage(page: Parameters<typeof waitForStableP
}, PORTABLE_MARKERS);
}
async function waitForPortableApplied(page: Parameters<typeof waitForStablePreview>[0]) {
async function waitForPortableValid(page: Parameters<typeof waitForStablePreview>[0]) {
await expect(
page
.getByText(/^Applied(?: with warnings)?$/)
.getByText(/^Valid(?: with warnings)?$/)
.filter({ visible: true })
.last(),
).toBeVisible({
@@ -145,23 +143,9 @@ async function applyPortableStylesheet(
await openSemanticCssEditor(page);
await replaceStylesheet(page, source);
await expect
.poll(async () => (await readSemanticCssFixture(resumeId)).stylesheet?.applied.text, { timeout: 30_000 })
.poll(async () => (await readSemanticCssFixture(resumeId)).stylesheet?.source.text, { timeout: 30_000 })
.toBe(source);
await waitForPortableApplied(page);
}
async function readProjectMinPresenceAhead(page: Page, resumeId: string) {
const fixture = await readSemanticCssFixture(resumeId);
const response = await page.request.get(
`/api/openapi/resumes/${encodeURIComponent(fixture.username)}/${encodeURIComponent(fixture.slug)}/style-projection`,
);
expect(response.ok(), `the public PDF projection request should succeed (${response.status()})`).toBe(true);
const projection = (await response.json()) as {
nodes: Record<string, { minPresenceAhead?: number }>;
};
const projectNodes = Object.entries(projection.nodes).filter(([key]) => key.endsWith("/section-projects"));
expect(projectNodes, "the public PDF projection should contain one projects section").toHaveLength(1);
return projectNodes[0]?.[1].minPresenceAhead;
await waitForPortableValid(page);
}
test("@semantic-css applies every portable selector behavior across Onyx, Azurill, and Ditto", async ({
@@ -176,7 +160,7 @@ test("@semantic-css applies every portable selector behavior across Onyx, Azuril
await applyPortableStylesheet(page, resumeId, PORTABLE_ACCEPTANCE_STYLESHEET);
await page.reload();
await openSemanticCssEditor(page);
await waitForPortableApplied(page);
await waitForPortableValid(page);
for (const template of ["Onyx", "Azurill", "Ditto"]) {
await switchTemplate(page, template);
@@ -201,9 +185,7 @@ test("@semantic-css applies every portable selector behavior across Onyx, Azuril
}
});
test("@semantic-css pagination directives keep a portable project section together", async ({
authPage: page,
}, testInfo) => {
test("@semantic-css break-inside keeps a portable project section together", async ({ authPage: page }, testInfo) => {
const resumeId = await createSemanticCssResume(page, testInfo);
await seedSemanticCssResume(page, resumeId, {
portableLayout: "pagination-stress",
@@ -220,30 +202,15 @@ test("@semantic-css pagination directives keep a portable project section togeth
await applyPortableStylesheet(page, resumeId, BREAK_INSIDE_ONLY_STYLESHEET);
const breakInsideOnlyDistribution = (await countPortableMarkersByPage(page)).map((page) => page.pagination);
await applyPortableStylesheet(page, resumeId, WITHOUT_PAGINATION_DIRECTIVES);
const withoutMinPresenceAhead = await readProjectMinPresenceAhead(page, resumeId);
await applyPortableStylesheet(page, resumeId, MIN_PRESENCE_AHEAD_ONLY_STYLESHEET);
const minPresenceAheadOnly = await readProjectMinPresenceAhead(page, resumeId);
console.info(
`PORTABLE_PAGINATION_EVIDENCE ${JSON.stringify({
breakInside: {
withoutDirective: withoutBreakInsideDistribution,
withDirective: breakInsideOnlyDistribution,
},
minPresenceAhead: {
withoutDirective: withoutMinPresenceAhead ?? null,
withDirective: minPresenceAheadOnly,
},
})}`,
);
expect(breakInsideOnlyDistribution, "break-inside: avoid must independently change project pagination").not.toEqual(
withoutBreakInsideDistribution,
);
expect(withoutMinPresenceAhead, "the no-directive control must omit minPresenceAhead").toBeUndefined();
expect(
minPresenceAheadOnly,
"-resume-min-presence-ahead: 24pt must independently reach the project pagination props",
).toBe(24);
});
@@ -9,7 +9,7 @@ test("@semantic-css preserves a persisted stylesheet through an old-client resum
await createSampleResumeFromDashboard(page, testInfo);
const resumeId = resumeIdFromPage(page);
const source = { languageVersion: 1, text: "@version 1;\nname { color: #dc2626; }\n" };
await updateSemanticCssFixture(resumeId, { stylesheet: { mode: "semantic", source, applied: source } });
await updateSemanticCssFixture(resumeId, { stylesheet: { mode: "semantic", source } });
await page.reload();
await openSidebarSection(page, "Basics");
@@ -21,7 +21,6 @@ test("@semantic-css preserves a persisted stylesheet through an old-client resum
stylesheet: {
mode: "semantic",
source,
applied: source,
},
});
});
@@ -1,26 +0,0 @@
import { updateSemanticCssFixture } from "../../fixtures/db";
import {
createSemanticCssResume,
readStylesheetSource,
replaceStylesheet,
seedSemanticCssResume,
waitForStylesheetStatus,
} from "../../fixtures/semantic-css";
import { expect, test } from "../../fixtures/test";
test.setTimeout(120_000);
test("@semantic-css rebases a stale revision without dropping the focused draft", async ({
authPage: page,
}, testInfo) => {
const resumeId = await createSemanticCssResume(page, testInfo);
await seedSemanticCssResume(page, resumeId);
await replaceStylesheet(page, "@version 1;\nname { color: #dc2626; }\n");
await waitForStylesheetStatus(page, "Applied");
await updateSemanticCssFixture(resumeId, { bumpRevision: true });
const latest = "@version 1;\nname { color: #2563eb; }\n";
await replaceStylesheet(page, latest);
await waitForStylesheetStatus(page, "Applied");
await expect.poll(() => readStylesheetSource(page)).toBe(latest);
});
@@ -94,7 +94,7 @@ test("@semantic-css renders deterministic first-page previews for all templates"
basicsName: "Semantic CSS Acceptance",
experienceItemId: "experience-item-2",
hidePicture: true,
stylesheet: { mode: "semantic", source: portable, applied: portable },
stylesheet: { mode: "semantic", source: portable },
});
for (const template of templates) {