195 lines
8.5 KiB
TypeScript
195 lines
8.5 KiB
TypeScript
// @ts-nocheck
|
|
import { test, expect } from "playwright/test";
|
|
|
|
test("catalog maker shell loads without page errors", async ({ page }) => {
|
|
const pageErrors = [];
|
|
page.on("pageerror", (error) => pageErrors.push(error.message));
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator("#settingsButton")).toBeVisible();
|
|
await expect(page.locator("#recordId")).toBeVisible();
|
|
await expect(page.locator("#variantHeader")).toBeVisible();
|
|
|
|
await page.screenshot({ path: "test-results/catalog-maker-shell.png", fullPage: true });
|
|
expect(pageErrors).toEqual([]);
|
|
});
|
|
|
|
test("settings modal opens and shows local database controls", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.locator("#settingsButton").click();
|
|
|
|
await expect(page.locator("#settingsModal")).toBeVisible();
|
|
await expect(page.locator("#localDbHost")).toBeVisible();
|
|
await expect(page.locator("#testLocalDbButton")).toBeVisible();
|
|
});
|
|
|
|
test("manufacturer picker shows ten results and supports search", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.locator("#manufacturerTrigger").click();
|
|
|
|
await expect(page.locator("#manufacturerOptions .manufacturer-option")).toHaveCount(10);
|
|
await expect(page.locator("#manufacturerSearch")).toBeVisible();
|
|
await page.locator("#manufacturerSearch").fill("La Sportiva");
|
|
await expect(page.locator("#manufacturerOptions .manufacturer-option")).toHaveText(["La Sportiva"]);
|
|
});
|
|
|
|
test("mapping uses cache on reload and reloads from DB on manual manufacturer change", async ({ page }) => {
|
|
let mappingRequests = 0;
|
|
await page.route("**/api/catalog-maker/mapping-sources**", async (route) => {
|
|
mappingRequests += 1;
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
mapped: true,
|
|
items: [{ key: "hudy", name: "Hudy", enabled: true }],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.addInitScript(() => {
|
|
localStorage.setItem("catalog-maker:selected-manufacturer", "13");
|
|
localStorage.setItem(
|
|
"catalog-maker:mapping-cache",
|
|
JSON.stringify({
|
|
13: {
|
|
mapped: true,
|
|
items: [{ key: "cached-hudy", name: "Cached Hudy", enabled: true }],
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
|
|
await page.goto("/");
|
|
await page.waitForFunction(() => document.querySelector("#manufacturerSelect")?.value === "13");
|
|
await expect(page.locator("#mappingStatusText")).toContainText("Mapping OK");
|
|
await page.waitForTimeout(300);
|
|
expect(mappingRequests).toBe(0);
|
|
|
|
await page.locator("#manufacturerSelect").evaluate((select) => {
|
|
const nextOption = [...select.options].find((option) => option.value && option.value !== "13");
|
|
select.value = nextOption?.value || "";
|
|
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
});
|
|
await expect.poll(() => mappingRequests).toBe(1);
|
|
|
|
await page.locator("#manufacturerSelect").evaluate((select) => {
|
|
select.value = "13";
|
|
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
});
|
|
await expect.poll(() => mappingRequests).toBe(2);
|
|
});
|
|
|
|
test("catalog maker remains usable on a narrow viewport", async ({ page }) => {
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
await page.goto("/");
|
|
|
|
await expect(page.locator("#sidebarDrawer")).toHaveAttribute("aria-hidden", "true");
|
|
await page.locator("#sidebarOpenButton").click();
|
|
await expect(page.locator("#findSourcesButton")).toBeVisible();
|
|
await expect(page.locator("#settingsButton")).toBeVisible();
|
|
await expect(page.locator("#sidebarOverlay")).toBeVisible();
|
|
await expect(page.locator(".secondary-panel")).toHaveCSS("order", "1");
|
|
await expect(page.locator(".main-workspace")).toHaveCSS("order", "2");
|
|
await page.screenshot({ path: "test-results/catalog-maker-mobile.png", fullPage: true });
|
|
});
|
|
|
|
test("left controls open in a responsive sidebar drawer", async ({ page }) => {
|
|
await page.goto("/");
|
|
await expect(page.locator("#sidebarDrawer")).toBeVisible();
|
|
await page.locator("#sidebarCloseButton").click();
|
|
await expect(page.locator("#sidebarDrawer")).toHaveAttribute("aria-hidden", "true");
|
|
await page.locator("#sidebarOpenButton").click();
|
|
await expect(page.locator("#sidebarDrawer")).toBeVisible();
|
|
await expect(page.locator("#sidebarOverlay")).toBeHidden();
|
|
});
|
|
|
|
test("workspace columns collapse and expand without leaving a gap", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.locator("#sidebarCloseButton").click();
|
|
await expect(page.locator(".catalog-screen")).toHaveClass(/sidebar-collapsed/);
|
|
const secondaryPanel = await page.locator(".secondary-panel").boundingBox();
|
|
expect(secondaryPanel?.x).toBeGreaterThanOrEqual(44);
|
|
|
|
await page.locator("#secondaryPanelToggle").click();
|
|
await expect(page.locator(".catalog-screen")).toHaveClass(/secondary-collapsed/);
|
|
await page.locator("#mainWorkspaceToggle").click();
|
|
await expect(page.locator(".catalog-screen")).toHaveClass(/workspace-collapsed/);
|
|
|
|
await page.locator("#secondaryPanelToggle").click();
|
|
await page.locator("#mainWorkspaceToggle").click();
|
|
await expect(page.locator(".catalog-screen")).not.toHaveClass(/secondary-collapsed/);
|
|
await expect(page.locator(".catalog-screen")).not.toHaveClass(/workspace-collapsed/);
|
|
});
|
|
|
|
test("main workspace keeps a bottom horizontal scrollbar for wide data", async ({ page }) => {
|
|
await page.setViewportSize({ width: 1061, height: 912 });
|
|
await page.goto("/");
|
|
|
|
const scrollState = await page.locator(".main-workspace .table-wrap").evaluate((element) => {
|
|
const style = getComputedStyle(element);
|
|
return {
|
|
overflowX: style.overflowX,
|
|
clientWidth: element.clientWidth,
|
|
scrollWidth: element.scrollWidth,
|
|
};
|
|
});
|
|
|
|
expect(scrollState.overflowX).toBe("auto");
|
|
expect(scrollState.scrollWidth).toBeGreaterThan(scrollState.clientWidth);
|
|
});
|
|
|
|
test("uses one shared height for the top panel headers", async ({ page }) => {
|
|
await page.goto("/");
|
|
const heights = await page.locator("#sidebarDrawer .sidebar-drawer-header, .secondary-panel .column-header, .main-workspace .column-header").evaluateAll((elements) => elements.map((element) => Math.round(element.getBoundingClientRect().height)));
|
|
expect(new Set(heights).size).toBe(1);
|
|
});
|
|
|
|
test("uses one shared visual style for the top panel headers", async ({ page }) => {
|
|
await page.goto("/");
|
|
const styles = await page.locator(".panel-header").evaluateAll((elements) => elements.map((element) => {
|
|
const style = getComputedStyle(element);
|
|
return [style.height, style.backgroundColor, style.borderBottomColor, style.fontSize, style.fontWeight, style.paddingLeft, style.paddingRight];
|
|
}));
|
|
expect(new Set(styles.map((style) => JSON.stringify(style))).size).toBe(1);
|
|
});
|
|
|
|
test("keeps the top panel headers on one baseline", async ({ page }) => {
|
|
await page.goto("/");
|
|
const positions = await page.locator(".panel-header").evaluateAll((elements) => elements.map((element) => {
|
|
const rect = element.getBoundingClientRect();
|
|
return [rect.top, rect.height];
|
|
}));
|
|
expect(new Set(positions.map((position) => JSON.stringify(position))).size).toBe(1);
|
|
expect(positions[0][0]).toBe(0);
|
|
});
|
|
|
|
test("uses one shared visual style for panel controls", async ({ page }) => {
|
|
await page.goto("/");
|
|
const styles = await page.locator("#sidebarCloseButton, #secondaryPanelToggle, #mainWorkspaceToggle").evaluateAll((elements) => elements.map((element) => {
|
|
const style = getComputedStyle(element);
|
|
return [style.width, style.height, style.borderColor, style.backgroundColor, style.borderRadius];
|
|
}));
|
|
expect(new Set(styles.map((style) => JSON.stringify(style))).size).toBe(1);
|
|
});
|
|
|
|
test("theme toggle switches and persists dark mode", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.evaluate(() => localStorage.clear());
|
|
await page.reload();
|
|
await expect(page.locator("html")).not.toHaveClass(/(?:^| )dark(?: |$)/);
|
|
const lightPanelBackground = await page.locator("#sidebarDrawer").evaluate((element) => getComputedStyle(element).backgroundColor);
|
|
|
|
await page.locator("#themeToggle").click();
|
|
await expect(page.locator("html")).toHaveClass(/(?:^| )dark(?: |$)/);
|
|
await expect(page.locator("#themeToggle")).toHaveText("Light theme");
|
|
const darkPanelBackground = await page.locator("#sidebarDrawer").evaluate((element) => getComputedStyle(element).backgroundColor);
|
|
expect(darkPanelBackground).not.toBe(lightPanelBackground);
|
|
|
|
await page.locator("#settingsButton").click();
|
|
await page.locator("#darkContrastSelect").selectOption("high");
|
|
await expect(page.locator("html")).toHaveClass(/contrast-dark-high/);
|
|
|
|
await page.reload();
|
|
await expect(page.locator("html")).toHaveClass(/(?:^| )dark(?: |$)/);
|
|
});
|