Skip to content

Commit c80388c

Browse files
committed
feat: add tests for BrowserActivityWidget and TimerWidget; update existing tests for consistency
1 parent c4b44fe commit c80388c

7 files changed

Lines changed: 88 additions & 6 deletions

‎docs/WIDGET_RUNTIME_REWRITE_IMPLEMENTATION_PLAN.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,13 +697,14 @@ After this plan is reviewed, the next artifact should be a narrow RFC that freez
697697
| 后端默认权限 | `src-tauri/src/commands/widget_cmd.rs`、`src-tauri/src/widget_registry.rs` | 创建官方组件时按 capability 自动授予默认权限 |
698698
| Gateway query | `src-tauri/src/widget_gateway/usage_data_broker.rs`、capability_resolver | 新增 `focus` namespace,返回 `active` + `active_session` |
699699
| SDK 测试 | `src/widgets/sdk/__tests__/WidgetClient.test.ts` | mock gateway 覆盖 query/state/subscribe/consent retry |
700+
| 官方组件全量测试 | `src/widgets/__tests__/*.test.tsx` | 覆盖 Clock/Todo/Note/Status/GoalProgress/SessionPulse/FocusCoach/Pet/QuickCapture/Timer/BrowserActivity |
700701
| 权限治理 UX | `src/pages/WidgetCenter/index.tsx` | 权限矩阵支持单个权限撤销,保留「撤销全部」入口 |
701702

702703
### 验证结果
703704

704705
- `npm run typecheck` ✅
705706
- `npm run lint` ✅(0 errors,8 pre-existing warnings)
706-
- `npm run test` ✅(22/22,含 11 个新增 WidgetClient 测试)
707+
- `npm run test` ✅(56/56,含 11 个 WidgetClient 测试 + 34 个官方组件测试)
707708
- `cargo check` ✅
708709
- `cargo test` ✅(38/38)
709710

@@ -713,7 +714,7 @@ After this plan is reviewed, the next artifact should be a narrow RFC that freez
713714
- 旧 widget 的 `widget_permissions` 被 Gateway 视为已授权,保证迁移期兼容。
714715
- `WidgetClient.fetch` 与 `loadMedia` 为占位实现,会抛出未实现错误。
715716
- 同意提示目前统一按 `low` 风险等级记录;后续应结合 capability 风险分级自动映射 `low/medium/high`。
716-
- TimerWidget、QuickCaptureWidget、BrowserActivityWidget 仍使用直接 `tauriApi` 调用,待 Gateway 暴露对应能力后再迁移。
717+
- TimerWidget、QuickCaptureWidget、BrowserActivityWidget 仍使用直接 `tauriApi` 调用,待 Gateway 暴露对应能力后再迁移;但其渲染与交互已纳入官方组件测试覆盖。
717718

718719
### 建议下一步
719720

@@ -729,6 +730,8 @@ After this plan is reviewed, the next artifact should be a narrow RFC that freez
729730

730731
## Agent 下一步计划(由当前 Kimi Code agent 制定)
731732

733+
- [x] Phase C 官方组件全量测试流程已落地。
734+
732735
基于 Phase C 已完成,当前建议转向用户此前提到的 v2.2.0 其他需求。优先级最高的未解决项包括:
733736

734737
1. **小组件中心左右栏独立滚动**:当前官方/第三方两栏共用滚动容器,需拆分布局。
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, it, expect, beforeEach } from "vitest";
2+
import { screen, waitFor } from "@testing-library/react";
3+
import { mockTauriApi, renderWithProviders, resetWidgetMocks } from "./test-utils";
4+
import BrowserActivityWidget from "../BrowserActivityWidget";
5+
6+
describe("BrowserActivityWidget", () => {
7+
beforeEach(() => {
8+
resetWidgetMocks();
9+
});
10+
11+
it("renders disconnected state", async () => {
12+
mockTauriApi.getBrowserDomainStats.mockResolvedValue([]);
13+
mockTauriApi.getBrowserExtensionStatus.mockResolvedValue({ connected: false });
14+
renderWithProviders(<BrowserActivityWidget widgetId="browser-test" />);
15+
16+
expect(screen.getByText("Browser Activity")).toBeInTheDocument();
17+
await waitFor(() => {
18+
expect(screen.getByText("Disconnected")).toBeInTheDocument();
19+
});
20+
await waitFor(() => {
21+
expect(screen.getByText("No browser data today")).toBeInTheDocument();
22+
});
23+
});
24+
25+
it("renders connected state with domains", async () => {
26+
mockTauriApi.getBrowserDomainStats.mockResolvedValue([
27+
{ host: "example.com", total_seconds: 300, visit_count: 5 },
28+
]);
29+
mockTauriApi.getBrowserExtensionStatus.mockResolvedValue({ connected: true, last_browser_name: "Chrome" });
30+
renderWithProviders(<BrowserActivityWidget widgetId="browser-test" />);
31+
32+
await waitFor(() => {
33+
expect(screen.getByText("Connected")).toBeInTheDocument();
34+
});
35+
await waitFor(() => {
36+
expect(screen.getByText("example.com")).toBeInTheDocument();
37+
});
38+
expect(screen.getByText("Chrome")).toBeInTheDocument();
39+
});
40+
});

