전 저장소 리팩터링과 SSOT 정비
This commit is contained in:
parent
14ecbd4e7d
commit
3dfddcac6f
173 changed files with 19679 additions and 6952 deletions
|
|
@ -97,63 +97,79 @@ async function createEndedSession(page: Page) {
|
|||
return session.session_id;
|
||||
}
|
||||
|
||||
/** 리뷰는 모든 폭에서 가로 탭으로 영역을 전환한다. */
|
||||
async function openReviewTab(page: Page, label: "축어록" | "피드백" | "워크시트") {
|
||||
// 탭 바는 리뷰 레이아웃이 로드된 뒤에만 DOM에 존재한다 — attach를 먼저 기다린다.
|
||||
const tabs = page.locator(".sr-tabs");
|
||||
await tabs.waitFor({ state: "attached", timeout: 10_000 }).catch(() => {});
|
||||
const tab = tabs.locator("button", { hasText: label });
|
||||
if (await tab.isVisible().catch(() => false)) {
|
||||
await tab.click();
|
||||
}
|
||||
}
|
||||
|
||||
async function expectReviewRegionsReachable(page: Page) {
|
||||
await expect(page.locator(".sr-overview")).toBeVisible();
|
||||
await expect(page.locator(".sr-tabs")).toBeVisible();
|
||||
|
||||
// 피드백 탭: 인사이트 흐름 + 평가 레일이 함께 도달 가능해야 한다.
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.locator(".sr-feedback")).toBeVisible();
|
||||
await expect(page.locator(".sr-card--chart")).toBeVisible();
|
||||
await expect(page.locator(".sr-card--flow")).toBeVisible();
|
||||
await expect(page.locator(".sr-card--rubric")).toBeVisible();
|
||||
await expect(page.locator(".sr-card--transcript")).toBeVisible();
|
||||
await expect(page.locator(".sr-card--prepost")).toBeVisible();
|
||||
|
||||
const grid = await page.locator(".sr-cols").evaluate((el) => {
|
||||
const insightsGrid = await page.locator(".sr-cols").evaluate((el) => {
|
||||
const style = window.getComputedStyle(el);
|
||||
return {
|
||||
display: style.display,
|
||||
areas: style.gridTemplateAreas,
|
||||
columns: style.gridTemplateColumns.split(" ").filter(Boolean).length,
|
||||
};
|
||||
});
|
||||
expect(grid.display).toBe("grid");
|
||||
expect(grid.areas).toContain("overview");
|
||||
expect(grid.areas).toContain("feedback");
|
||||
expect(grid.areas).toContain("transcript");
|
||||
expect(grid.areas).not.toContain("teacher");
|
||||
expect(insightsGrid.display).toBe("grid");
|
||||
expect(insightsGrid.areas).toContain("insight");
|
||||
expect(insightsGrid.areas).toContain("feedback");
|
||||
expect(insightsGrid.areas).not.toContain("teacher");
|
||||
// 전 폭 탭 계약: 활성 탭 하나가 전폭 단일 컬럼을 쓴다(인사이트 위 / 평가 아래).
|
||||
expect(insightsGrid.columns).toBe(1);
|
||||
const stackGeometry = await page.locator(".sr-cols").evaluate((el) => {
|
||||
const insight = el.querySelector(".sr-left")?.getBoundingClientRect();
|
||||
const feedback = el.querySelector(".sr-right")?.getBoundingClientRect();
|
||||
return {
|
||||
insightBottom: insight ? Math.ceil(insight.bottom) : 0,
|
||||
feedbackTop: feedback ? Math.floor(feedback.top) : 0,
|
||||
};
|
||||
});
|
||||
expect(stackGeometry.insightBottom).toBeLessThanOrEqual(stackGeometry.feedbackTop + 1);
|
||||
|
||||
// 워크시트 탭.
|
||||
await openReviewTab(page, "워크시트");
|
||||
await expect(page.locator(".sr-card--worksheet")).toBeVisible();
|
||||
|
||||
// 축어록 탭(기본)으로 복귀 — 축어록은 전폭 단일 컬럼.
|
||||
await openReviewTab(page, "축어록");
|
||||
await expect(page.locator(".sr-card--transcript")).toBeVisible();
|
||||
const transcriptAreas = await page
|
||||
.locator(".sr-cols")
|
||||
.evaluate((el) => window.getComputedStyle(el).gridTemplateAreas);
|
||||
expect(transcriptAreas).toContain("transcript");
|
||||
|
||||
await expect(page.locator(".sr-overview")).toBeInViewport();
|
||||
await page.locator(".sr-card--transcript").scrollIntoViewIfNeeded();
|
||||
await expect(page.locator(".sr-card--transcript")).toBeInViewport();
|
||||
|
||||
const railGeometry = await page.locator(".sr-cols").evaluate((gridEl) => {
|
||||
const root = gridEl.closest(".sr-root");
|
||||
const overview = gridEl.querySelector(".sr-overview")?.getBoundingClientRect();
|
||||
const transcript = gridEl.querySelector(".sr-card--transcript")?.getBoundingClientRect();
|
||||
const rubric = gridEl.querySelector(".sr-card--rubric")?.getBoundingClientRect();
|
||||
const prepost = gridEl.querySelector(".sr-card--prepost")?.getBoundingClientRect();
|
||||
const worksheet = gridEl.querySelector(".sr-card--worksheet")?.getBoundingClientRect();
|
||||
const columns = window
|
||||
.getComputedStyle(gridEl)
|
||||
.gridTemplateColumns.split(" ")
|
||||
.filter(Boolean).length;
|
||||
// 리뷰 요약은 본문 그리드 위의 전폭 밴드다(도킹/스크롤 부유 금지).
|
||||
const band = await page.locator(".sr-root").evaluate((rootEl) => {
|
||||
const overview = rootEl.querySelector(".sr-overview")?.getBoundingClientRect();
|
||||
const gridRect = rootEl.querySelector(".sr-cols")?.getBoundingClientRect();
|
||||
return {
|
||||
isEmpty: root?.classList.contains("sr-root--empty") ?? false,
|
||||
columns,
|
||||
overviewLeft: overview ? Math.floor(overview.left) : 0,
|
||||
overviewRight: overview ? Math.ceil(overview.right) : 0,
|
||||
transcriptLeft: transcript ? Math.floor(transcript.left) : 0,
|
||||
transcriptRight: transcript ? Math.ceil(transcript.right) : 0,
|
||||
rubricLeft: rubric ? Math.floor(rubric.left) : 0,
|
||||
prepostLeft: prepost ? Math.floor(prepost.left) : 0,
|
||||
worksheetLeft: worksheet ? Math.floor(worksheet.left) : 0,
|
||||
overviewBottom: overview ? Math.ceil(overview.bottom) : 0,
|
||||
gridTop: gridRect ? Math.floor(gridRect.top) : 0,
|
||||
};
|
||||
});
|
||||
if (!railGeometry.isEmpty && railGeometry.columns === 2) {
|
||||
expect(railGeometry.transcriptRight).toBeLessThanOrEqual(railGeometry.overviewLeft);
|
||||
expect(railGeometry.rubricLeft).toBeGreaterThanOrEqual(railGeometry.overviewLeft);
|
||||
expect(railGeometry.prepostLeft).toBeGreaterThanOrEqual(railGeometry.overviewLeft);
|
||||
expect(railGeometry.worksheetLeft).toBeGreaterThanOrEqual(railGeometry.overviewLeft);
|
||||
} else if (railGeometry.columns >= 3) {
|
||||
expect(railGeometry.overviewRight).toBeLessThanOrEqual(railGeometry.transcriptLeft);
|
||||
}
|
||||
expect(band.overviewBottom).toBeLessThanOrEqual(band.gridTop + 1);
|
||||
}
|
||||
|
||||
test.describe("session review", () => {
|
||||
|
|
@ -186,6 +202,7 @@ test.describe("session review", () => {
|
|||
await page.goto(`/learn/session/${sessionId}/review`);
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/teach/session/${sessionId}/review$`));
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.getByText("교수자 검토", { exact: true })).toBeVisible();
|
||||
expect(reviewAttempts).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
|
@ -221,6 +238,7 @@ test.describe("session review", () => {
|
|||
await page.goto(`/learn/session/${sessionId}/review`);
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/teach/session/${sessionId}/review$`));
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.getByText("교수자 검토", { exact: true })).toBeVisible();
|
||||
await expect(page.locator(".sr-card--prepost")).toHaveCount(0);
|
||||
expect(reviewAttempts).toBeGreaterThanOrEqual(2);
|
||||
|
|
@ -233,6 +251,7 @@ test.describe("session review", () => {
|
|||
|
||||
await page.goto(`/teach/session/${sessionId}/review`);
|
||||
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.getByText("AI 평가 범위", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("현재 구현", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText(/fast-loop.*deep-loop/)).toBeVisible();
|
||||
|
|
@ -348,6 +367,7 @@ test.describe("session review", () => {
|
|||
|
||||
await page.goto(`/teach/session/${sessionId}/review`);
|
||||
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.getByText("평가 대기", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("평가 AI가 저장된 축어록을 분석 중입니다.")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "AI 평가 재시도" })).toHaveCount(0);
|
||||
|
|
@ -413,6 +433,7 @@ test.describe("session review", () => {
|
|||
});
|
||||
|
||||
await page.goto(`/teach/session/${sessionId}/review`);
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.getByText("평가 실패")).toBeVisible();
|
||||
await expect(
|
||||
page.getByText("저장된 축어록은 확인했지만 deep-loop 평가 AI 산출물을 표시하지 못했습니다. AI 평가 재시도가 필요합니다."),
|
||||
|
|
@ -507,6 +528,7 @@ test.describe("session review", () => {
|
|||
|
||||
await expect(page).toHaveURL(new RegExp(`/learn/session/${sessionId}/review$`));
|
||||
await expect(page.getByText("축어록 없음")).toBeVisible();
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.getByText("감정 타임라인 대기")).toBeVisible();
|
||||
await expect(page.getByText("개선점 대기")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "오디오 다시 듣기" })).toBeDisabled();
|
||||
|
|
@ -547,9 +569,12 @@ test.describe("session review", () => {
|
|||
|
||||
await expect(page).toHaveURL(new RegExp(`/learn/session/${FILLED_REVIEW_SESSION_ID}/review$`));
|
||||
await expect(page.getByText("관계 신호가 안정적으로 회복된 회기")).toBeVisible();
|
||||
await expect(page.getByText("세션 트랜스크립트")).toBeVisible();
|
||||
await expect(page.getByText("감정 반영").first()).toBeVisible();
|
||||
await expect(page.getByText("AI 슈퍼바이저").first()).toBeVisible();
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.getByText("감정 밸런스 타임라인")).toBeVisible();
|
||||
await expect(page.getByText("회기 흐름")).toBeVisible();
|
||||
await expect(page.getByText("세션 트랜스크립트")).toBeVisible();
|
||||
await expect(page.getByText("기법 사용 분포")).toBeVisible();
|
||||
await expect(page.getByText("파일럿 증거 원장")).toBeVisible();
|
||||
await expect(page.getByText("1/3 쌍")).toBeVisible();
|
||||
|
|
@ -569,13 +594,12 @@ test.describe("session review", () => {
|
|||
"수련만족도 사후 점수",
|
||||
]);
|
||||
await expect(page.getByText("효과 판정과 통계 검정은 평가설계 확정 후 별도 산출합니다.")).toBeVisible();
|
||||
await expect(page.getByText("사례개념화 워크시트")).toBeVisible();
|
||||
await expect(page.getByText("감정 반영").first()).toBeVisible();
|
||||
await expect(page.getByText("AI 슈퍼바이저").first()).toBeVisible();
|
||||
await expect(page.getByText("다음에 시도할 문장")).toBeVisible();
|
||||
await expect(page.getByText("방어를 낮춘 감정 반영")).toBeVisible();
|
||||
await expect(page.getByText("개입 전 준비도 확인")).toBeVisible();
|
||||
await expect(page.locator(".sr-chart__svg")).toBeVisible();
|
||||
await openReviewTab(page, "워크시트");
|
||||
await expect(page.getByText("사례개념화 워크시트")).toBeVisible();
|
||||
await expect(page.locator(".sr-ws-input").first()).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "오디오 다시 듣기" })).toBeEnabled();
|
||||
await expect(page.getByRole("button", { name: "PDF 내보내기" })).toBeEnabled();
|
||||
|
|
@ -624,6 +648,7 @@ test.describe("session review", () => {
|
|||
"저는 익명 내담자이고 소속 기관에서 상담받고 있어요.",
|
||||
);
|
||||
await expect(transcript).not.toContainText("[NAME]");
|
||||
await openReviewTab(page, "워크시트");
|
||||
await expect(page.locator(".sr-ws-evidence").filter({ hasText: "[NAME]" })).toBeVisible();
|
||||
});
|
||||
|
||||
|
|
@ -633,6 +658,8 @@ test.describe("session review", () => {
|
|||
await routePrepostMeasures(page);
|
||||
|
||||
await page.goto(`/learn/session/${FILLED_REVIEW_SESSION_ID}/review`);
|
||||
await expect(page.locator(".sr-overview")).toBeVisible({ timeout: 15_000 });
|
||||
await openReviewTab(page, "피드백");
|
||||
await expect(page.locator(".sr-card--prepost")).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
const prepostInputs = page.locator(".sr-card--prepost input");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue