test(e2e): scope imported table border geometry (#3476)

This commit is contained in:
Amruth Pillai
2026-09-05 20:30:37 -07:00
committed by GitHub
parent 578cb496aa
commit 66c25efe18
4 changed files with 102 additions and 7 deletions
@@ -0,0 +1,39 @@
# Imported-table raster CI fix
## Root cause
Hosted runs `34007560930` (PR #3471) and `34007788443` (PR #3472) failed only in
`tests/e2e/specs/imported-table.spec.ts` with `horizontal: 18` instead of Plan 16's
`horizontal: 17`; text and vertical checks passed.
The PDF operator dump from the failed hosted artifact showed 29 table path records matching the Plan 16 contract
(17 horizontal, 12 vertical), followed by an unrelated `constructPath` `endPath` bbox:
`[0, 19.65, 358.93, 20.65]`. Its stroke color was reported as `#cc00cc` only because the helper retained the
last table stroke color. It was a later red section-divider fill/no-paint path, not an extra table border. The old
helper classified every thin bbox after the last matching color state, so it counted this false positive.
The table's explicit width is stable at 300pt, while row height legitimately changes from 30pt to 31pt after the
`Beta!` edit. The helper therefore scopes candidate paths by the fixture's 300pt horizontal grid envelope, not by a
row-height tolerance. Missing or duplicated paths inside that envelope still change the exact 17/12 contract.
## Change
- Added `tests/e2e/fixtures/pdf-borders.ts` with deterministic `countTableBorderGeometry` filtering.
- Updated browser/server PDF inspection in `tests/e2e/specs/imported-table.spec.ts` to use the helper.
- Added `tests/e2e/fixtures/pdf-borders.test.ts`; regression proves old stale-color counting returns 2 horizontal
paths while topology-scoped counting returns 1.
## Verification
- Intent skill inventory: 7 packages, 26 skills; no matching local skill for this E2E/PDF helper.
- Focused helper regression: 1 file, 1 passed.
- Dedicated imported-table E2E: 2 consecutive runs, each 1 passed; both exercise initial, unrelated-edit, and table-edit
stages plus browser and server PDF exports.
- Production build: 3/3 tasks successful.
- Web typecheck via `rtk proxy pnpm --filter web typecheck`: passed (`tsgo --noEmit`).
- Turbo boundaries: 1,443 files across 20 packages, no issues.
- Targeted Biome: 3 files, no issues.
- `git diff --check`: passed.
The root `pnpm typecheck` wrapper was also tried but invokes an incompatible `tsc` path and reports TS5096 for
`allowImportingTsExtensions`; the package's documented `tsgo --noEmit` typecheck passes.
+20
View File
@@ -0,0 +1,20 @@
import type { BorderPath } from "./pdf-borders";
import { describe, expect, it } from "vitest";
import { countTableBorderGeometry } from "./pdf-borders";
describe("table border geometry", () => {
it("does not count a later path after stale magenta stroke state", () => {
const paths: BorderPath[] = [
{ color: "#cc00cc", bounds: [0, 0, 100, 1] },
{ color: "#cc00cc", bounds: [0, 19.65, 358.93, 20.65] },
];
const oldHorizontal = paths.filter(({ color, bounds: [x0, y0, x1, y1] }) => {
const width = Math.abs((x1 ?? 0) - (x0 ?? 0));
const height = Math.abs((y1 ?? 0) - (y0 ?? 0));
return color === "#cc00cc" && height > 0 && height <= 1.01 && width > height;
}).length;
expect(oldHorizontal).toBe(2);
expect(countTableBorderGeometry(paths)).toEqual({ horizontal: 1, vertical: 0 });
});
});
+35
View File
@@ -0,0 +1,35 @@
export type BorderPath = {
color: string;
bounds: readonly number[];
};
export type BorderGeometry = {
horizontal: number;
vertical: number;
};
// The imported-table fixture is explicitly 300pt wide. Row height can change when a cell is edited,
// so use width as stable topology while excluding unrelated paths that inherit the table's stroke color.
const TABLE_GRID_MAX_X = 300;
const isInsideTableWidth = (bounds: readonly number[]) => {
const [x0, y0, x1, y1] = bounds;
if (![x0, y0, x1, y1].every((value) => Number.isFinite(value))) return false;
return Math.min(x0 ?? 0, x1 ?? 0) >= 0 && Math.max(x0 ?? 0, x1 ?? 0) <= TABLE_GRID_MAX_X;
};
export function countTableBorderGeometry(paths: readonly BorderPath[]): BorderGeometry {
let horizontal = 0;
let vertical = 0;
for (const path of paths) {
if (path.color !== "#cc00cc" || !isInsideTableWidth(path.bounds)) continue;
const [x0, y0, x1, y1] = path.bounds;
const width = Math.abs((x1 ?? 0) - (x0 ?? 0));
const height = Math.abs((y1 ?? 0) - (y0 ?? 0));
if (height > 0 && height <= 1.01 && width > height) horizontal++;
if (width > 0 && width <= 1.01 && height > width) vertical++;
}
return { horizontal, vertical };
}
+8 -7
View File
@@ -3,6 +3,7 @@ import { readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { Pool } from "pg";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { countTableBorderGeometry } from "../fixtures/pdf-borders";
import { ACTIVE_PREVIEW_PAGE_SELECTOR } from "../fixtures/preview";
import { openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
@@ -59,17 +60,17 @@ async function inspectPdf(bytes: Uint8Array) {
const text = (await page.getTextContent()).items.flatMap((item: { str?: string }) => item.str ?? []);
const operators = await page.getOperatorList();
let stroke = "";
let horizontal = 0;
let vertical = 0;
const borderPaths = [];
for (const [index, fn] of operators.fnArray.entries()) {
if (fn === OPS.setStrokeRGBColor) stroke = operators.argsArray[index][0];
if (fn !== OPS.constructPath || stroke !== "#cc00cc") continue;
if (fn !== OPS.constructPath) continue;
const bounds = operators.argsArray[index][2] as ArrayLike<number>;
const width = Math.abs((bounds[2] ?? 0) - (bounds[0] ?? 0));
const height = Math.abs((bounds[3] ?? 0) - (bounds[1] ?? 0));
if (height > 0 && height <= 1.01 && width > height) horizontal++;
if (width > 0 && width <= 1.01 && height > width) vertical++;
borderPaths.push({
color: stroke,
bounds: [bounds[0] ?? 0, bounds[1] ?? 0, bounds[2] ?? 0, bounds[3] ?? 0],
});
}
const { horizontal, vertical } = countTableBorderGeometry(borderPaths);
return { text, horizontal, vertical };
} finally {
await loading.destroy();