Add full Blender desktop parity validation workflow
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const viewports = [
|
||||
@@ -84,4 +85,59 @@ test.describe("M7-14 responsive layout", () => {
|
||||
expect(geometry.timeline.height).toBeLessThanOrEqual(120);
|
||||
expect(geometry.propertyTabsDirection).toBe("column");
|
||||
});
|
||||
|
||||
test("desktop cycles every Blender 5.2 workspace with its source editor set", async ({ page }) => {
|
||||
const expected = {
|
||||
Layout: { mode: "Object Mode", menus: "View Select Add Object", editors: ["3D Viewport", "Outliner", "Properties", "Timeline"] },
|
||||
Modeling: { mode: "Edit Mode", menus: "View Select Add Mesh Vertex Edge Face UV", editors: ["3D Viewport", "Outliner", "Properties"] },
|
||||
Sculpting: { mode: "Sculpt Mode", menus: "Sculpt Mask Face Sets View", editors: ["3D Viewport", "Outliner", "Properties"] },
|
||||
"UV Editing": { mode: "Edit Mode", menus: "View Select Image UV", editors: ["3D Viewport", "Image Editor", "Outliner", "Properties"] },
|
||||
"Texture Paint": { mode: "Texture Paint Mode", menus: "View Paint Mask Face Selection", editors: ["3D Viewport", "Image Editor", "Outliner", "Properties"] },
|
||||
Shading: { mode: "Object Mode", menus: "View Select Add Node", editors: ["3D Viewport", "File Browser", "Image Editor", "Outliner", "Properties", "Shader Editor"] },
|
||||
Animation: { mode: "Object Mode", menus: "View Select Marker Channel Key", editors: ["3D Viewport", "3D Viewport", "Dope Sheet", "Outliner", "Properties"] },
|
||||
Rendering: { mode: "Object Mode", menus: "View Image", editors: ["Properties", "Render Result", "Timeline"] },
|
||||
Compositing: { mode: "Object Mode", menus: "View Select Add Node", editors: ["Compositor", "Image Editor", "Properties", "Timeline"] },
|
||||
"Geometry Nodes": { mode: "Object Mode", menus: "View Select Add Node", editors: ["3D Viewport", "Geometry Nodes", "Outliner", "Properties", "Spreadsheet", "Timeline"] },
|
||||
Scripting: { mode: "Object Mode", menus: "View Text Templates", editors: ["3D Viewport", "Info", "Outliner", "Outliner", "Properties", "Python Console", "Text Editor"] },
|
||||
} as const;
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("engine-status")).toContainText("ready, open a .blend file", { timeout: 20_000 });
|
||||
|
||||
for (const [workspace, contract] of Object.entries(expected)) {
|
||||
await page.getByRole("button", { name: workspace, exact: true }).click();
|
||||
await expect(page.locator("main.blender-app")).toHaveAttribute("data-workspace", workspace);
|
||||
await expect(page.locator(".mode-chip")).toHaveText(contract.mode);
|
||||
await expect(page.locator(".workspace-editor-menus")).toHaveText(contract.menus);
|
||||
const report = await page.evaluate(() => {
|
||||
const visibleAreas = [...document.querySelectorAll<HTMLElement>(".workspace-grid > .area-frame")].filter((area) => {
|
||||
const bounds = area.getBoundingClientRect();
|
||||
return getComputedStyle(area).display !== "none" && bounds.width > 0 && bounds.height > 0;
|
||||
});
|
||||
const overlaps: string[] = [];
|
||||
for (let leftIndex = 0; leftIndex < visibleAreas.length; leftIndex += 1) {
|
||||
for (let rightIndex = leftIndex + 1; rightIndex < visibleAreas.length; rightIndex += 1) {
|
||||
const left = visibleAreas[leftIndex].getBoundingClientRect();
|
||||
const right = visibleAreas[rightIndex].getBoundingClientRect();
|
||||
if (Math.min(left.right, right.right) - Math.max(left.left, right.left) > 1 && Math.min(left.bottom, right.bottom) - Math.max(left.top, right.top) > 1) {
|
||||
overlaps.push(`${visibleAreas[leftIndex].dataset.editor}/${visibleAreas[rightIndex].dataset.editor}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
editors: visibleAreas.map((area) => area.dataset.editor).sort(),
|
||||
overlaps,
|
||||
rootScrollWidth: document.documentElement.scrollWidth,
|
||||
viewportWidth: window.innerWidth,
|
||||
};
|
||||
});
|
||||
expect(report.editors, workspace).toEqual([...contract.editors].sort());
|
||||
expect(report.overlaps, workspace).toEqual([]);
|
||||
expect(report.rootScrollWidth, workspace).toBeLessThanOrEqual(report.viewportWidth);
|
||||
if (await page.locator(".workspace-editor-surface").count() > 0) {
|
||||
const accessibility = await new AxeBuilder({ page }).include(".workspace-editor-surface").withTags(["wcag2aa", "wcag21aa", "wcag22aa"]).analyze();
|
||||
expect(accessibility.violations.filter((violation) => violation.impact === "critical" || violation.impact === "serious").map((violation) => violation.id), workspace).toEqual([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,29 @@ const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
|
||||
});
|
||||
assert.deepEqual(transpiled.diagnostics, []);
|
||||
const moduleUrl = "data:text/javascript;base64," + Buffer.from(transpiled.outputText).toString("base64");
|
||||
const { createDefaultWebWorkspaceState, reduceUICommand } = await import(moduleUrl);
|
||||
const { BLENDER_WORKSPACE_IDS, createDefaultWebWorkspaceState, reduceUICommand } = await import(moduleUrl);
|
||||
|
||||
test("Blender 5.2 workspaces retain their source editor area sets", () => {
|
||||
const state = createDefaultWebWorkspaceState();
|
||||
const expected = {
|
||||
Layout: ["3D Viewport", "Outliner", "Properties", "Timeline"],
|
||||
Modeling: ["3D Viewport", "Outliner", "Properties"],
|
||||
Sculpting: ["3D Viewport", "Outliner", "Properties"],
|
||||
"UV Editing": ["3D Viewport", "Image Editor", "Outliner", "Properties"],
|
||||
"Texture Paint": ["3D Viewport", "Image Editor", "Outliner", "Properties"],
|
||||
Shading: ["3D Viewport", "File Browser", "Image Editor", "Outliner", "Properties", "Shader Editor"],
|
||||
Animation: ["3D Viewport", "3D Viewport", "Dope Sheet", "Outliner", "Properties"],
|
||||
Rendering: ["Properties", "Render Result", "Timeline"],
|
||||
Compositing: ["Compositor", "Image Editor", "Properties", "Timeline"],
|
||||
"Geometry Nodes": ["3D Viewport", "Geometry Nodes", "Outliner", "Properties", "Spreadsheet", "Timeline"],
|
||||
Scripting: ["3D Viewport", "Info", "Outliner", "Outliner", "Properties", "Python Console", "Text Editor"],
|
||||
};
|
||||
assert.deepEqual(Object.keys(state.workspaces), BLENDER_WORKSPACE_IDS);
|
||||
for (const id of BLENDER_WORKSPACE_IDS) {
|
||||
assert.deepEqual(state.workspaces[id].areas.map((area) => area.editor).sort(), expected[id].toSorted(), id);
|
||||
assert.ok(state.workspaces[id].areas.every((area) => area.rect.width > 0 && area.rect.height > 0), id);
|
||||
}
|
||||
});
|
||||
|
||||
test("M7-13 menu and modal overlays are mutually exclusive and Escape-closeable", () => {
|
||||
let state = createDefaultWebWorkspaceState();
|
||||
|
||||
Reference in New Issue
Block a user