세션 화면 디자인 개편
This commit is contained in:
parent
04a5f485ed
commit
12bdc59497
13 changed files with 1430 additions and 386 deletions
|
|
@ -673,7 +673,8 @@ test.describe("full sweep — counseling session", () => {
|
|||
await routeSessionFixtureApi(page);
|
||||
|
||||
await startFixtureSession(page);
|
||||
await expect(page.locator(".sx-signal__rest")).toContainText("아직 표시할 신호가 없어요");
|
||||
await expect(page.locator(".sx-signal__rest")).toHaveCount(0);
|
||||
await expect(page.locator(".sx-coach-card")).toBeVisible();
|
||||
|
||||
await page.getByLabel("학습자 발화 입력").fill(learnerText);
|
||||
await page.getByRole("button", { name: "보내기" }).click();
|
||||
|
|
@ -702,6 +703,7 @@ test.describe("full sweep — counseling session", () => {
|
|||
test("shows the coach nudge for a pending turn and requests coaching immediately when opened", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1366, height: 640 });
|
||||
const api = await routeSessionFixtureApi(page);
|
||||
|
||||
await startFixtureSession(page);
|
||||
|
|
@ -716,12 +718,175 @@ test.describe("full sweep — counseling session", () => {
|
|||
expect(api.liveCoachRequests).toHaveLength(0);
|
||||
|
||||
await nudge.click();
|
||||
await expect(page.locator(".sx-coach-card").getByText("감정 반영이 선명합니다")).toBeVisible();
|
||||
const coachCard = page.locator(".sx-coach-card");
|
||||
await expect(coachCard.getByText("감정 반영이 선명합니다")).toBeVisible();
|
||||
await expect(page.locator(".sx-signal__wave")).toHaveCount(0);
|
||||
|
||||
const railLayout = await page.evaluate(() => {
|
||||
const panel = document.querySelector<HTMLElement>(".sx-signal");
|
||||
const coach = document.querySelector<HTMLElement>(".sx-coach-card");
|
||||
const signal = document.querySelector<HTMLElement>(".sx-signal__one");
|
||||
if (!panel || !coach || !signal) return null;
|
||||
const panelRect = panel.getBoundingClientRect();
|
||||
const coachRect = coach.getBoundingClientRect();
|
||||
const signalRect = signal.getBoundingClientRect();
|
||||
return {
|
||||
coachBeforeSignal: coachRect.top < signalRect.top,
|
||||
coachInsidePanel:
|
||||
coachRect.top >= panelRect.top - 1 && coachRect.bottom <= panelRect.bottom + 1,
|
||||
coachInsideViewport: coachRect.bottom <= window.innerHeight + 1,
|
||||
};
|
||||
});
|
||||
expect(railLayout).toEqual({
|
||||
coachBeforeSignal: true,
|
||||
coachInsidePanel: true,
|
||||
coachInsideViewport: true,
|
||||
});
|
||||
|
||||
await page.setViewportSize({ width: 1024, height: 640 });
|
||||
await expect(coachCard).toBeVisible();
|
||||
await expect(page.locator(".sx-page--active .sx-col-right")).toBeVisible();
|
||||
await expect(page.locator(".sx-signal__one")).toBeHidden();
|
||||
const compactCoachInsideViewport = await coachCard.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return rect.top >= -1 && rect.bottom <= window.innerHeight + 1;
|
||||
});
|
||||
expect(compactCoachInsideViewport).toBe(true);
|
||||
|
||||
await expect(nudge).toHaveCount(0);
|
||||
expect(api.liveCoachRequests).toHaveLength(1);
|
||||
expect(api.liveCoachRequests[0]).toMatchObject({ learner_text: learnerText });
|
||||
});
|
||||
|
||||
test("uses the global light theme tokens throughout an active session", async ({ page }) => {
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("vignette.theme", "light");
|
||||
});
|
||||
await routeSessionFixtureApi(page);
|
||||
|
||||
await startFixtureSession(page);
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "light");
|
||||
await page.getByRole("button", { name: /코칭 모드, 남은 기회/ }).click();
|
||||
await expect(page.locator(".sx-coach-bubble")).toBeVisible();
|
||||
|
||||
const themeState = await page.evaluate(() => {
|
||||
const rootStyle = getComputedStyle(document.documentElement);
|
||||
const session = document.querySelector<HTMLElement>(".sx-page--active");
|
||||
const coachBubble = document.querySelector<HTMLElement>(".sx-coach-bubble");
|
||||
if (!session || !coachBubble) return null;
|
||||
const sessionStyle = getComputedStyle(session);
|
||||
const coachStyle = getComputedStyle(coachBubble);
|
||||
return {
|
||||
rootSurface: rootStyle.getPropertyValue("--bg-surface").trim(),
|
||||
sessionSurface: sessionStyle.getPropertyValue("--bg-surface").trim(),
|
||||
rootText: rootStyle.getPropertyValue("--text-strong").trim(),
|
||||
sessionText: sessionStyle.getPropertyValue("--text-strong").trim(),
|
||||
colorScheme: sessionStyle.colorScheme,
|
||||
coachInsetSurface:
|
||||
coachBubble.classList.contains("vg-surface--inset") &&
|
||||
coachStyle.getPropertyValue("--glass-surface-inset").trim() ===
|
||||
rootStyle.getPropertyValue("--glass-surface-inset").trim(),
|
||||
};
|
||||
});
|
||||
|
||||
expect(themeState).not.toBeNull();
|
||||
expect(themeState!.sessionSurface).toBe(themeState!.rootSurface);
|
||||
expect(themeState!.sessionText).toBe(themeState!.rootText);
|
||||
expect(themeState!.colorScheme).toBe("light");
|
||||
expect(themeState!.coachInsetSurface).toBe(true);
|
||||
});
|
||||
|
||||
test("matches the 1536px botanical session workspace composition", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await page.setViewportSize({ width: 1536, height: 1024 });
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("vignette.theme", "light");
|
||||
});
|
||||
await routeSessionFixtureApi(page);
|
||||
|
||||
await startFixtureSession(page);
|
||||
await expect(page.getByText("라이브 코칭", { exact: true })).toBeVisible();
|
||||
await expect(page.locator(".sx-coach-card")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "상태 신호" })).toHaveClass(/is-on/);
|
||||
|
||||
const layout = await page.evaluate(async () => {
|
||||
const pageRoot = document.querySelector<HTMLElement>(".sx-page--active");
|
||||
const bar = document.querySelector<HTMLElement>(".sx-sessionbar");
|
||||
const grid = document.querySelector<HTMLElement>(".sx-grid");
|
||||
const left = document.querySelector<HTMLElement>(".sx-col-left");
|
||||
const center = document.querySelector<HTMLElement>(".sx-col-center");
|
||||
const right = document.querySelector<HTMLElement>(".sx-col-right");
|
||||
const controls = document.querySelector<HTMLElement>(".sx-controlbar");
|
||||
const mic = document.querySelector<HTMLElement>(".sx-mic");
|
||||
const micText = document.querySelector<HTMLElement>(".sx-mic-block__ms");
|
||||
if (!pageRoot || !bar || !grid || !left || !center || !right || !controls || !mic || !micText) {
|
||||
return null;
|
||||
}
|
||||
const rect = (element: HTMLElement) => {
|
||||
const value = element.getBoundingClientRect();
|
||||
return {
|
||||
left: Math.round(value.left),
|
||||
top: Math.round(value.top),
|
||||
right: Math.round(value.right),
|
||||
bottom: Math.round(value.bottom),
|
||||
width: Math.round(value.width),
|
||||
height: Math.round(value.height),
|
||||
};
|
||||
};
|
||||
const leafResponse = await fetch("/session-botanical/leaf-1.webp");
|
||||
return {
|
||||
viewport: { width: window.innerWidth, height: window.innerHeight },
|
||||
page: rect(pageRoot),
|
||||
bar: rect(bar),
|
||||
grid: rect(grid),
|
||||
left: rect(left),
|
||||
center: rect(center),
|
||||
right: rect(right),
|
||||
controls: rect(controls),
|
||||
mic: rect(mic),
|
||||
micText: rect(micText),
|
||||
pageLeaf: getComputedStyle(pageRoot, "::before").backgroundImage,
|
||||
stageLeaf: getComputedStyle(
|
||||
document.querySelector<HTMLElement>(".sx-orb-wrap")!,
|
||||
"::before",
|
||||
).backgroundImage,
|
||||
leafAsset: {
|
||||
ok: leafResponse.ok,
|
||||
contentType: leafResponse.headers.get("content-type"),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
expect(layout).not.toBeNull();
|
||||
expect(layout!.viewport).toEqual({ width: 1536, height: 1024 });
|
||||
expect(layout!.page).toMatchObject({ left: 0, top: 0, width: 1536, height: 1024 });
|
||||
expect(layout!.bar.width).toBeGreaterThanOrEqual(1400);
|
||||
expect(layout!.bar.width).toBeLessThanOrEqual(1410);
|
||||
expect(layout!.grid.width).toBeGreaterThanOrEqual(1400);
|
||||
expect(layout!.grid.width).toBeLessThanOrEqual(1410);
|
||||
expect(layout!.left.width).toBeGreaterThanOrEqual(324);
|
||||
expect(layout!.left.width).toBeLessThanOrEqual(328);
|
||||
expect(layout!.right.width).toBeGreaterThanOrEqual(355);
|
||||
expect(layout!.right.width).toBeLessThanOrEqual(359);
|
||||
expect(layout!.center.left).toBeGreaterThan(layout!.left.right);
|
||||
expect(layout!.right.left).toBeGreaterThan(layout!.center.right);
|
||||
expect(layout!.controls.width).toBeGreaterThanOrEqual(1460);
|
||||
expect(layout!.controls.width).toBeLessThanOrEqual(1470);
|
||||
expect(layout!.controls.bottom).toBeLessThanOrEqual(1024);
|
||||
expect(layout!.micText.left).toBeGreaterThanOrEqual(layout!.mic.right + 8);
|
||||
expect(layout!.pageLeaf).toContain("leaf-5.webp");
|
||||
expect(layout!.stageLeaf).toContain("leaf-1.webp");
|
||||
expect(layout!.leafAsset).toEqual({ ok: true, contentType: "image/webp" });
|
||||
|
||||
const screenshotPath = testInfo.outputPath("session-botanical-1536x1024.png");
|
||||
await page.screenshot({ path: screenshotPath, animations: "disabled" });
|
||||
await testInfo.attach("session-botanical-1536x1024", {
|
||||
path: screenshotPath,
|
||||
contentType: "image/png",
|
||||
});
|
||||
});
|
||||
|
||||
// checklist: session-meters-collapse
|
||||
// 모바일(≤880px) 압축 레이아웃은 관찰 게이지 패널을 의도적으로 숨긴다(session.css) —
|
||||
// 데스크톱 전용 UI라 desktop 프로젝트에서만 검증한다.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue