평가 및 화면 구조 정리

This commit is contained in:
Yun Chan 2026-06-28 21:46:22 +09:00
parent 1248ae8ca4
commit 391639c1de
44 changed files with 5816 additions and 4501 deletions

View file

@ -376,6 +376,10 @@ test.describe("layout visual gate @single-run", () => {
await gateScreen(page, "persona-studio", async () => {
await expect(page.locator(".ps-layout")).toBeVisible({ timeout: 15_000 });
await expect(page.getByText("내담자 설계·검수 작업면")).toBeVisible();
await page.getByRole("tab", { name: "프롬프트" }).click();
const promptPreview = page.getByLabel("프롬프트 미리보기");
await expect(promptPreview).toBeVisible();
await expect(promptPreview.getByRole("textbox")).toHaveCount(0);
});
});

View file

@ -329,6 +329,108 @@ test.describe("teacher console", () => {
await expectNoHorizontalOverflow(page);
});
test("saves persona studio list rows as structured arrays @single-run", async ({ page }) => {
type PersonaDraftSavePayload = {
ccd: { automatic_thought: string[] };
code: string;
difficulty: string;
display_name: string;
is_synthetic: boolean;
source_provenance: unknown;
theory_target: string;
triggers: { forbidden: string[]; sore_spots: string[] };
};
let savedPayload: PersonaDraftSavePayload | null = null;
await signInAsTeacher(page);
await page.route("**/api/personas", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.route("**/api/personas/review", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.route("**/api/personas/drafts", async (route) => {
if (route.request().method() !== "POST") return route.fallback();
savedPayload = route.request().postDataJSON() as PersonaDraftSavePayload;
await route.fulfill({
status: 201,
contentType: "application/json",
body: JSON.stringify({
persona_id: "00000000-0000-0000-0000-000000000703",
code: savedPayload.code,
version: 1,
status: "draft",
display_name: savedPayload.display_name,
difficulty: savedPayload.difficulty,
theory_target: savedPayload.theory_target,
source_provenance: savedPayload.source_provenance,
is_synthetic: savedPayload.is_synthetic,
created_at: "2026-06-28T00:00:00Z",
approved_at: null,
}),
});
});
await page.goto("/teach/personas");
await page.getByLabel("표시 이름").fill("항목형 페르소나");
await page.getByRole("tab", { name: "임상" }).click();
const automaticThoughts = page.locator(".ps-list-field").filter({ hasText: "자동사고" });
await automaticThoughts.getByRole("textbox", { name: "자동사고 1", exact: true }).fill("삭제될 자동사고");
await automaticThoughts.getByRole("button", { name: "항목 추가" }).click();
await automaticThoughts.getByRole("textbox", { name: "자동사고 2", exact: true }).fill("남길 자동사고");
await automaticThoughts.getByRole("button", { name: "자동사고 1 삭제" }).click();
await page.getByRole("tab", { name: "안전" }).click();
const forbidden = page.locator(".ps-list-field").filter({ hasText: "상담자 금기" });
await forbidden.getByRole("textbox", { name: "상담자 금기 1", exact: true }).fill("네가 예민한 거라고 단정");
await page.getByRole("button", { name: "초안 저장" }).click();
await expect(page.getByText("P1 v1 초안을 저장했습니다.")).toBeVisible();
expect(savedPayload).toBeTruthy();
expect(savedPayload?.ccd.automatic_thought).toEqual(["남길 자동사고"]);
expect(savedPayload?.triggers.forbidden).toEqual(["네가 예민한 거라고 단정"]);
expect(savedPayload?.triggers.sore_spots).toEqual([]);
await expectNoHorizontalOverflow(page);
});
test("renders persona prompt preview as labeled sections without raw JSON @single-run", async ({ page }) => {
await signInAsTeacher(page);
await page.route("**/api/personas", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.route("**/api/personas/review", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.goto("/teach/personas");
await page.getByLabel("표시 이름").fill("프롬프트 검토 페르소나");
await page.getByRole("tab", { name: "임상" }).click();
const automaticThoughts = page.locator(".ps-list-field").filter({ hasText: "자동사고" });
await automaticThoughts.getByRole("textbox", { name: "자동사고 1", exact: true }).fill("말하면 더 이상하게 볼 거야");
await page.getByRole("tab", { name: "안전" }).click();
const forbidden = page.locator(".ps-list-field").filter({ hasText: "상담자 금기" });
await forbidden.getByRole("textbox", { name: "상담자 금기 1", exact: true }).fill("네가 예민한 거라고 단정");
await page.getByRole("tab", { name: "프롬프트" }).click();
const promptPreview = page.getByLabel("프롬프트 미리보기");
await expect(promptPreview.getByRole("heading", { name: /L1 페르소나 카드/ })).toBeVisible();
await expect(promptPreview.getByText("자동사고")).toBeVisible();
await expect(promptPreview.getByText("말하면 더 이상하게 볼 거야")).toBeVisible();
await expect(promptPreview.getByText("상담자 금기")).toBeVisible();
await expect(promptPreview.getByText("네가 예민한 거라고 단정")).toBeVisible();
await expect(promptPreview.getByRole("textbox")).toHaveCount(0);
await expect(promptPreview).not.toContainText('"automatic_thought"');
await expect(promptPreview).not.toContainText('"forbidden"');
await expect(promptPreview).not.toContainText("{");
await expectNoHorizontalOverflow(page);
});
test("archives an approved persona from persona studio without layout drift @single-run", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
const personaId = "00000000-0000-0000-0000-000000000702";