B-221: keep header tape readable and commands inside the viewport
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
f3a1f1adc2
commit
4228d5cd62
@@ -3610,3 +3610,127 @@ test("header username stays untruncated on desktop and in the mobile menu", asyn
|
||||
|
||||
fs.writeFileSync(path.join(shotDir, "measurements.json"), `${JSON.stringify(measurements, null, 2)}\n`);
|
||||
});
|
||||
|
||||
function measureHeaderProbe() {
|
||||
const name = document.querySelector("#accountName");
|
||||
const group = document.querySelector("#headerCommandGroup");
|
||||
const strip = document.querySelector(".app-header .overview-strip");
|
||||
const stripBox = strip ? strip.getBoundingClientRect() : null;
|
||||
const stripShown = Boolean(stripBox && stripBox.width > 0 && stripBox.height > 0);
|
||||
const tapeClip = stripShown
|
||||
? [...strip.querySelectorAll("*")].filter((el) => (
|
||||
el.children.length === 0 && el.scrollWidth > el.clientWidth + 1
|
||||
)).map((el) => (el.textContent || "").trim()).filter(Boolean)
|
||||
: [];
|
||||
const groupBox = group ? group.getBoundingClientRect() : null;
|
||||
const nameBox = name ? name.getBoundingClientRect() : null;
|
||||
return {
|
||||
groupRight: groupBox ? groupBox.right : 0,
|
||||
nameRight: nameBox ? nameBox.right : 0,
|
||||
nameFits: name ? name.clientWidth >= name.scrollWidth : false,
|
||||
nameText: name ? String(name.textContent || "") : "",
|
||||
tapeClip,
|
||||
documentOverflow: document.documentElement.scrollWidth - window.innerWidth,
|
||||
viewport: window.innerWidth,
|
||||
};
|
||||
}
|
||||
|
||||
async function applyColorTheme(page, theme) {
|
||||
const wanted = theme === "night" ? "dark" : "light";
|
||||
await page.evaluate((value) => {
|
||||
document.documentElement.dataset.theme = value;
|
||||
document.documentElement.style.colorScheme = value;
|
||||
try { localStorage.setItem("xiaobaiTheme", value); } catch (_error) {}
|
||||
}, wanted);
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", wanted);
|
||||
}
|
||||
|
||||
async function probeHeaderWorkspaces(page, { account, theme, viewport, views, measurements }) {
|
||||
await page.setViewportSize(viewport);
|
||||
await applyColorTheme(page, theme);
|
||||
for (const view of views) {
|
||||
await page.locator(`[data-view="${view}"]`).first().click();
|
||||
await expect(page.locator(`#${view}`)).toHaveClass(/active-view/);
|
||||
const geometry = await page.evaluate(measureHeaderProbe);
|
||||
const label = `${account.label}-${view}-${viewport.width}-${theme}`;
|
||||
expect(geometry.nameText, `${label} username`).toBe(account.username);
|
||||
expect(geometry.nameFits, `${label} accountName truncated`).toBe(true);
|
||||
expect(geometry.groupRight, `${label} command group overflow ${geometry.groupRight}>${geometry.viewport}`).toBeLessThanOrEqual(geometry.viewport + 0.5);
|
||||
expect(geometry.nameRight, `${label} accountName overflow ${geometry.nameRight}>${geometry.viewport}`).toBeLessThanOrEqual(geometry.viewport + 0.5);
|
||||
expect(geometry.documentOverflow, `${label} document overflow`).toBeLessThanOrEqual(1);
|
||||
expect(geometry.tapeClip, `${label} tape clipped ${geometry.tapeClip.join("|")}`).toEqual([]);
|
||||
measurements.push({ account: account.username, view, theme, width: viewport.width, ...geometry });
|
||||
}
|
||||
}
|
||||
|
||||
test("desktop header keeps commands in view and tape text unclipped across workspaces", async ({ page }) => {
|
||||
test.setTimeout(120_000);
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const shotDir = path.join(__dirname, "../../runtime/b221-shots");
|
||||
fs.mkdirSync(shotDir, { recursive: true });
|
||||
const measurements = [];
|
||||
const views = [
|
||||
"sentimentCycleView",
|
||||
"auctionView",
|
||||
"themeLibraryView",
|
||||
"popularityView",
|
||||
"rotationView",
|
||||
"mentorView",
|
||||
"screenerView",
|
||||
"reviewWorkspaceView",
|
||||
"heavenView",
|
||||
];
|
||||
const viewports = [
|
||||
{ width: 1600, height: 1000 },
|
||||
{ width: 1440, height: 900 },
|
||||
{ width: 1280, height: 800 },
|
||||
];
|
||||
const accounts = [
|
||||
{ label: "admin", username: "review_admin", auth: namedSession("review_admin", "admin", true) },
|
||||
{ label: "user", username: "normal_user", auth: namedSession("normal_user", "user", false) },
|
||||
];
|
||||
|
||||
for (const account of accounts) {
|
||||
await mockApplication(page, account.auth);
|
||||
await page.goto("/index.html");
|
||||
await expect(page.locator("#accountName")).toHaveText(account.username);
|
||||
for (const viewport of viewports) {
|
||||
await probeHeaderWorkspaces(page, { account, theme: "day", viewport, views, measurements });
|
||||
await page.locator('[data-view="themeLibraryView"]').first().click();
|
||||
await page.locator(".app-header").screenshot({
|
||||
path: path.join(shotDir, `${account.label}-${viewport.width}-day.png`),
|
||||
});
|
||||
}
|
||||
await probeHeaderWorkspaces(page, {
|
||||
account,
|
||||
theme: "night",
|
||||
viewport: { width: 1600, height: 1000 },
|
||||
views,
|
||||
measurements,
|
||||
});
|
||||
await page.locator('[data-view="sentimentCycleView"]').first().click();
|
||||
await page.locator(".app-header").screenshot({
|
||||
path: path.join(shotDir, `${account.label}-1600-night.png`),
|
||||
});
|
||||
}
|
||||
|
||||
await mockApplication(page, accounts[0].auth);
|
||||
await page.goto("/index.html");
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await applyColorTheme(page, "day");
|
||||
await page.locator("#headerMenuButton").click();
|
||||
await expect(page.locator("#headerCommandGroup")).toBeVisible();
|
||||
const mobileDay = await page.evaluate(measureAccountName);
|
||||
expect(mobileDay.fits, "390 day account name truncated").toBe(true);
|
||||
expect(mobileDay.stacked, "390 day identity and account should stack").toBe(true);
|
||||
await page.screenshot({ path: path.join(shotDir, "admin-390-day.png") });
|
||||
await page.keyboard.press("Escape");
|
||||
await applyColorTheme(page, "night");
|
||||
await page.locator("#headerMenuButton").click();
|
||||
const mobileNight = await page.evaluate(measureAccountName);
|
||||
expect(mobileNight.fits, "390 night account name truncated").toBe(true);
|
||||
expect(mobileNight.stacked, "390 night identity and account should stack").toBe(true);
|
||||
await page.screenshot({ path: path.join(shotDir, "admin-390-night.png") });
|
||||
fs.writeFileSync(path.join(shotDir, "measurements.json"), `${JSON.stringify(measurements, null, 2)}\n`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user