feat(ai-character): 관리자 라우트 지연 로딩 적용

This commit is contained in:
Yu Sung
2026-08-06 21:38:12 +09:00
parent 68959cb33b
commit 2a04955bd7
10 changed files with 849 additions and 48 deletions

View File

@@ -2,6 +2,7 @@ import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
// allow: SIZE_OK - Character workspace browser scenarios share one authenticated E2E surface.
const interactiveActionRoles = ["button", "link", "menuitem"] as const;
async function loginThroughMockMode(page: Page): Promise<void> {
@@ -42,6 +43,27 @@ async function zoomTo200Percent(page: Page): Promise<void> {
});
}
async function expectNoKoreanSyllableColumns(root: Locator): Promise<void> {
const syllableColumns = await root.evaluate((element) => Array.from(element.querySelectorAll<HTMLElement>("*"))
.filter((candidate) => candidate.childElementCount === 0 && /[가-힣]/.test(candidate.textContent ?? ""))
.filter((candidate) => {
const style = getComputedStyle(candidate);
return style.display !== "none" && style.visibility !== "hidden" && candidate.clientWidth > 0 && candidate.clientHeight > 0;
})
.flatMap((candidate) => {
const range = document.createRange();
range.selectNodeContents(candidate);
const lineWidths = Array.from(range.getClientRects(), (rect) => rect.width).filter((width) => width > 0);
const fontSize = Number.parseFloat(getComputedStyle(candidate).fontSize);
const koreanSyllableCount = candidate.textContent?.match(/[가-힣]/g)?.length ?? 0;
const isSyllableColumn = koreanSyllableCount >= 3 && lineWidths.length >= 3 && Math.max(...lineWidths) <= fontSize * 2.25;
return isSyllableColumn ? [{ lineWidths, text: candidate.textContent?.trim() }] : [];
}));
expect(syllableColumns).toEqual([]);
}
async function pressTabUntilFocused(page: Page, target: Locator): Promise<void> {
for (let attempt = 0; attempt < 80; attempt += 1) {
if (await target.evaluate((element) => element === document.activeElement || element.contains(document.activeElement))) {
@@ -193,6 +215,19 @@ test("mobile cards preserve the selection name and table-visible core metadata",
await expect(lunaCard).toContainText("상담, 힐링");
});
test("mobile zoom keeps Korean list and pagination text out of syllable columns", async ({ page }) => {
// Given
await page.setViewportSize({ width: 320, height: 640 });
await loginThroughMockMode(page);
// When
await zoomTo200Percent(page);
// Then
await expectNoKoreanSyllableColumns(page.getByRole("region", { name: "AI 캐릭터 목록" }));
await expectNoKoreanSyllableColumns(page.getByRole("navigation", { name: "페이지" }));
});
test("keyboard-only path reaches the edit form and dirty-leave dialog", async ({ browserName, isMobile, page }) => {
test.skip(isMobile, "Mobile view intentionally hides Character mutation actions.");
test.skip(browserName === "webkit", "WebKit does not consistently tab-focus links in this keyboard path.");