fix(ui): keep console layout usable at mid widths
This commit is contained in:
parent
0c75220adc
commit
8bb5052dd5
6 changed files with 98 additions and 14 deletions
|
|
@ -35,7 +35,7 @@ describe("React app dashboard state view", () => {
|
||||||
|
|
||||||
expect(html).toContain('data-ui-theme-toggle="true"');
|
expect(html).toContain('data-ui-theme-toggle="true"');
|
||||||
expect(html).toContain('data-ui-theme-current="dark"');
|
expect(html).toContain('data-ui-theme-current="dark"');
|
||||||
expect(html).toContain('aria-label="Switch to light theme"');
|
expect(html).toContain('aria-label="Light theme"');
|
||||||
expect(html).toContain('aria-pressed="false"');
|
expect(html).toContain('aria-pressed="false"');
|
||||||
expect(html).toContain("Theme");
|
expect(html).toContain("Theme");
|
||||||
expect(html).toContain("Dark");
|
expect(html).toContain("Dark");
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,10 @@ export interface ThemeToggleProps {
|
||||||
|
|
||||||
export function ThemeToggle({ theme, onThemeChange }: ThemeToggleProps) {
|
export function ThemeToggle({ theme, onThemeChange }: ThemeToggleProps) {
|
||||||
const nextTheme = getNextUiTheme(theme);
|
const nextTheme = getNextUiTheme(theme);
|
||||||
const label = `Switch to ${nextTheme} theme`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
aria-label={label}
|
aria-label="Light theme"
|
||||||
aria-pressed={theme === "light"}
|
aria-pressed={theme === "light"}
|
||||||
className="theme-toggle"
|
className="theme-toggle"
|
||||||
data-ui-theme-current={theme}
|
data-ui-theme-current={theme}
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ describe("dashboard UI components", () => {
|
||||||
|
|
||||||
expect(body).toContain('class="theme-toggle"');
|
expect(body).toContain('class="theme-toggle"');
|
||||||
expect(body).toContain('data-ui-theme-current="light"');
|
expect(body).toContain('data-ui-theme-current="light"');
|
||||||
expect(body).toContain('aria-label="Switch to dark theme"');
|
expect(body).toContain('aria-label="Light theme"');
|
||||||
expect(body).toContain('aria-pressed="true"');
|
expect(body).toContain('aria-pressed="true"');
|
||||||
expect(body).toContain('class="theme-toggle__label"');
|
expect(body).toContain('class="theme-toggle__label"');
|
||||||
expect(body).toContain('class="theme-toggle__switch"');
|
expect(body).toContain('class="theme-toggle__switch"');
|
||||||
|
|
|
||||||
|
|
@ -862,13 +862,34 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 780px) {
|
@media (max-width: 1180px), (max-height: 840px) {
|
||||||
.dashboard-frame {
|
.dashboard-frame {
|
||||||
|
grid-template-rows: auto auto auto auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-frame__panels {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1180px) {
|
||||||
|
.dashboard-frame__header {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-frame__panels {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-frame__panels .service-panel[data-layout="grid"] .panel__body {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 780px) {
|
||||||
.dashboard-frame__header,
|
.dashboard-frame__header,
|
||||||
.dashboard-frame__title,
|
.dashboard-frame__title,
|
||||||
.dashboard-header,
|
.dashboard-header,
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,61 @@ test.describe("dashboard page QA gate", () => {
|
||||||
expect(metrics.clippedItems).toEqual([]);
|
expect(metrics.clippedItems).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps intermediate viewports scrollable without horizontal clipping", async ({
|
||||||
|
page,
|
||||||
|
}, testInfo) => {
|
||||||
|
test.skip(testInfo.project.name !== "chromium-desktop");
|
||||||
|
|
||||||
|
for (const viewport of [
|
||||||
|
{ width: 900, height: 956 },
|
||||||
|
{ width: 1024, height: 768 },
|
||||||
|
]) {
|
||||||
|
await page.setViewportSize(viewport);
|
||||||
|
await page.goto("/");
|
||||||
|
await waitForDashboardReady(page);
|
||||||
|
|
||||||
|
const metrics = await page.evaluate(() => {
|
||||||
|
const footer = document.querySelector("[data-model-id='footer-status']");
|
||||||
|
const trackedElements = [
|
||||||
|
document.querySelector(".dashboard-frame__header"),
|
||||||
|
...document.querySelectorAll(".telemetry-card"),
|
||||||
|
...document.querySelectorAll(".service-panel"),
|
||||||
|
document.querySelector("[data-model-id='runtime-health']"),
|
||||||
|
footer,
|
||||||
|
].filter((element): element is Element => Boolean(element));
|
||||||
|
|
||||||
|
const footerRect = footer?.getBoundingClientRect();
|
||||||
|
const clippedRight = trackedElements
|
||||||
|
.map((element) => {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
id: element.getAttribute("data-model-id") || element.className,
|
||||||
|
right: rect.right,
|
||||||
|
width: rect.width,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item) => item.right > window.innerWidth + 1 || item.width <= 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
clippedRight,
|
||||||
|
footerBottomInDocument: (footerRect?.bottom ?? 0) + window.scrollY,
|
||||||
|
frameOverflow: window.getComputedStyle(
|
||||||
|
document.querySelector(".dashboard-frame") as Element,
|
||||||
|
).overflow,
|
||||||
|
scrollHeight: document.documentElement.scrollHeight,
|
||||||
|
scrollWidth: document.documentElement.scrollWidth,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(metrics.scrollWidth).toBeLessThanOrEqual(viewport.width);
|
||||||
|
expect(metrics.scrollHeight).toBeGreaterThanOrEqual(
|
||||||
|
Math.ceil(metrics.footerBottomInDocument),
|
||||||
|
);
|
||||||
|
expect(metrics.frameOverflow).not.toBe("hidden");
|
||||||
|
expect(metrics.clippedRight).toEqual([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("renders bookmark rows as a compact divider list", async ({ page }, testInfo) => {
|
test("renders bookmark rows as a compact divider list", async ({ page }, testInfo) => {
|
||||||
test.skip(testInfo.project.name !== "chromium-desktop");
|
test.skip(testInfo.project.name !== "chromium-desktop");
|
||||||
|
|
||||||
|
|
@ -210,10 +265,9 @@ test.describe("dashboard page QA gate", () => {
|
||||||
|
|
||||||
await expect(page.getByRole("main")).toHaveCount(1);
|
await expect(page.getByRole("main")).toHaveCount(1);
|
||||||
await page.keyboard.press("Tab");
|
await page.keyboard.press("Tab");
|
||||||
const themeToggle = page.getByRole("button", {
|
const themeToggle = page.getByRole("button", { name: "Light theme" });
|
||||||
name: "Switch to light theme",
|
|
||||||
});
|
|
||||||
await expect(themeToggle).toBeFocused();
|
await expect(themeToggle).toBeFocused();
|
||||||
|
await expect(themeToggle).toHaveAttribute("aria-pressed", "false");
|
||||||
const themeFocusBoxShadow = await themeToggle.evaluate((element) => {
|
const themeFocusBoxShadow = await themeToggle.evaluate((element) => {
|
||||||
return window.getComputedStyle(element).boxShadow;
|
return window.getComputedStyle(element).boxShadow;
|
||||||
});
|
});
|
||||||
|
|
@ -247,8 +301,12 @@ test.describe("dashboard page QA gate", () => {
|
||||||
|
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
await waitForDashboardReady(page);
|
await waitForDashboardReady(page);
|
||||||
await page.getByRole("button", { name: "Switch to light theme" }).click();
|
await page.getByRole("button", { name: "Light theme" }).click();
|
||||||
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
|
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
|
||||||
|
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
|
||||||
|
"aria-pressed",
|
||||||
|
"true",
|
||||||
|
);
|
||||||
|
|
||||||
const results = await new AxeBuilder({ page }).analyze();
|
const results = await new AxeBuilder({ page }).analyze();
|
||||||
expect(results.violations).toEqual([]);
|
expect(results.violations).toEqual([]);
|
||||||
|
|
@ -291,15 +349,17 @@ test.describe("dashboard page QA gate", () => {
|
||||||
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "dark");
|
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "dark");
|
||||||
|
|
||||||
const switchToLight = page.getByRole("button", {
|
const switchToLight = page.getByRole("button", {
|
||||||
name: "Switch to light theme",
|
name: "Light theme",
|
||||||
});
|
});
|
||||||
await expect(switchToLight).toBeVisible();
|
await expect(switchToLight).toBeVisible();
|
||||||
|
await expect(switchToLight).toHaveAttribute("aria-pressed", "false");
|
||||||
await switchToLight.click();
|
await switchToLight.click();
|
||||||
|
|
||||||
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
|
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
|
||||||
await expect(
|
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
|
||||||
page.getByRole("button", { name: "Switch to dark theme" }),
|
"aria-pressed",
|
||||||
).toBeVisible();
|
"true",
|
||||||
|
);
|
||||||
|
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await waitForDashboardReady(page);
|
await waitForDashboardReady(page);
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,12 @@ test.describe("Storybook theme QA", () => {
|
||||||
|
|
||||||
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
|
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole("button", { name: "Switch to dark theme" }),
|
page.getByRole("button", { name: "Light theme" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
|
||||||
|
"aria-pressed",
|
||||||
|
"true",
|
||||||
|
);
|
||||||
await expect(page.locator("body")).toHaveCSS(
|
await expect(page.locator("body")).toHaveCSS(
|
||||||
"background-color",
|
"background-color",
|
||||||
"rgb(238, 242, 231)",
|
"rgb(238, 242, 231)",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue