Skip to content

Commit e58837b

Browse files
test: raise coverage and enforce global 90% thresholds (#219)
Add focused hook/component coverage tests and expand settings coverage for migration/shortcut branches. Switch Vitest coverage enforcement to global 90% across statements, branches, functions, and lines. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 28d9014 commit e58837b

9 files changed

Lines changed: 1101 additions & 5 deletions
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { fireEvent, render, screen } from "@testing-library/react"
2+
import { beforeEach, describe, expect, it, vi } from "vitest"
3+
4+
const { overviewPageMock, providerDetailPageMock, settingsPageMock } = vi.hoisted(() => ({
5+
overviewPageMock: vi.fn(),
6+
settingsPageMock: vi.fn(),
7+
providerDetailPageMock: vi.fn(),
8+
}))
9+
10+
vi.mock("@/pages/overview", () => ({
11+
OverviewPage: (props: unknown) => {
12+
overviewPageMock(props)
13+
return <div data-testid="overview-page" />
14+
},
15+
}))
16+
17+
vi.mock("@/pages/settings", () => ({
18+
SettingsPage: (props: unknown) => {
19+
settingsPageMock(props)
20+
return <div data-testid="settings-page" />
21+
},
22+
}))
23+
24+
vi.mock("@/pages/provider-detail", () => ({
25+
ProviderDetailPage: (props: { onRetry?: () => void }) => {
26+
providerDetailPageMock(props)
27+
return (
28+
<div data-testid="provider-detail-page">
29+
{props.onRetry ? <button onClick={props.onRetry}>retry-provider</button> : null}
30+
</div>
31+
)
32+
},
33+
}))
34+
35+
import { AppContent, type AppContentProps } from "@/components/app/app-content"
36+
import { useAppPreferencesStore } from "@/stores/app-preferences-store"
37+
import { useAppUiStore } from "@/stores/app-ui-store"
38+
39+
function createProps(): AppContentProps {
40+
return {
41+
displayPlugins: [],
42+
settingsPlugins: [],
43+
selectedPlugin: {
44+
meta: {
45+
id: "codex",
46+
name: "Codex",
47+
iconUrl: "/codex.svg",
48+
brandColor: "#000000",
49+
lines: [],
50+
primaryCandidates: [],
51+
},
52+
data: null,
53+
loading: false,
54+
error: null,
55+
lastManualRefreshAt: null,
56+
},
57+
onRetryPlugin: vi.fn(),
58+
onReorder: vi.fn(),
59+
onToggle: vi.fn(),
60+
onAutoUpdateIntervalChange: vi.fn(),
61+
onThemeModeChange: vi.fn(),
62+
onDisplayModeChange: vi.fn(),
63+
onResetTimerDisplayModeChange: vi.fn(),
64+
onResetTimerDisplayModeToggle: vi.fn(),
65+
onGlobalShortcutChange: vi.fn(),
66+
onStartOnLoginChange: vi.fn(),
67+
}
68+
}
69+
70+
describe("AppContent", () => {
71+
beforeEach(() => {
72+
overviewPageMock.mockReset()
73+
settingsPageMock.mockReset()
74+
providerDetailPageMock.mockReset()
75+
useAppUiStore.getState().resetState()
76+
useAppPreferencesStore.getState().resetState()
77+
})
78+
79+
it("renders overview page for home view", () => {
80+
useAppUiStore.getState().setActiveView("home")
81+
render(<AppContent {...createProps()} />)
82+
83+
expect(screen.getByTestId("overview-page")).toBeInTheDocument()
84+
expect(overviewPageMock).toHaveBeenCalledTimes(1)
85+
})
86+
87+
it("renders settings page for settings view", () => {
88+
useAppUiStore.getState().setActiveView("settings")
89+
render(<AppContent {...createProps()} />)
90+
91+
expect(screen.getByTestId("settings-page")).toBeInTheDocument()
92+
expect(settingsPageMock).toHaveBeenCalledTimes(1)
93+
})
94+
95+
it("passes retry callback for provider detail view", () => {
96+
const props = createProps()
97+
useAppUiStore.getState().setActiveView("codex")
98+
render(<AppContent {...props} />)
99+
100+
fireEvent.click(screen.getByRole("button", { name: "retry-provider" }))
101+
102+
expect(providerDetailPageMock).toHaveBeenCalledTimes(1)
103+
expect(props.onRetryPlugin).toHaveBeenCalledWith("codex")
104+
})
105+
})

src/hooks/app/use-panel.test.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { act, renderHook, waitFor } from "@testing-library/react"
2+
import { beforeEach, describe, expect, it, vi } from "vitest"
3+
4+
const {
5+
currentMonitorMock,
6+
getCurrentWindowMock,
7+
invokeMock,
8+
isTauriMock,
9+
listenMock,
10+
} = vi.hoisted(() => ({
11+
invokeMock: vi.fn(),
12+
isTauriMock: vi.fn(),
13+
listenMock: vi.fn(),
14+
getCurrentWindowMock: vi.fn(),
15+
currentMonitorMock: vi.fn(),
16+
}))
17+
18+
vi.mock("@tauri-apps/api/core", () => ({
19+
invoke: invokeMock,
20+
isTauri: isTauriMock,
21+
}))
22+
23+
vi.mock("@tauri-apps/api/event", () => ({
24+
listen: listenMock,
25+
}))
26+
27+
vi.mock("@tauri-apps/api/window", () => ({
28+
getCurrentWindow: getCurrentWindowMock,
29+
currentMonitor: currentMonitorMock,
30+
PhysicalSize: class PhysicalSize {
31+
width: number
32+
height: number
33+
34+
constructor(width: number, height: number) {
35+
this.width = width
36+
this.height = height
37+
}
38+
},
39+
}))
40+
41+
import { usePanel } from "@/hooks/app/use-panel"
42+
43+
describe("usePanel", () => {
44+
beforeEach(() => {
45+
invokeMock.mockReset()
46+
isTauriMock.mockReset()
47+
listenMock.mockReset()
48+
getCurrentWindowMock.mockReset()
49+
currentMonitorMock.mockReset()
50+
51+
isTauriMock.mockReturnValue(true)
52+
invokeMock.mockResolvedValue(undefined)
53+
currentMonitorMock.mockResolvedValue(null)
54+
getCurrentWindowMock.mockReturnValue({ setSize: vi.fn().mockResolvedValue(undefined) })
55+
})
56+
57+
it("handles tray show-about event", async () => {
58+
const setShowAbout = vi.fn()
59+
const callbacks = new Map<string, (event: { payload: unknown }) => void>()
60+
61+
listenMock.mockImplementation(async (event: string, callback: (event: { payload: unknown }) => void) => {
62+
callbacks.set(event, callback)
63+
return vi.fn()
64+
})
65+
66+
renderHook(() =>
67+
usePanel({
68+
activeView: "home",
69+
setActiveView: vi.fn(),
70+
showAbout: false,
71+
setShowAbout,
72+
displayPlugins: [],
73+
})
74+
)
75+
76+
await waitFor(() => {
77+
expect(listenMock).toHaveBeenCalledTimes(2)
78+
})
79+
80+
act(() => {
81+
callbacks.get("tray:show-about")?.({ payload: null })
82+
})
83+
84+
expect(setShowAbout).toHaveBeenCalledWith(true)
85+
})
86+
87+
it("cleans first listener if hook unmounts before setup resolves", async () => {
88+
const unlistenNavigate = vi.fn()
89+
let resolveNavigate: ((value: () => void) => void) | null = null
90+
91+
listenMock
92+
.mockImplementationOnce(
93+
() =>
94+
new Promise((resolve) => {
95+
resolveNavigate = resolve
96+
})
97+
)
98+
.mockResolvedValue(vi.fn())
99+
100+
const { unmount } = renderHook(() =>
101+
usePanel({
102+
activeView: "home",
103+
setActiveView: vi.fn(),
104+
showAbout: false,
105+
setShowAbout: vi.fn(),
106+
displayPlugins: [],
107+
})
108+
)
109+
110+
unmount()
111+
resolveNavigate?.(unlistenNavigate)
112+
113+
await waitFor(() => {
114+
expect(unlistenNavigate).toHaveBeenCalledTimes(1)
115+
})
116+
})
117+
118+
it("cleans second listener if hook unmounts between listener registrations", async () => {
119+
const unlistenNavigate = vi.fn()
120+
const unlistenShowAbout = vi.fn()
121+
let resolveShowAbout: ((value: () => void) => void) | null = null
122+
123+
listenMock
124+
.mockResolvedValueOnce(unlistenNavigate)
125+
.mockImplementationOnce(
126+
() =>
127+
new Promise((resolve) => {
128+
resolveShowAbout = resolve
129+
})
130+
)
131+
132+
const { unmount } = renderHook(() =>
133+
usePanel({
134+
activeView: "home",
135+
setActiveView: vi.fn(),
136+
showAbout: false,
137+
setShowAbout: vi.fn(),
138+
displayPlugins: [],
139+
})
140+
)
141+
142+
await waitFor(() => {
143+
expect(listenMock).toHaveBeenCalledTimes(2)
144+
})
145+
146+
unmount()
147+
resolveShowAbout?.(unlistenShowAbout)
148+
149+
await waitFor(() => {
150+
expect(unlistenShowAbout).toHaveBeenCalledTimes(1)
151+
})
152+
})
153+
})

0 commit comments

Comments
 (0)