운영 화면과 회기 리뷰 UI 갱신

This commit is contained in:
Yun Chan 2026-06-28 20:13:21 +09:00
parent e7ebb38177
commit 3a9f70a97b
18 changed files with 2210 additions and 137 deletions

View file

@ -1,7 +1,13 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import { expect, test, type Page } from "@playwright/test";
import { FILLED_REVIEW_SESSION_ID, routeFilledSessionReview } from "./session-review-fixture";
import {
EMPTY_REVIEW_SESSION_ID,
FILLED_REVIEW_SESSION_ID,
routeEmptySessionReview,
routeFilledSessionReview,
routePrepostMeasures,
} from "./session-review-fixture";
import {
completeOnboarding,
expectNoHorizontalOverflow,
@ -190,6 +196,81 @@ async function gateScreen(
}
}
async function expectEmptyReviewNoDeadThirdColumn(page: Page) {
const report = await page.evaluate(() => {
const root = document.querySelector<HTMLElement>(".sr-root--empty");
const cols = document.querySelector<HTMLElement>(".sr-root--empty .sr-cols");
const transcript = document.querySelector<HTMLElement>(".sr-card--transcript");
if (!root || !cols || !transcript) {
return {
present: false,
columnCount: 0,
};
}
const gridTemplate = getComputedStyle(cols).gridTemplateColumns;
const columnCount = gridTemplate.split(" ").filter(Boolean).length;
const transcriptRect = transcript.getBoundingClientRect();
return {
present: true,
columnCount,
transcriptWidth: Math.round(transcriptRect.width),
mainColumnWidth: Math.round(cols.getBoundingClientRect().width),
};
});
expect(report.present, "empty review layout should be mounted").toBe(true);
expect(
report.columnCount,
"empty review should collapse the desktop masonry layout instead of leaving a sparse third column",
).toBeLessThanOrEqual(2);
}
async function expectFilledReviewLearnerTwoColumn(page: Page) {
const report = await page.evaluate(() => {
const cols = document.querySelector<HTMLElement>(".sr-cols--learner");
const transcript = document.querySelector<HTMLElement>(".sr-card--transcript");
const overview = document.querySelector<HTMLElement>(".sr-overview");
const rubric = document.querySelector<HTMLElement>(".sr-card--rubric");
const worksheet = document.querySelector<HTMLElement>(".sr-card--worksheet");
if (!cols || !transcript || !overview || !rubric || !worksheet) {
return {
present: false,
viewportWidth: window.innerWidth,
columnCount: 0,
transcriptRight: 0,
overviewLeft: 0,
rubricLeft: 0,
worksheetLeft: 0,
};
}
const columnCount = getComputedStyle(cols).gridTemplateColumns.split(" ").filter(Boolean).length;
const transcriptRect = transcript.getBoundingClientRect();
const overviewRect = overview.getBoundingClientRect();
const rubricRect = rubric.getBoundingClientRect();
const worksheetRect = worksheet.getBoundingClientRect();
return {
present: true,
viewportWidth: window.innerWidth,
columnCount,
transcriptRight: Math.ceil(transcriptRect.right),
overviewLeft: Math.floor(overviewRect.left),
rubricLeft: Math.floor(rubricRect.left),
worksheetLeft: Math.floor(worksheetRect.left),
};
});
expect(report.present, "filled learner review layout should be mounted").toBe(true);
if (report.viewportWidth > 1180) {
expect(report.columnCount, "desktop learner review should use transcript + work column").toBe(2);
expect(report.transcriptRight).toBeLessThanOrEqual(report.overviewLeft);
expect(report.rubricLeft).toBeGreaterThanOrEqual(report.overviewLeft);
expect(report.worksheetLeft).toBeGreaterThanOrEqual(report.overviewLeft);
}
}
test.describe("layout visual gate @single-run", () => {
test.describe.configure({ mode: "serial" });
@ -257,10 +338,25 @@ test.describe("layout visual gate @single-run", () => {
test("session review stays contained across all widths", async ({ page }) => {
await signInAsLearner(page);
await routeFilledSessionReview(page);
await routePrepostMeasures(page);
await page.goto(`/learn/session/${FILLED_REVIEW_SESSION_ID}/review`);
await gateScreen(page, "session-review", async () => {
await expect(page.locator(".sr-overview")).toBeVisible({ timeout: 15_000 });
await expect(page.getByText("사례개념화 워크시트")).toBeVisible();
await expectFilledReviewLearnerTwoColumn(page);
});
});
test("empty session review avoids sparse column gaps across all widths", async ({ page }) => {
await signInAsLearner(page);
await routeEmptySessionReview(page);
await routePrepostMeasures(page);
await page.goto(`/learn/session/${EMPTY_REVIEW_SESSION_ID}/review`);
await gateScreen(page, "session-review-empty", async () => {
await expect(page.locator(".sr-root--empty")).toBeVisible({ timeout: 15_000 });
await expect(page.getByText("축어록 저장 후 생성")).toBeVisible();
await expect(page.getByText("감정 타임라인 대기")).toBeVisible();
await expectEmptyReviewNoDeadThirdColumn(page);
});
});