Merge pull request #369 from pikasTech/fix/d601-panel-wiring-layout-guard-276
test: guard patch-panel wiring long table
This commit is contained in:
@@ -233,6 +233,8 @@ test("source/default smoke covers #276 two-column wiring long-table contract", (
|
||||
["res_boxsimu_1", "res_boxsimu_2", "DO1 对 DI1"]
|
||||
);
|
||||
assert.equal(check.evidence.includes("legacy 8-column table forbidden"), true);
|
||||
assert.equal(check.evidence.includes("trace/evidence header forbidden"), true);
|
||||
assert.equal(check.evidence.includes("状态/证据/trace kept in summary/detail"), true);
|
||||
});
|
||||
|
||||
test("source/default smoke covers #288 gate single-table contract", () => {
|
||||
@@ -748,8 +750,14 @@ test("layout smoke verifies desktop and mobile default workbench geometry withou
|
||||
assert.equal(desktopDefault.wiring.longTableOk, true);
|
||||
assert.equal(desktopDefault.wiring.noHorizontalScroll, true);
|
||||
assert.equal(desktopDefault.wiring.headers.length, 2);
|
||||
assert.deepEqual(desktopDefault.wiring.devicePair, ["res_boxsimu_1", "res_boxsimu_2"]);
|
||||
assert.equal(desktopDefault.wiring.ioPairs.some((row) => row.join("|") === "DO1|DI1"), true);
|
||||
assert.deepEqual(desktopDefault.wiring.legacyHeaderHits, []);
|
||||
assert.deepEqual(desktopDefault.wiring.metadataColumnHits, []);
|
||||
assert.equal(desktopDefault.wiring.metadataInSummary, true);
|
||||
assert.equal(desktopDefault.wiring.metadataInDetails, true);
|
||||
assert.equal(desktopDefault.wiring.horizontalScroll.panelScrollWidth <= desktopDefault.wiring.horizontalScroll.panelClientWidth + 2, true);
|
||||
assert.equal(desktopDefault.noHorizontalOverflow.right, true);
|
||||
assert.equal(Object.hasOwn(desktopDefault.boxes.shell, "text"), false);
|
||||
assert.equal(Object.hasOwn(desktopDefault.semanticOverlapChecks[0].boxes["#m3-control-form"], "text"), false);
|
||||
assert.equal(desktopDefault.failures.length, 0);
|
||||
@@ -761,6 +769,13 @@ test("layout smoke verifies desktop and mobile default workbench geometry withou
|
||||
assert.equal(mobileDefault.keyTargetsReachable, true);
|
||||
assert.equal(mobileDefault.wiring.longTableOk, true);
|
||||
assert.equal(mobileDefault.wiring.noHorizontalScroll, true);
|
||||
assert.deepEqual(mobileDefault.wiring.devicePair, ["res_boxsimu_1", "res_boxsimu_2"]);
|
||||
assert.equal(mobileDefault.wiring.ioPairs.some((row) => row.join("|") === "DO1|DI1"), true);
|
||||
assert.deepEqual(mobileDefault.wiring.legacyHeaderHits, []);
|
||||
assert.deepEqual(mobileDefault.wiring.metadataColumnHits, []);
|
||||
assert.equal(mobileDefault.wiring.metadataInSummary, true);
|
||||
assert.equal(mobileDefault.wiring.metadataInDetails, true);
|
||||
assert.equal(mobileDefault.noHorizontalOverflow.right, true);
|
||||
|
||||
const compact = compactLayoutSmokeCliOutput(report);
|
||||
assert.equal(compact.status, "pass");
|
||||
|
||||
@@ -166,7 +166,10 @@ const forbiddenLegacyWiringColumns = Object.freeze([
|
||||
"目标端口",
|
||||
"状态",
|
||||
"证据来源",
|
||||
"轨迹/证据"
|
||||
"轨迹/证据",
|
||||
"trace",
|
||||
"evidence",
|
||||
"trace/evidence"
|
||||
]);
|
||||
const requiredWiringSummaryTerms = Object.freeze(["hwlab-patch-panel", "状态", "证据来源", "轨迹/证据"]);
|
||||
|
||||
@@ -463,6 +466,8 @@ function runStaticSmoke() {
|
||||
"DO1 对 DI1",
|
||||
"data-wiring-layout=\"two-column-long-table\"",
|
||||
"legacy 8-column table forbidden",
|
||||
"trace/evidence header forbidden",
|
||||
"状态/证据/trace kept in summary/detail",
|
||||
...requiredWiringSummaryTerms
|
||||
]
|
||||
});
|
||||
@@ -1941,6 +1946,14 @@ function textHasForbiddenTerm(text, term) {
|
||||
return text.includes(term);
|
||||
}
|
||||
|
||||
function normalizeWiringHeaderText(text) {
|
||||
return String(text ?? "")
|
||||
.replace(/\s+/gu, " ")
|
||||
.replace(/\s*\/\s*/gu, "/")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function hasCompletePrimaryNavigation(html) {
|
||||
const navLabelsPresent =
|
||||
["工作台", "内部复核", "使用说明"].every((label) => html.includes(`>${label}<`)) &&
|
||||
@@ -2005,6 +2018,7 @@ function wiringTableContract(html, styles = "", app = "") {
|
||||
const headerCount = (wiringPanel.match(/<th\b/gu) ?? []).length;
|
||||
const bodyRow = wiringPanel.match(/<tr\b[^>]*\bdata-wiring-row=["']io["'][\s\S]*?<\/tr>/u)?.[0] ?? "";
|
||||
const bodyCellCount = (bodyRow.match(/<td\b/gu) ?? []).length;
|
||||
const headerTexts = [...wiringPanel.matchAll(/<th\b[^>]*>([\s\S]*?)<\/th>/gu)].map((match) => normalizeWiringHeaderText(visibleTextFromHtml(match[1])));
|
||||
const renderWiringBody = functionBody(app, "renderWiringList");
|
||||
return (
|
||||
/data-wiring-layout=["']two-column-long-table["']/u.test(wiringPanel) &&
|
||||
@@ -2014,7 +2028,8 @@ function wiringTableContract(html, styles = "", app = "") {
|
||||
requiredWiringLongTableHeaders.every((header) => wiringPanel.includes(header)) &&
|
||||
["DO1", "DI1"].every((port) => wiringPanel.includes(port)) &&
|
||||
requiredWiringSummaryTerms.every((term) => wiringPanel.includes(term)) &&
|
||||
forbiddenLegacyWiringColumns.every((column) => !wiringPanel.includes(`<th>${column}</th>`)) &&
|
||||
headerTexts.length === 2 &&
|
||||
forbiddenLegacyWiringColumns.every((column) => !headerTexts.includes(normalizeWiringHeaderText(column))) &&
|
||||
/id=["']wiring-summary["']/u.test(wiringPanel) &&
|
||||
/overflow-x:\s*hidden/u.test(styles.match(/\.wiring-table-wrap\s*\{[\s\S]*?\}/u)?.[0] ?? "") &&
|
||||
!/\.wiring-table\s*\{[\s\S]*?min-width:\s*9\d\dpx/u.test(styles) &&
|
||||
@@ -2983,14 +2998,22 @@ async function inspectLiveDom(url, options = {}) {
|
||||
document.querySelector("#wiring-target-device")?.textContent?.trim() ?? ""
|
||||
],
|
||||
wiringHeaderCount: document.querySelectorAll(".wiring-table thead th").length,
|
||||
legacyWiringHeaders: [...document.querySelectorAll(".wiring-table thead th")].map((cell) => cell.textContent?.trim() ?? "").filter((text) =>
|
||||
["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据"].includes(text)
|
||||
),
|
||||
legacyWiringHeaders: [...document.querySelectorAll(".wiring-table thead th")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "").filter((text) => {
|
||||
const normalized = text.replace(/\s*\/\s*/gu, "/").trim().toLowerCase();
|
||||
return ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据", "trace", "evidence", "trace/evidence"].includes(normalized);
|
||||
}),
|
||||
m3WiringRows: [...document.querySelectorAll("#wiring-body tr")].map((row) =>
|
||||
[...row.querySelectorAll("td")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "")
|
||||
),
|
||||
wiringBodyCellCounts: [...document.querySelectorAll("#wiring-body tr")].map((row) => row.querySelectorAll("td").length),
|
||||
wiringSummaryText: document.querySelector("#wiring-summary")?.textContent?.replace(/\s+/gu, " ").trim() ?? "",
|
||||
wiringMetadataColumnHits: [...document.querySelectorAll(".wiring-table thead th")].filter((cell) => /状态|证据来源|轨迹\/证据|\btrace\b|\bevidence\b/iu.test(cell.textContent?.replace(/\s+/gu, " ").trim() ?? "")).length,
|
||||
wiringMetadataInSummary: /状态/u.test(document.querySelector("#wiring-summary")?.textContent ?? "") &&
|
||||
/证据来源/u.test(document.querySelector("#wiring-summary")?.textContent ?? "") &&
|
||||
/轨迹\/证据/u.test(document.querySelector("#wiring-summary")?.textContent ?? ""),
|
||||
wiringMetadataInDetails: [...document.querySelectorAll(".wiring-detail")].some((detail) => /状态/u.test(detail.textContent ?? "")) &&
|
||||
[...document.querySelectorAll(".wiring-detail")].some((detail) => /证据来源/u.test(detail.textContent ?? "")) &&
|
||||
[...document.querySelectorAll(".wiring-detail")].some((detail) => /轨迹\/证据/u.test(detail.textContent ?? "")),
|
||||
coreControlsVisible: {
|
||||
commandInput: visible("#command-input"),
|
||||
commandSend: visible("#command-send"),
|
||||
@@ -3022,6 +3045,7 @@ async function inspectLiveDom(url, options = {}) {
|
||||
dom.wiringLayout === "two-column-long-table" &&
|
||||
dom.wiringHeaderCount === 2 &&
|
||||
dom.legacyWiringHeaders.length === 0 &&
|
||||
dom.wiringMetadataColumnHits === 0 &&
|
||||
requiredWiringLongTableHeaders.every((header) => dom.wiringHeaderDevices.includes(header)) &&
|
||||
dom.wiringBodyCellCounts.every((count) => count === 2) &&
|
||||
dom.m3WiringRows.some((row) =>
|
||||
@@ -3033,6 +3057,8 @@ async function inspectLiveDom(url, options = {}) {
|
||||
row[1].includes("轨迹/证据")
|
||||
) &&
|
||||
requiredWiringSummaryTerms.every((term) => dom.wiringSummaryText.includes(term)) &&
|
||||
dom.wiringMetadataInSummary &&
|
||||
dom.wiringMetadataInDetails &&
|
||||
(postGuard ? postGuard.attempts.length === 0 : true) &&
|
||||
Object.values(dom.coreControlsVisible).every(Boolean);
|
||||
return {
|
||||
@@ -3046,6 +3072,8 @@ async function inspectLiveDom(url, options = {}) {
|
||||
`gateRouteAvailable=${dom.gateRouteAvailable}`,
|
||||
`wiringLayout=${dom.wiringLayout}`,
|
||||
`wiringHeaderDevices=${dom.wiringHeaderDevices.join("|")}`,
|
||||
`legacyWiringHeaders=${dom.legacyWiringHeaders.join("|") || "none"}`,
|
||||
`wiringMetadataColumnHits=${dom.wiringMetadataColumnHits}`,
|
||||
`wiringBodyCellCounts=${dom.wiringBodyCellCounts.join("|")}`,
|
||||
`m3WiringRows=${dom.m3WiringRows.length}`,
|
||||
...(postGuard ? [`codeAgentPostAttempts=${postGuard.attempts.length}`] : [])
|
||||
@@ -5793,7 +5821,8 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
[".hardware-status", ".side-workspace", "hardware status vs side workspace"],
|
||||
["#command-form", ".right-sidebar", "Code Agent command form vs right sidebar"]
|
||||
];
|
||||
const wiringLegacyHeaders = ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据"];
|
||||
const wiringLegacyHeaders = ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据", "trace", "evidence", "trace/evidence"];
|
||||
const normalizeHeaderText = (text) => String(text ?? "").replace(/\s+/gu, " ").replace(/\s*\/\s*/gu, "/").trim().toLowerCase();
|
||||
const boxForElement = (element) => {
|
||||
if (!element) return null;
|
||||
const box = element.getBoundingClientRect();
|
||||
@@ -6104,10 +6133,18 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
const wiringPanel = document.querySelector("#panel-wiring");
|
||||
const wiringWrap = document.querySelector(".wiring-table-wrap");
|
||||
const wiringTable = document.querySelector(".wiring-long-table");
|
||||
const wiringHeaders = [...document.querySelectorAll(".wiring-table thead th")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "");
|
||||
const wiringHeaderCells = [...document.querySelectorAll(".wiring-table thead th")];
|
||||
const wiringHeaders = wiringHeaderCells.map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "");
|
||||
const wiringDevicePair = [
|
||||
document.querySelector("#wiring-source-device")?.textContent?.trim() ?? "",
|
||||
document.querySelector("#wiring-target-device")?.textContent?.trim() ?? ""
|
||||
];
|
||||
const wiringRows = [...document.querySelectorAll("#wiring-body tr")].map((row) =>
|
||||
[...row.querySelectorAll("td")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "")
|
||||
);
|
||||
const wiringIoPairs = [...document.querySelectorAll("#wiring-body tr")].map((row) =>
|
||||
[...row.querySelectorAll("td")].map((cell) => [...cell.querySelectorAll(".wiring-port")].map((port) => port.textContent?.trim() ?? "").join(" "))
|
||||
);
|
||||
const wiringCellGeometry = [...document.querySelectorAll(".wiring-long-table th, .wiring-long-table td, #wiring-summary")].map((element) => {
|
||||
const box = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
@@ -6127,7 +6164,16 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
const wiringTextNonOverlapping = wiringCellBoxes.every((box, index) =>
|
||||
wiringCellBoxes.every((other, otherIndex) => index === otherIndex || !boxesOverlap(box, other, -1))
|
||||
);
|
||||
const wiringLegacyHeaderHits = wiringHeaders.filter((header) => wiringLegacyHeaders.includes(header));
|
||||
const normalizedLegacyHeaders = wiringLegacyHeaders.map(normalizeHeaderText);
|
||||
const wiringLegacyHeaderHits = wiringHeaders.filter((header) => normalizedLegacyHeaders.includes(normalizeHeaderText(header)));
|
||||
const wiringMetadataColumnHits = wiringHeaderCells.filter((cell) => /状态|证据来源|轨迹\/证据|\btrace\b|\bevidence\b/iu.test(normalizeHeaderText(cell.textContent))).map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "");
|
||||
const wiringSummaryText = document.querySelector("#wiring-summary")?.textContent?.replace(/\s+/gu, " ").trim() ?? "";
|
||||
const wiringDetails = [...document.querySelectorAll(".wiring-detail")].map((detail) => detail.textContent?.replace(/\s+/gu, " ").trim() ?? "");
|
||||
const wiringMetadataInSummary = ["状态", "证据来源", "轨迹/证据"].every((term) => wiringSummaryText.includes(term));
|
||||
const wiringMetadataInDetails =
|
||||
wiringDetails.some((detail) => detail.includes("状态")) &&
|
||||
wiringDetails.some((detail) => detail.includes("证据来源")) &&
|
||||
wiringDetails.some((detail) => detail.includes("轨迹/证据"));
|
||||
const wiringHorizontalScroll = {
|
||||
panelClientWidth: wiringPanel?.clientWidth ?? 0,
|
||||
panelScrollWidth: wiringPanel?.scrollWidth ?? 0,
|
||||
@@ -6145,9 +6191,9 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
const wiringLongTableOk =
|
||||
wiringWrap?.getAttribute("data-wiring-layout") === "two-column-long-table" &&
|
||||
wiringHeaders.length === 2 &&
|
||||
wiringHeaders.includes("res_boxsimu_1 互连设备 A") &&
|
||||
wiringHeaders.includes("res_boxsimu_2 互连设备 B") &&
|
||||
JSON.stringify(wiringDevicePair) === JSON.stringify(["res_boxsimu_1", "res_boxsimu_2"]) &&
|
||||
wiringLegacyHeaderHits.length === 0 &&
|
||||
wiringMetadataColumnHits.length === 0 &&
|
||||
wiringRows.some((row) =>
|
||||
row.length === 2 &&
|
||||
row[0].includes("DO1") &&
|
||||
@@ -6155,7 +6201,10 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
row[1].includes("DI1") &&
|
||||
row[1].includes("证据来源") &&
|
||||
row[1].includes("轨迹/证据")
|
||||
);
|
||||
) &&
|
||||
wiringIoPairs.some((row) => JSON.stringify(row) === JSON.stringify(["DO1", "DI1"])) &&
|
||||
wiringMetadataInSummary &&
|
||||
wiringMetadataInDetails;
|
||||
if (!wiringLongTableOk) {
|
||||
failures.push({
|
||||
failureType: "blocked/skip",
|
||||
@@ -6164,8 +6213,15 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
summary: "#276 wiring panel is not the required two-column long table with device headers and DO1/DI1 row details.",
|
||||
wiring: {
|
||||
headers: wiringHeaders,
|
||||
devicePair: wiringDevicePair,
|
||||
legacyHeaderHits: wiringLegacyHeaderHits,
|
||||
metadataColumnHits: wiringMetadataColumnHits,
|
||||
metadataInSummary: wiringMetadataInSummary,
|
||||
metadataInDetails: wiringMetadataInDetails,
|
||||
rows: wiringRows,
|
||||
ioPairs: wiringIoPairs,
|
||||
summaryText: wiringSummaryText,
|
||||
details: wiringDetails,
|
||||
layout: wiringWrap?.getAttribute("data-wiring-layout") ?? null
|
||||
}
|
||||
});
|
||||
@@ -6307,8 +6363,15 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
textContained: wiringTextContained,
|
||||
textNonOverlapping: wiringTextNonOverlapping,
|
||||
headers: wiringHeaders,
|
||||
devicePair: wiringDevicePair,
|
||||
legacyHeaderHits: wiringLegacyHeaderHits,
|
||||
metadataColumnHits: wiringMetadataColumnHits,
|
||||
metadataInSummary: wiringMetadataInSummary,
|
||||
metadataInDetails: wiringMetadataInDetails,
|
||||
rows: wiringRows,
|
||||
ioPairs: wiringIoPairs,
|
||||
summaryText: wiringSummaryText,
|
||||
details: wiringDetails,
|
||||
horizontalScroll: wiringHorizontalScroll,
|
||||
cellGeometry: wiringCellGeometry
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user