‎src/widgets/__tests__/FocusCoachWidget.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ describe("FocusCoachWidget", () => {
2121
});
2222

2323
it("starts a focus session when clicking start", async () => {
24-
const today = new Date().toISOString().slice(0, 10);
2524
mockTauriApi.widgetGatewayRequest.mockResolvedValue(successResponse([]));
2625
mockTauriApi.startFocusSession.mockResolvedValue(42);
2726
renderWithProviders(<FocusCoachWidget widgetId="focus-test" />);

‎src/widgets/__tests__/QuickCaptureWidget.test.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe("QuickCaptureWidget", () => {
3232

3333
const textarea = screen.getByPlaceholderText("Add a quick task…");
3434
await userEvent.type(textarea, "Quick task");
35-
await userEvent.click(screen.getByRole("button", { name: "Add Todo" }));
35+
await userEvent.click(screen.getByRole("button", { name: "Add todo" }));
3636

3737
await waitFor(() => {
3838
expect(mockTauriApi.addTodo).toHaveBeenCalledWith("Quick task");

‎src/widgets/__tests__/StatusWidget.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
mockTauriApi,
66
renderWithProviders,
77
resetWidgetMocks,
8-
mockGatewayState,
98
successResponse,
109
} from "./test-utils";
1110
import StatusWidget from "../StatusWidget";
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, it, expect, beforeEach } from "vitest";
2+
import { screen, waitFor } from "@testing-library/react";
3+
import userEvent from "@testing-library/user-event";
4+
import { renderWithProviders, resetWidgetMocks } from "./test-utils";
5+
import TimerWidget from "../TimerWidget";
6+
7+
describe("TimerWidget", () => {
8+
beforeEach(() => {
9+
resetWidgetMocks();
10+
});
11+
12+
it("renders pomodoro mode by default", async () => {
13+
renderWithProviders(<TimerWidget widgetId="timer-test" />);
14+
15+
expect(screen.getByText("Timer")).toBeInTheDocument();
16+
expect(screen.getByText("Pomodoro")).toBeInTheDocument();
17+
expect(screen.getByText("25:00")).toBeInTheDocument();
18+
});
19+
20+
it("switches to countdown mode", async () => {
21+
renderWithProviders(<TimerWidget widgetId="timer-test" />);
22+
23+
await userEvent.click(screen.getByRole("button", { name: "Countdown" }));
24+
25+
await waitFor(() => {
26+
expect(screen.getByText("Countdown")).toHaveClass("bg-accent-blue");
27+
});
28+
});
29+
30+
it("toggles timer running state", async () => {
31+
renderWithProviders(<TimerWidget widgetId="timer-test" />);
32+
33+
const startButton = screen.getByRole("button", { name: "Start" });
34+
await userEvent.click(startButton);
35+
36+
await waitFor(() => {
37+
expect(screen.getByRole("button", { name: "Pause" })).toBeInTheDocument();
38+
});
39+
});
40+
});

‎src/widgets/__tests__/test-utils.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ export function mockGatewayState(initialState: Record<string, string | null>) {
199199
}
200200
if (request.scope === "state" && request.request_type === "state_write") {
201201
const key = request.payload?.key;
202+
const value = request.payload?.value;
202203
if (key) {
203-
state[key] = request.payload.value ?? null;
204+
state[key] = value ?? null;
204205
}
205206
return successResponse(null);
206207
}

0 commit comments

Comments
 (0)