From 84fcdfb2bf01848f429bdafeab20c6fdb7fa9111 Mon Sep 17 00:00:00 2001 From: Yaphet Y Date: Sun, 1 Nov 2026 19:44:56 +0800 Subject: [PATCH] Fix e2e failures after goal-weights merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Manual boards keep the 我能解释 seal (weight-aware mastery only for weighted AI cards) - Update e2e tests to goal-weights behavior: 第 n 站 label, new recheck toast, default weight on import/restore, click title past weight footer - Skip probe-entry and two-wrong 卡住了 tests superseded by spec-v2 - goal-weights screenshots default to test-results (CI has no /workspace) --- src/components/CanvasNodes.tsx | 9 ++++++--- tests/e2e/electron-transfer.spec.ts | 5 ++++- tests/e2e/flow-p2.spec.ts | 13 +++++++++---- tests/e2e/goal-weights.spec.ts | 5 ++++- tests/e2e/sessions.spec.ts | 3 ++- tests/e2e/transfer.spec.ts | 5 ++++- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/components/CanvasNodes.tsx b/src/components/CanvasNodes.tsx index f3e5d67..46ee988 100644 --- a/src/components/CanvasNodes.tsx +++ b/src/components/CanvasNodes.tsx @@ -488,6 +488,9 @@ export function faceText(body: string): string { export function ConceptCard({ data, selected }: NodeProps) { const n = data.concept; + // Weighted AI cards (weightActions) only count core/hard answers as mastery; manual boards keep + // the learner's own 「我能解释」 mark as the seal. + const mastered = data.weightActions ? cardMastered(n) : n.status === "explained"; const edits = useContext(AgentEdits); // Stamp only when the status changes while mounted, not on first paint. const first = useRef(n.status); @@ -509,11 +512,11 @@ export function ConceptCard({ data, selected }: NodeProps) { {data.generating && } {data.noteOpen && } - {cardMastered(n) && data.station === undefined && } + {mastered && (!data.weightActions || data.station === undefined) && } {data.preview === "replace" && 待替换} {data.station !== undefined && data.preview !== "remove" && ( - - {n.status !== "unseen" && } + + {n.status !== "unseen" && } 第 {data.station} 站 )} diff --git a/tests/e2e/electron-transfer.spec.ts b/tests/e2e/electron-transfer.spec.ts index 3ebf100..7eb9c0c 100644 --- a/tests/e2e/electron-transfer.spec.ts +++ b/tests/e2e/electron-transfer.spec.ts @@ -132,7 +132,10 @@ test("desktop import restores SQLite histories; native exports and reopening pre }), [imported!.id, board.notes[0].id], ); - expect(restored.board!.nodes).toEqual(board.nodes); + // Restored boards pass the schema, which fills the goal-weights default weight. + expect(restored.board!.nodes).toEqual( + board.nodes.map((n: any) => ({ ...n, weight: n.weight ?? "background" })), + ); expect(restored.history.messages).toHaveLength(242); expect(restored.history.messages[0].text).toBe("桌面共享记录 0"); expect(restored.history.running).toBe(false); diff --git a/tests/e2e/flow-p2.spec.ts b/tests/e2e/flow-p2.spec.ts index f0897b0..49911a4 100644 --- a/tests/e2e/flow-p2.spec.ts +++ b/tests/e2e/flow-p2.spec.ts @@ -22,7 +22,10 @@ async function newQuestion(page: Page, topic: string) { await page.getByRole("button", { name: "开始探索", exact: true }).click(); } -test("摸底 v2: brackets the edge without levels or marks; stations below the start get the light seal", async ({ page }) => { +// spec-v2 (goal-weights): a new question now opens the chat kickoff (产出物/成功标准/约束 3–5 题), +// not the 摸底 probe, so this entry path is no longer reachable from 新问题. Kept for reference until the +// probe code is either rewired or removed. +test.skip("摸底 v2: brackets the edge without levels or marks; stations below the start get the light seal", async ({ page }) => { await installFakeAtlas(page); await newQuestion(page, "重疾险有哪些重要条款?"); const probe = page.locator(".drawer").getByRole("region", { name: "摸底" }); @@ -110,7 +113,7 @@ test("a light-sealed stop can be rechecked; failing it removes the seal and queu await stop.getByRole("button", { name: "复查一下" }).click(); await stop.getByRole("radio", { name: /不赔/ }).click(); await stop.getByRole("button", { name: /提交/ }).click(); - await expect(page.getByText(/去掉了浅印/)).toBeVisible(); + await expect(page.getByText(/这一站排在下一步之后/)).toBeVisible(); await expect .poll(async () => { const d = await disk(page); @@ -119,7 +122,9 @@ test("a light-sealed stop can be rechecked; failing it removes the seal and queu .toEqual(["none", "c-when-not,c-what,c-how,c-money"]); }); -test("stuck after two wrong answers: three exits; 换个类比 goes back to 学, 往下拆 drafts two children", async ({ page }) => { +// spec-v2 (goal-weights): the first wrong answer now promotes the card one weight level and swaps in +// the choice footer (see flow.spec.ts), so the old two-wrong-answers 卡住了 exits are no longer reached. +test.skip("stuck after two wrong answers: three exits; 换个类比 goes back to 学, 往下拆 drafts two children", async ({ page }) => { const b = bare(); Object.assign(b.nodes.find((n: any) => n.id === "c-when-not"), { status: "exploring", beat: "test", fails: 1 }); await installFakeAtlas(page, true, "web", [b]); @@ -180,7 +185,7 @@ test("稍后 puts the stuck stop off to the end of the route", async ({ page }) await page.getByRole("group", { name: "卡住了" }).getByRole("button", { name: /稍后/ }).click(); await expect(bar).toContainText("下一步 · 第 2 站"); await expect(bar).not.toContainText("等待期"); - await expect(card(page, "c-when-not").locator(".station")).toHaveText("4"); + await expect(card(page, "c-when-not").locator(".station")).toHaveText("第 4 站"); }); test("合起来讲: with every stop sealed, the root asks for the wrap-up; the stations it reaches light up", async ({ page }) => { diff --git a/tests/e2e/goal-weights.spec.ts b/tests/e2e/goal-weights.spec.ts index 7a903fa..11572f7 100644 --- a/tests/e2e/goal-weights.spec.ts +++ b/tests/e2e/goal-weights.spec.ts @@ -1,10 +1,12 @@ +import { mkdirSync } from "node:fs"; import { test, expect, type Page } from "@playwright/test"; import { installFakeAtlas } from "./fake-atlas"; import { gotoApp, reloadApp } from "./ready"; import { fixtureGoal, goalBoard } from "../goal-fixtures"; import { createEmptyBoard } from "../../src/model/boards"; -const shots = "/workspace/studyboard-goal/v2/shots"; +// Review screenshots; CI has no /workspace, so default into test-results unless GOAL_SHOTS is set. +const shots = process.env.GOAL_SHOTS ?? "test-results/goal-weights-shots"; const card = (page: Page, id: string) => page.locator(`.react-flow__node[data-id="${id}"]`); const disk = (page: Page) => @@ -28,6 +30,7 @@ async function screenshot(page: Page, name: string) { await page .locator("textarea") .evaluateAll((els) => els.forEach((el) => el.blur())); + mkdirSync(shots, { recursive: true }); await page.screenshot({ path: `${shots}/${name}.png`, animations: "disabled", diff --git a/tests/e2e/sessions.spec.ts b/tests/e2e/sessions.spec.ts index 9513ce1..1e55777 100644 --- a/tests/e2e/sessions.spec.ts +++ b/tests/e2e/sessions.spec.ts @@ -70,7 +70,8 @@ test("sidebar conversations stay ordered, persist and reopen on their cards", as await expect(drawer.locator(".msg.user")).toHaveText("赔付次数怎么算?"); await expect(card(page, "c-how")).toHaveClass(/selected/); await drawer.getByRole("button", { name: "关闭", exact: true }).click(); - await card(page, "c-how").click(); + // Asking on c-how promotes it (goal-weights), so its face now has a weight footer; click the title. + await card(page, "c-how").locator("h3").click(); await expect(drawer.locator(".msg.user")).toHaveText("间隔期是多久?"); }); diff --git a/tests/e2e/transfer.spec.ts b/tests/e2e/transfer.spec.ts index 2f6d7ef..d442863 100644 --- a/tests/e2e/transfer.spec.ts +++ b/tests/e2e/transfer.spec.ts @@ -72,7 +72,10 @@ test("a shared file restores complete conversations as a new board, survives rel JSON.parse(localStorage.getItem("studyboard-preview-boards-v1")!).at(-1), ); expect(imported.id).not.toBe(original.id); - expect(imported.nodes).toEqual(original.nodes); + // Import validates through the board schema, which fills the goal-weights default weight. + expect(imported.nodes).toEqual( + original.nodes.map((n: any) => ({ ...n, weight: n.weight ?? "background" })), + ); expect(imported.notes[0].messages).toHaveLength(200); await reloadApp(page); await page