모바일 톱바 조작면 보강

This commit is contained in:
Yun Chan 2026-08-12 14:34:00 +09:00
parent 2605661f0c
commit 9687f18669
3 changed files with 122 additions and 8 deletions

View file

@ -119,20 +119,105 @@ test.describe("full sweep — shared shell, GNB, and routing guards", () => {
// checklist: shell-appshell-role-theming
// 역할별 셸 진입 시 body[data-role]이 learner/instructor/admin으로 설정된다.
test("applies body[data-role] theming for each role shell", async ({ page }) => {
await signInAsLearner(page);
let currentUser = meResponse();
await page.route("**/api/auth/me", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(currentUser),
});
});
await page.goto("/learn");
await expect(page.locator(".lh-root")).toBeVisible({ timeout: 15_000 });
await expect(page.locator("body")).toHaveAttribute("data-role", "learner");
await signInAsTeacher(page);
currentUser = meResponse({
role: "teacher",
email: "shell.teacher@hs.ac.kr",
display_name: "Shell Teacher",
});
await page.goto("/teach");
await expect(page.locator(".vg-topbar")).toBeVisible({ timeout: 15_000 });
await expect(page.locator("body")).toHaveAttribute("data-role", "instructor");
await signInAsAdmin(page);
currentUser = meResponse({
role: "admin",
email: "shell.admin@twentyoz.kr",
display_name: "Shell Admin",
admin_access: true,
super_admin: true,
});
await page.goto("/admin");
await expect(page.locator(".vg-topbar")).toBeVisible({ timeout: 15_000 });
await expect(page.locator("body")).toHaveAttribute("data-role", "admin");
for (const viewport of [
{ width: 390, height: 844 },
{ width: 320, height: 568 },
]) {
await page.setViewportSize(viewport);
await page.evaluate(() => new Promise(requestAnimationFrame));
const topbar = await page.locator(".vg-topbar").evaluate((element) => {
const topbarRect = element.getBoundingClientRect();
const controls = Array.from(
element.querySelectorAll<HTMLElement>(
".vg-topbar__brand, .vg-topbar__switch-link, .vg-iconbtn",
),
).map((control) => {
const rect = control.getBoundingClientRect();
return {
label:
control.getAttribute("aria-label") ??
control.getAttribute("title") ??
control.textContent?.trim() ??
control.className,
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
width: rect.width,
height: rect.height,
};
});
return {
controls,
topbarLeft: topbarRect.left,
topbarRight: topbarRect.right,
topbarTop: topbarRect.top,
topbarBottom: topbarRect.bottom,
viewportWidth: window.innerWidth,
scrollWidth: document.documentElement.scrollWidth,
};
});
const labels = topbar.controls.map((control) => control.label);
expect(labels.slice(0, 4)).toEqual([
"Vignette",
"학습자 공간",
"교수 콘솔",
"운영 콘솔",
]);
expect(labels[4]).toMatch(/^(라이트|다크) 모드로$/);
expect(labels[5]).toBe("로그아웃");
for (const control of topbar.controls) {
expect(control.width, `${control.label} ${viewport.width}px width`).toBeGreaterThanOrEqual(44);
expect(control.height, `${control.label} ${viewport.width}px height`).toBeGreaterThanOrEqual(44);
expect(control.left, `${control.label} ${viewport.width}px left`).toBeGreaterThanOrEqual(
topbar.topbarLeft,
);
expect(control.right, `${control.label} ${viewport.width}px right`).toBeLessThanOrEqual(
topbar.topbarRight,
);
expect(control.top, `${control.label} ${viewport.width}px top`).toBeGreaterThanOrEqual(
topbar.topbarTop,
);
expect(control.bottom, `${control.label} ${viewport.width}px bottom`).toBeLessThanOrEqual(
topbar.topbarBottom,
);
}
expect(topbar.scrollWidth).toBe(topbar.viewportWidth);
}
});
// checklist: shell-auth-bootstrap-loading