feat: 운영 안정성과 세션 음성 경험 개선

This commit is contained in:
Yun Chan 2026-07-31 00:13:08 +09:00
parent facc4ad2d9
commit c788343467
95 changed files with 8431 additions and 1785 deletions

View file

@ -199,6 +199,54 @@ test.describe("full sweep — shared shell, GNB, and routing guards", () => {
await expect(page.getByText(BOOT_SCREEN_TEXT)).toHaveCount(0);
});
// checklist: shell-stale-chunk-recovery
// Pages 배포 전환 뒤 열린 탭의 Vite lazy 청크가 사라진 경우 한 번만 새 문서를
// 다시 받고, 정상 렌더 뒤 잠금을 해제한다. 같은 실패가 연속되면 무한 reload하지 않는다.
test("reloads once for a stale Vite route chunk without entering a reload loop", async ({
page,
}) => {
let documentRequests = 0;
page.on("request", (request) => {
if (request.isNavigationRequest()) documentRequests += 1;
});
await page.goto("/login");
await expect(page.getByRole("button", { name: /학교 Google 계정으로 계속/ })).toBeVisible();
expect(documentRequests).toBe(1);
const firstPrevented = await page.evaluate(() => {
const event = new Event("vite:preloadError", { cancelable: true });
Object.assign(event, {
payload: new TypeError("Failed to fetch dynamically imported module"),
});
return !window.dispatchEvent(event);
});
expect(firstPrevented).toBeTruthy();
await expect.poll(() => documentRequests).toBe(2);
await expect(page.getByRole("button", { name: /학교 Google 계정으로 계속/ })).toBeVisible();
await expect
.poll(() =>
page.evaluate(() => sessionStorage.getItem("vignette:chunk-recovery:/login")),
)
.toBeNull();
const secondAttempt = await page.evaluate(() => {
sessionStorage.setItem("vignette:chunk-recovery:/login", "reload-attempted");
const event = new Event("vite:preloadError", { cancelable: true });
Object.assign(event, {
payload: new TypeError("Failed to fetch dynamically imported module"),
});
return {
prevented: !window.dispatchEvent(event),
marker: sessionStorage.getItem("vignette:chunk-recovery:/login"),
};
});
expect(secondAttempt).toEqual({ prevented: false, marker: "reload-attempted" });
await expect.poll(() => documentRequests).toBe(2);
});
// checklist: shell-guard-pending-approval
// 미승인(account_status=pending) 사용자는 어떤 보호 경로에서든 /pending으로 회수된다.
test("redirects a pending-approval user to /pending from any protected route", async ({