49 lines
3.6 KiB
JavaScript
49 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const p6 = path.join(root, "docs/p6");
|
|
const artifact = path.join(p6, "view-interaction-chrome.json");
|
|
const chrome = JSON.parse(fs.readFileSync(artifact, "utf8"));
|
|
const cases = Object.fromEntries((chrome.cases ?? []).map((item) => [item.id, item]));
|
|
const required = ["view-ready", "view-selection", "view-section", "view-hidden", "view-desktop", "view-mobile"];
|
|
const fileHash = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
const pngInfo = (file) => { const bytes = fs.readFileSync(file); return { bytes: bytes.length, width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20), sha256: fileHash(file) }; };
|
|
const desktop = path.join(p6, "view-interaction-desktop.png");
|
|
const mobile = path.join(p6, "view-interaction-mobile.png");
|
|
const result = chrome.result === "pass"
|
|
&& required.every((id) => cases[id]?.status === "pass")
|
|
&& cases["view-ready"]?.state?.view?.objectCount === 1
|
|
&& cases["view-ready"]?.state?.canvasPixels > 0
|
|
&& cases["view-selection"]?.state?.view?.selected === true
|
|
&& cases["view-section"]?.state?.view?.section === true
|
|
&& cases["view-hidden"]?.state?.view?.visible === false
|
|
&& cases["view-desktop"]?.state?.view?.visible === true
|
|
&& cases["view-desktop"]?.state?.view?.camera?.position?.length === 3
|
|
&& cases["view-mobile"]?.state?.controls?.includes("section-button")
|
|
&& fs.existsSync(desktop) && fs.existsSync(mobile)
|
|
&& pngInfo(desktop).bytes > 1000 && pngInfo(mobile).bytes > 1000
|
|
&& (chrome.cases ?? []).flatMap((item) => item.externalRequests ?? []).length === 0
|
|
&& (chrome.cases ?? []).flatMap((item) => item.exceptions ?? []).length === 0;
|
|
const report = {
|
|
schema: "freecad-web-p6-view-interaction-gate/v1",
|
|
taskId: "FW-P6-FPA-H",
|
|
result: result ? "pass" : "fail",
|
|
cases: required.map((id) => ({ id, status: cases[id]?.status ?? "missing", stage: cases[id]?.state?.stage ?? null, view: cases[id]?.state?.view ?? null })),
|
|
screenshots: { desktop: pngInfo(desktop), mobile: pngInfo(mobile) },
|
|
browser: { version: chrome.chrome?.version ?? null, mode: chrome.mode ?? null, viewport: chrome.viewport ?? null },
|
|
artifacts: { chrome: { path: "docs/p6/view-interaction-chrome.json", sha256: fileHash(artifact) }, source: { path: "web/src/main.ts", sha256: fileHash(path.join(root, "web/src/main.ts")) }, renderer: { path: "web/src/render-object-registry.ts", sha256: fileHash(path.join(root, "web/src/render-object-registry.ts")) } },
|
|
boundaries: [
|
|
"View state changes only Three.js projection and persisted view-provider state; geometry remains a native FreeCAD Worker mesh payload.",
|
|
"The compact browser view uses one native STEP object as the representative Document actor; full multi-object projection remains governed by object/graph family cards.",
|
|
"Section is an X-mid-plane clipping projection and is intentionally reversible; it does not mutate the native document geometry.",
|
|
],
|
|
};
|
|
fs.writeFileSync(path.join(p6, "view-interaction-gate.json"), `${JSON.stringify(report, null, 2)}\n`);
|
|
fs.writeFileSync(path.join(root, "docs/freecad-p6-view-interaction-gate.log"), `${JSON.stringify({ result: report.result, cases: required.length, desktop: report.screenshots.desktop, mobile: report.screenshots.mobile, externalRequests: 0, exceptions: 0 })}\n`);
|
|
console.log(JSON.stringify({ result: report.result, cases: required.length, desktop: report.screenshots.desktop, mobile: report.screenshots.mobile }));
|
|
if (!result) process.exitCode = 1;
|