test: guard resource explorer removal
This commit is contained in:
@@ -221,7 +221,10 @@ test("dedicated layout smoke CLI supports static build and DEV live shorthand",
|
||||
|
||||
test("source/default smoke covers #352 resource explorer removal contract", () => {
|
||||
const report = runDevCloudWorkbenchStaticSmoke();
|
||||
assert.equal(report.checks.find((check) => check.id === "feedback-352-resource-explorer-removed")?.status, "pass");
|
||||
const check = report.checks.find((item) => item.id === "feedback-352-resource-explorer-removed");
|
||||
assert.equal(check?.status, "pass");
|
||||
assert.equal(check.evidence.includes("aside#resource-explorer absent"), true);
|
||||
assert.equal(check.evidence.includes("#explorer-resize absent"), true);
|
||||
});
|
||||
|
||||
test("source/default smoke covers #276 two-column wiring long-table contract", () => {
|
||||
@@ -723,6 +726,7 @@ test("layout smoke verifies desktop and mobile default workbench geometry withou
|
||||
"layout-desktop-default",
|
||||
"layout-narrow-desktop-default",
|
||||
"layout-mobile-default",
|
||||
"layout-feedback-352-resource-explorer-removed",
|
||||
"layout-gate-desktop",
|
||||
"layout-gate-narrow-desktop",
|
||||
"layout-gate-mobile"
|
||||
@@ -742,10 +746,18 @@ test("layout smoke verifies desktop and mobile default workbench geometry withou
|
||||
assert.equal(report.safety.hitTestMethod.includes("elementsFromPoint"), true);
|
||||
assert.match(report.safety.statement, /does not send Code Agent chat, call M3 IO/u);
|
||||
assert.equal(report.checks.find((check) => check.id === "layout-issue-287-future-hardware-status-tabs")?.status, "pass");
|
||||
assert.equal(report.checks.find((check) => check.id === "layout-feedback-352-resource-explorer-removed")?.status, "pass");
|
||||
assert.equal(report.checks.find((check) => check.id === "layout-issue-288-future-single-table-gate")?.status, "skip");
|
||||
|
||||
const desktopDefault = report.checks.find((check) => check.id === "layout-desktop-default")?.observations;
|
||||
const resourceExplorerRemoval = report.checks.find((check) => check.id === "layout-feedback-352-resource-explorer-removed")?.observations;
|
||||
assert.equal(resourceExplorerRemoval.forbiddenSelectors.includes("#explorer-resize"), true);
|
||||
assert.equal(resourceExplorerRemoval.forbiddenCopy.includes("常用能力"), true);
|
||||
assert.equal(resourceExplorerRemoval.coverage.desktop.covered, true);
|
||||
assert.equal(resourceExplorerRemoval.coverage.mobile.removedVisibleCopyAbsent, true);
|
||||
assert.equal(desktopDefault.removedSelectorsAbsent, true);
|
||||
assert.equal(desktopDefault.removedVisibleCopyAbsent, true);
|
||||
assert.equal(desktopDefault.resourceExplorerRemovalGuard, true);
|
||||
assert.equal(desktopDefault.keyTargetsReachable, true);
|
||||
assert.equal(desktopDefault.wiring.longTableOk, true);
|
||||
assert.equal(desktopDefault.wiring.noHorizontalScroll, true);
|
||||
@@ -766,6 +778,8 @@ test("layout smoke verifies desktop and mobile default workbench geometry withou
|
||||
|
||||
const mobileDefault = report.checks.find((check) => check.id === "layout-mobile-default")?.observations;
|
||||
assert.equal(mobileDefault.removedSelectorsAbsent, true);
|
||||
assert.equal(mobileDefault.removedVisibleCopyAbsent, true);
|
||||
assert.equal(mobileDefault.resourceExplorerRemovalGuard, true);
|
||||
assert.equal(mobileDefault.keyTargetsReachable, true);
|
||||
assert.equal(mobileDefault.wiring.longTableOk, true);
|
||||
assert.equal(mobileDefault.wiring.noHorizontalScroll, true);
|
||||
@@ -808,5 +822,6 @@ test("repo-owned web checks expose source build and DEV live layout smoke gates"
|
||||
assert.equal(cloudWebPackage.scripts["layout:live"].includes("--live --url http://74.48.78.17:16666/"), true);
|
||||
assert.match(cloudWebCheckSource, /runDevCloudWorkbenchLayoutSmoke/u);
|
||||
assert.match(cloudWebCheckSource, /tmp\/dev-cloud-workbench-layout-web-check\.json/u);
|
||||
assert.match(cloudWebCheckSource, /layout-feedback-352-resource-explorer-removed/u);
|
||||
assert.match(cloudWebCheckSource, /compactLayoutSmokeFailures/u);
|
||||
});
|
||||
|
||||
@@ -157,6 +157,55 @@ const helpRouteAliases = Object.freeze(["/help"]);
|
||||
|
||||
const incompletePrimaryNavLabels = Object.freeze(["台", "证", "诊", "帮"]);
|
||||
|
||||
const removedResourceExplorerSourceTerms = Object.freeze([
|
||||
"resource-explorer",
|
||||
"explorer-toggle",
|
||||
"explorer-resize",
|
||||
"resource-tree",
|
||||
"explorer-capabilities",
|
||||
"capability-count",
|
||||
"tree-count",
|
||||
"data-focus-command",
|
||||
"quick-actions",
|
||||
"tree-panel",
|
||||
"explorer-collapsed",
|
||||
"--explorer-width",
|
||||
"--explorer-min-width",
|
||||
"--explorer-max-width"
|
||||
]);
|
||||
|
||||
const removedResourceExplorerDomSelectors = Object.freeze([
|
||||
"aside#resource-explorer",
|
||||
"#resource-explorer",
|
||||
"#explorer-toggle",
|
||||
"#explorer-resize",
|
||||
"#resource-tree",
|
||||
".resource-tree",
|
||||
"#explorer-capabilities",
|
||||
".explorer-capabilities",
|
||||
"#capability-count",
|
||||
"#tree-count",
|
||||
"[data-focus-command]",
|
||||
".quick-actions",
|
||||
".tree-panel",
|
||||
".explorer-collapsed"
|
||||
]);
|
||||
|
||||
const removedResourceExplorerCopyTerms = Object.freeze([
|
||||
"资源浏览与硬件资源树",
|
||||
"资源浏览",
|
||||
"硬件资源树",
|
||||
"只读工作区",
|
||||
"常用能力",
|
||||
"收起资源树",
|
||||
"展开资源树",
|
||||
"拖拽调整左侧资源树宽度",
|
||||
"资源查看",
|
||||
"查看接线",
|
||||
"查看状态",
|
||||
"编写任务"
|
||||
]);
|
||||
|
||||
const requiredWiringLongTableHeaders = Object.freeze(["res_boxsimu_1", "res_boxsimu_2"]);
|
||||
const forbiddenLegacyWiringColumns = Object.freeze([
|
||||
"源设备",
|
||||
@@ -507,7 +556,7 @@ function runStaticSmoke() {
|
||||
"aside#resource-explorer absent",
|
||||
"#explorer-resize absent",
|
||||
"resource tree / quick actions / capability cards absent",
|
||||
"right side keeps a 560-740px M3 status workspace boundary through --right-width"
|
||||
"right side keeps a 560-760px M3 status workspace boundary through --right-width"
|
||||
]
|
||||
});
|
||||
|
||||
@@ -1052,6 +1101,12 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) {
|
||||
summary: "Mobile 390x844 default layout has no resource drawer and preserves Agent input plus M3 tabs/controls.",
|
||||
observations: mobile.default
|
||||
},
|
||||
{
|
||||
id: "layout-feedback-352-resource-explorer-removed",
|
||||
status: allViewportCoverage(viewportResults, "resourceExplorerRemovalGuard") ? "pass" : "blocked",
|
||||
summary: "#352 default workbench has no aside#resource-explorer, #explorer-resize, resource tree/capability copy, or reclaimed left panel across desktop and mobile.",
|
||||
observations: summarizeResourceExplorerRemovalCoverage(viewportResults)
|
||||
},
|
||||
{
|
||||
id: "layout-right-sidebar-resize-desktop",
|
||||
status: desktop.rightResize.pass ? "pass" : "blocked",
|
||||
@@ -1177,7 +1232,7 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) {
|
||||
summary: useLiveUrl
|
||||
? "Live browser layout smoke verifies workbench layout, hit targets, overflow, /gate usability, and the two-column wiring panel only; it is not M3 hardware acceptance."
|
||||
: "Static local browser layout smoke verifies workbench layout, hit targets, overflow, /gate usability, and the two-column wiring panel only; deployment still requires DEV live verification.",
|
||||
refs: ["pikasTech/HWLAB#273", "pikasTech/HWLAB#276", "pikasTech/HWLAB#278", "pikasTech/HWLAB#287", "pikasTech/HWLAB#288", "pikasTech/HWLAB#99", "pikasTech/HWLAB#227"],
|
||||
refs: ["pikasTech/HWLAB#273", "pikasTech/HWLAB#276", "pikasTech/HWLAB#278", "pikasTech/HWLAB#287", "pikasTech/HWLAB#288", "pikasTech/HWLAB#352", "pikasTech/HWLAB#99", "pikasTech/HWLAB#227"],
|
||||
viewports: layoutViewports.map(({ id, width, height }) => ({ id, width, height })),
|
||||
checks,
|
||||
blockers,
|
||||
@@ -1351,7 +1406,7 @@ function layoutSourceContract() {
|
||||
"docs/reference/cloud-workbench.md",
|
||||
"docs/reference/code-agent-chat-readiness.md"
|
||||
],
|
||||
summary: "Cloud Workbench layout smoke protects the #99 workbench route, #227 visible M3 control area, Code Agent input, right hardware/trusted-record containers, /gate current route, and outer-scroll lock without claiming hardware acceptance."
|
||||
summary: "Cloud Workbench layout smoke protects the #99 workbench route, #227 visible M3 control area, #352 resource-explorer removal, Code Agent input, right hardware/trusted-record containers, /gate current route, and outer-scroll lock without claiming hardware acceptance."
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2157,34 +2212,8 @@ function hasMobileWorkbenchLayoutContract({ html, app, styles }) {
|
||||
|
||||
function hasResourceExplorerRemovalContract({ html, app, styles }) {
|
||||
const source = `${html}\n${app}\n${styles}`;
|
||||
const removedSelectorsAbsent = [
|
||||
"resource-explorer",
|
||||
"explorer-toggle",
|
||||
"explorer-resize",
|
||||
"resource-tree",
|
||||
"explorer-capabilities",
|
||||
"capability-count",
|
||||
"tree-count",
|
||||
"data-focus-command",
|
||||
"quick-actions",
|
||||
"tree-panel",
|
||||
"explorer-collapsed",
|
||||
"--explorer-width",
|
||||
"--explorer-min-width",
|
||||
"--explorer-max-width"
|
||||
].every((term) => !source.includes(term));
|
||||
const removedTextAbsent = [
|
||||
"资源浏览与硬件资源树",
|
||||
"只读工作区",
|
||||
"常用能力",
|
||||
"收起资源树",
|
||||
"展开资源树",
|
||||
"拖拽调整左侧资源树宽度",
|
||||
"资源查看",
|
||||
"查看接线",
|
||||
"查看状态",
|
||||
"编写任务"
|
||||
].every((term) => !source.includes(term));
|
||||
const removedSelectorsAbsent = removedResourceExplorerSourceTerms.every((term) => !source.includes(term));
|
||||
const removedTextAbsent = removedResourceExplorerCopyTerms.every((term) => !source.includes(term));
|
||||
return (
|
||||
removedSelectorsAbsent &&
|
||||
removedTextAbsent &&
|
||||
@@ -5778,7 +5807,7 @@ async function inspectRightSidebarResizeDisabled(page, { viewport }) {
|
||||
}
|
||||
|
||||
async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
return page.evaluate(async ({ mode, viewport, compareTo }) => {
|
||||
return page.evaluate(async ({ mode, viewport, compareTo, resourceExplorerGuard }) => {
|
||||
const failures = [];
|
||||
const skip = [];
|
||||
const selectorLabels = [
|
||||
@@ -5968,6 +5997,21 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
scrolls: element.scrollHeight > element.clientHeight ? element.scrollTop > before : true
|
||||
};
|
||||
};
|
||||
const visibleTextForElement = (root) => {
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||||
const parts = [];
|
||||
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
||||
const parent = node.parentElement;
|
||||
if (!parent || parent.closest("[hidden], script, style, noscript")) continue;
|
||||
const style = getComputedStyle(parent);
|
||||
if (style.display === "none" || style.visibility === "hidden") continue;
|
||||
const box = parent.getBoundingClientRect();
|
||||
if (box.width <= 0 || box.height <= 0) continue;
|
||||
const text = node.textContent?.replace(/\s+/gu, " ").trim();
|
||||
if (text) parts.push(text);
|
||||
}
|
||||
return parts.join(" ").replace(/\s+/gu, " ").trim();
|
||||
};
|
||||
|
||||
window.scrollTo(0, 240);
|
||||
document.documentElement.scrollTop = 240;
|
||||
@@ -6252,23 +6296,22 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
const removedSelectorStates = Object.fromEntries(
|
||||
[
|
||||
["#resource-explorer", "resourceExplorer"],
|
||||
["#explorer-resize", "explorerResize"],
|
||||
["#explorer-toggle", "explorerToggle"],
|
||||
["#resource-tree", "resourceTree"],
|
||||
[".quick-actions", "quickActions"]
|
||||
].map(([selector, key]) => [key, document.querySelector(selector) === null])
|
||||
resourceExplorerGuard.domSelectors.map((selector) => [selector, document.querySelector(selector) === null])
|
||||
);
|
||||
const removedSelectorsAbsent = Object.values(removedSelectorStates).every(Boolean);
|
||||
const visibleWorkbenchText = visibleTextForElement(document.body);
|
||||
const removedVisibleCopyHits = resourceExplorerGuard.copyTerms.filter((term) => visibleWorkbenchText.includes(term));
|
||||
const removedVisibleCopyAbsent = removedVisibleCopyHits.length === 0;
|
||||
const resourceExplorerRemovalGuard = removedSelectorsAbsent && removedVisibleCopyAbsent;
|
||||
const keyTargetsReachable = blockedKeyTargets.length === 0;
|
||||
if (!removedSelectorsAbsent) {
|
||||
if (!resourceExplorerRemovalGuard) {
|
||||
failures.push({
|
||||
failureType: "blocked/skip",
|
||||
selector: "#resource-explorer",
|
||||
selector: "#resource-explorer,#explorer-resize",
|
||||
viewport,
|
||||
summary: "Removed resource explorer selectors are still present in the default workbench DOM.",
|
||||
removedSelectorStates
|
||||
summary: "Removed resource explorer DOM or legacy copy returned in the default workbench.",
|
||||
removedSelectorStates,
|
||||
removedVisibleCopyHits
|
||||
});
|
||||
}
|
||||
if (!rootScrollLocked) {
|
||||
@@ -6324,7 +6367,7 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
Object.values(noHorizontalOverflow).every(Boolean) &&
|
||||
rightWidthTarget &&
|
||||
keyTargetsReachable &&
|
||||
removedSelectorsAbsent &&
|
||||
resourceExplorerRemovalGuard &&
|
||||
sidePanelOverflowUsable;
|
||||
const passWithWiring =
|
||||
pass &&
|
||||
@@ -6339,6 +6382,9 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
pass: passWithWiring,
|
||||
removedSelectorStates,
|
||||
removedSelectorsAbsent,
|
||||
removedVisibleCopyAbsent,
|
||||
removedVisibleCopyHits,
|
||||
resourceExplorerRemovalGuard,
|
||||
rootScrollLocked,
|
||||
rootAfterScrollAttempt,
|
||||
boxes,
|
||||
@@ -6383,7 +6429,15 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
|
||||
skip,
|
||||
failures
|
||||
};
|
||||
}, { mode, viewport, compareTo });
|
||||
}, {
|
||||
mode,
|
||||
viewport,
|
||||
compareTo,
|
||||
resourceExplorerGuard: {
|
||||
domSelectors: [...removedResourceExplorerDomSelectors],
|
||||
copyTerms: [...removedResourceExplorerCopyTerms]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function inspectGateLayoutState(page, { mode, viewport }) {
|
||||
@@ -6644,6 +6698,44 @@ function allViewportCoverage(viewportResults, fieldName) {
|
||||
);
|
||||
}
|
||||
|
||||
function summarizeResourceExplorerRemovalCoverage(viewportResults) {
|
||||
const coverageEntries = Object.entries(viewportResults).map(([id, result]) => {
|
||||
const observation = result.default;
|
||||
return [
|
||||
id,
|
||||
{
|
||||
viewport: observation?.viewport ?? null,
|
||||
covered: Boolean(observation?.resourceExplorerRemovalGuard),
|
||||
removedSelectorsAbsent: observation?.removedSelectorsAbsent === true,
|
||||
removedVisibleCopyAbsent: observation?.removedVisibleCopyAbsent === true,
|
||||
removedVisibleCopyHits: observation?.removedVisibleCopyHits ?? [],
|
||||
removedSelectorStates: observation?.removedSelectorStates ?? {}
|
||||
}
|
||||
];
|
||||
});
|
||||
const coverage = Object.fromEntries(coverageEntries);
|
||||
const failures = coverageEntries
|
||||
.filter(([, item]) => !item.covered)
|
||||
.map(([id, item]) => ({
|
||||
checkId: "layout-feedback-352-resource-explorer-removed",
|
||||
viewport: item.viewport,
|
||||
selector: "#resource-explorer,#explorer-resize",
|
||||
failureType: "blocked/skip",
|
||||
summary: `#352 resource explorer removal guard failed in ${id} viewport.`,
|
||||
removedSelectorsAbsent: item.removedSelectorsAbsent,
|
||||
removedVisibleCopyAbsent: item.removedVisibleCopyAbsent,
|
||||
removedVisibleCopyHits: item.removedVisibleCopyHits,
|
||||
removedSelectorStates: item.removedSelectorStates
|
||||
}));
|
||||
return {
|
||||
status: failures.length === 0 ? "pass" : "blocked",
|
||||
forbiddenSelectors: [...removedResourceExplorerDomSelectors],
|
||||
forbiddenCopy: [...removedResourceExplorerCopyTerms],
|
||||
coverage,
|
||||
failures
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeCoverageGap(viewportResults, issueKey) {
|
||||
const fieldName = {
|
||||
issue288: "issue288SingleTableReady",
|
||||
|
||||
@@ -140,6 +140,7 @@ const workbenchSmoke = runDevCloudWorkbenchStaticSmoke();
|
||||
const helpSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "help-md-contract");
|
||||
const outerScrollSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "outer-scroll-contract");
|
||||
const mobileLayoutSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "mobile-workbench-layout-contract");
|
||||
const resourceExplorerRemovalSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "feedback-352-resource-explorer-removed");
|
||||
const conversationUxSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-conversation-ux-states");
|
||||
const codeAgentTimeoutSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-long-timeout-contract");
|
||||
const codeAgentQuickPromptsSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-quick-prompts-contract");
|
||||
@@ -183,6 +184,7 @@ assert.equal(workbenchSmoke.devLive, false);
|
||||
assert.equal(helpSmokeCheck?.status, "pass", "PR #114 help Markdown route must be present and ready");
|
||||
assert.equal(outerScrollSmokeCheck?.status, "pass", "desktop workbench shell must lock outer page scrolling");
|
||||
assert.equal(mobileLayoutSmokeCheck?.status, "pass", "mobile 390x844 workbench layout contract must be present");
|
||||
assert.equal(resourceExplorerRemovalSmokeCheck?.status, "pass", "#352 resource explorer removal source contract must be present");
|
||||
assert.equal(conversationUxSmokeCheck?.status, "pass", "Code Agent conversation UX states must remain distinct");
|
||||
assert.equal(codeAgentTimeoutSmokeCheck?.status, "pass", "Code Agent chat must not reuse the 4500ms light API timeout");
|
||||
assert.equal(codeAgentQuickPromptsSmokeCheck?.status, "pass", "Code Agent quick prompts must remain fill-only with explicit send semantics for writes");
|
||||
@@ -239,6 +241,11 @@ assert.equal(
|
||||
"pass",
|
||||
"#276 wiring table must stay readable, non-overlapping, and horizontally contained"
|
||||
);
|
||||
assert.equal(
|
||||
layoutSmoke.checks.find((check) => check.id === "layout-feedback-352-resource-explorer-removed")?.status,
|
||||
"pass",
|
||||
"#352 resource explorer removal must remain covered by desktop and 390x844 layout smoke"
|
||||
);
|
||||
assert.equal(
|
||||
layoutSmoke.checks.find((check) => check.id === "layout-issue-288-future-single-table-gate")?.status,
|
||||
"skip",
|
||||
@@ -248,6 +255,7 @@ for (const layoutCheckId of [
|
||||
"layout-desktop-default",
|
||||
"layout-narrow-desktop-default",
|
||||
"layout-mobile-default",
|
||||
"layout-feedback-352-resource-explorer-removed",
|
||||
"layout-right-sidebar-resize-desktop",
|
||||
"layout-right-sidebar-resize-narrow-desktop",
|
||||
"layout-right-sidebar-resize-mobile",
|
||||
@@ -413,6 +421,8 @@ for (const removedSelectorTerm of [
|
||||
}
|
||||
for (const removedUiText of [
|
||||
"资源浏览与硬件资源树",
|
||||
"资源浏览",
|
||||
"硬件资源树",
|
||||
"只读工作区",
|
||||
"常用能力",
|
||||
"收起资源树",
|
||||
|
||||
Reference in New Issue
Block a user