Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817
This commit is contained in:
commit
ad8ad252a9
825 files changed
+124216
No files matched your search
@@ -0,0 +1,126 @@
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { authenticatedStatePath } from "../global-setup";
|
||||
import { expectAuthenticatedShell } from "./support/v2";
|
||||
|
||||
/**
|
||||
* V2-M6: visualizations, motion, matrices and dashboard.
|
||||
*
|
||||
* `ServerMap`/`ServiceTopology`/`ProjectSignature` and `DataTable` were already
|
||||
* implemented from the prior Codex session (see docs/v2/V2_ROADMAP.md's V2-M6
|
||||
* entry). This spec adds the missing real proofs for this milestone's specific
|
||||
* acceptance criteria: reduced-motion renders a *genuine final state* (not just
|
||||
* "axe is clean", which src/visualizations/*.test.tsx and the pre-existing
|
||||
* v2-routing-and-fidelity.spec.ts reduced-motion test already cover at the
|
||||
* component/axe level), a real matrix route has no page-level horizontal overflow,
|
||||
* and dashboard widget order/visibility actually round-trips through Settings → a
|
||||
* real reload of the dashboard route (not just localStorage keys, already proven at
|
||||
* the unit level in PreferencesContext.test.tsx).
|
||||
*/
|
||||
|
||||
test.use({ storageState: authenticatedStatePath });
|
||||
|
||||
test.describe("V2-M6 motion, matrices and dashboard personalization", () => {
|
||||
test("the server map renders its final, fully-opaque state under prefers-reduced-motion, not a mid-animation frame", async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("heading", { name: "Operationeel overzicht" })).toBeVisible();
|
||||
|
||||
const serverMap = page.locator(".server-map");
|
||||
await expect(serverMap).toBeVisible();
|
||||
const opacities = await serverMap.locator("svg > g, svg > path").evaluateAll((elements) =>
|
||||
elements.map((element) => window.getComputedStyle(element).opacity)
|
||||
);
|
||||
expect(opacities.length).toBeGreaterThan(0);
|
||||
for (const opacity of opacities) {
|
||||
expect(Number(opacity)).toBeCloseTo(1, 2);
|
||||
}
|
||||
});
|
||||
|
||||
test("a project's signature illustration renders its final, fully-opaque state under prefers-reduced-motion", async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
const projectName = `E2E-motion-project ${Date.now()}`;
|
||||
await page.goto("/projects");
|
||||
await expectAuthenticatedShell(page);
|
||||
await page.getByRole("button", { name: "Nieuw project" }).click();
|
||||
await page.getByLabel("Naam").fill(projectName);
|
||||
await page.getByRole("button", { name: "Project aanmaken" }).click();
|
||||
await expect(page.getByRole("heading", { name: projectName })).toBeVisible();
|
||||
|
||||
const signature = page.locator(".project-signature");
|
||||
// Below 900px the decorative signature is deliberately not rendered at all (OUX-6:
|
||||
// operational data comes before decoration on a narrow screen), so there is no
|
||||
// final animation state to assert there. Asserting it visible on every viewport
|
||||
// would be asserting the opposite of the product decision.
|
||||
const viewport = page.viewportSize();
|
||||
test.skip(
|
||||
(viewport?.width ?? 0) < 900,
|
||||
"The project signature is intentionally hidden below 900px; nothing animates there.",
|
||||
);
|
||||
await expect(signature).toBeVisible();
|
||||
// Only the ".project-core" diamond marker animates opacity (0 → 1 via `animate`);
|
||||
// the decorative rings around it use a permanent, layered per-ring `opacity`
|
||||
// *attribute* (e.g. 0.72, 0.64, ...) as a static design choice, not an
|
||||
// animate-in artifact — asserting rings end at opacity 1 would be a real, wrong
|
||||
// assumption about this component (confirmed by reading ProjectSignature.tsx,
|
||||
// not guessed after a confusing first failure).
|
||||
const coreOpacity = await signature.locator("svg .project-core").evaluate(
|
||||
(element) => window.getComputedStyle(element).opacity
|
||||
);
|
||||
expect(Number(coreOpacity)).toBeCloseTo(1, 2);
|
||||
|
||||
const accessibility = await new AxeBuilder({ page }).analyze();
|
||||
expect(accessibility.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("the storage matrix causes no page-level horizontal overflow at a realistic viewport", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto("/infrastructure/storage");
|
||||
await expect(page.getByRole("heading", { name: "Opslag en capaciteit" })).toBeVisible();
|
||||
const overflow = await page.evaluate(
|
||||
() => document.documentElement.scrollWidth - document.documentElement.clientWidth
|
||||
);
|
||||
expect(overflow).toBeLessThanOrEqual(1);
|
||||
|
||||
const accessibility = await new AxeBuilder({ page }).analyze();
|
||||
expect(accessibility.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("hiding and reordering a dashboard widget in Settings actually changes the real dashboard, and survives a reload", async ({ page }) => {
|
||||
await page.goto("/settings");
|
||||
await expectAuthenticatedShell(page);
|
||||
await expect(page.getByRole("heading", { name: "Dashboardwidgets" })).toBeVisible();
|
||||
|
||||
// Hide the "Projecten" widget entirely.
|
||||
await page.getByRole("checkbox", { name: "Projecten tonen" }).uncheck();
|
||||
// "Operationele signalen" is the default first widget since OUX-4 (critical
|
||||
// signals before the topology illustration), so its "up" control is correctly
|
||||
// disabled. Reorder in the direction that is actually available: move
|
||||
// "Unraid-overzicht" above it.
|
||||
await expect(page.getByRole("button", { name: "Operationele signalen omhoog" })).toBeDisabled();
|
||||
await page.getByRole("button", { name: "Unraid-overzicht omhoog" }).click();
|
||||
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("heading", { name: "Operationeel overzicht" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Projecten", exact: true })).toHaveCount(0);
|
||||
const headingOrder = await page.getByRole("main").getByRole("heading", { level: 2 }).allTextContents();
|
||||
expect(headingOrder.indexOf("Unraid-overzicht")).toBeLessThan(headingOrder.indexOf("Operationele signalen"));
|
||||
|
||||
// Reload the dashboard directly (not a fresh in-app navigation) to prove this is
|
||||
// real persisted state, not just in-memory React state surviving a client route
|
||||
// change.
|
||||
await page.reload();
|
||||
await expect(page.getByRole("heading", { name: "Operationeel overzicht" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Projecten", exact: true })).toHaveCount(0);
|
||||
const headingOrderAfterReload = await page.getByRole("main").getByRole("heading", { level: 2 }).allTextContents();
|
||||
expect(headingOrderAfterReload.indexOf("Unraid-overzicht")).toBeLessThan(
|
||||
headingOrderAfterReload.indexOf("Operationele signalen")
|
||||
);
|
||||
|
||||
// Restore the default so this test does not leak state into any other test that
|
||||
// shares the same authenticated storage state / owner account.
|
||||
await page.goto("/settings");
|
||||
await page.getByRole("checkbox", { name: "Projecten tonen" }).check();
|
||||
await page.getByRole("button", { name: "Unraid-overzicht omlaag" }).click();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user