Skip to content

Commit 78f92b7

Browse files
committed
expanded tests and made better mock
1 parent aeea95b commit 78f92b7

File tree

1 file changed

+175
-86
lines changed

1 file changed

+175
-86
lines changed
Lines changed: 175 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import React, { createContext, useContext } from "react"
12
import { render, screen } from "@testing-library/react"
23
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
34
import { FollowUpSuggest } from "../FollowUpSuggest"
4-
import { ExtensionStateContext, ExtensionStateContextType } from "@src/context/ExtensionStateContext"
55
import { TooltipProvider } from "@radix-ui/react-tooltip"
66

77
// Mock the translation hook
@@ -23,87 +23,40 @@ vi.mock("@src/i18n/TranslationContext", () => ({
2323
}),
2424
}))
2525

26-
// Mock the extension state
27-
const createMockExtensionState = (overrides?: Partial<ExtensionStateContextType>): ExtensionStateContextType =>
28-
({
29-
version: "1.0.0",
30-
clineMessages: [],
31-
taskHistory: [],
32-
shouldShowAnnouncement: false,
33-
allowedCommands: [],
34-
soundEnabled: false,
35-
soundVolume: 0.5,
36-
ttsEnabled: false,
37-
ttsSpeed: 1.0,
38-
diffEnabled: false,
39-
enableCheckpoints: true,
40-
fuzzyMatchThreshold: 1.0,
41-
language: "en",
42-
writeDelayMs: 1000,
43-
browserViewportSize: "900x600",
44-
screenshotQuality: 75,
45-
terminalOutputLineLimit: 500,
46-
terminalShellIntegrationTimeout: 4000,
47-
mcpEnabled: true,
48-
enableMcpServerCreation: false,
49-
alwaysApproveResubmit: false,
50-
requestDelaySeconds: 5,
51-
currentApiConfigName: "default",
52-
listApiConfigMeta: [],
53-
mode: "code",
54-
customModePrompts: {},
55-
customSupportPrompts: {},
56-
experiments: {},
57-
enhancementApiConfigId: "",
58-
condensingApiConfigId: "",
59-
customCondensingPrompt: "",
60-
hasOpenedModeSelector: false,
61-
autoApprovalEnabled: true,
62-
alwaysAllowFollowupQuestions: true,
63-
followupAutoApproveTimeoutMs: 3000, // 3 seconds for testing
64-
customModes: [],
65-
maxOpenTabsContext: 20,
66-
maxWorkspaceFiles: 200,
67-
cwd: "",
68-
browserToolEnabled: true,
69-
telemetrySetting: "unset",
70-
showRooIgnoredFiles: true,
71-
renderContext: "sidebar",
72-
maxReadFileLine: -1,
73-
pinnedApiConfigs: {},
74-
didHydrateState: true,
75-
showWelcome: false,
76-
theme: {},
77-
mcpServers: [],
78-
filePaths: [],
79-
openedTabs: [],
80-
organizationAllowList: { type: "all" },
81-
cloudIsAuthenticated: false,
82-
sharingEnabled: false,
83-
mdmCompliant: true,
84-
autoCondenseContext: false,
85-
autoCondenseContextPercent: 50,
86-
setHasOpenedModeSelector: vi.fn(),
87-
setAlwaysAllowFollowupQuestions: vi.fn(),
88-
setFollowupAutoApproveTimeoutMs: vi.fn(),
89-
setCondensingApiConfigId: vi.fn(),
90-
setCustomCondensingPrompt: vi.fn(),
91-
setPinnedApiConfigs: vi.fn(),
92-
togglePinnedApiConfig: vi.fn(),
93-
setTerminalCompressProgressBar: vi.fn(),
94-
setHistoryPreviewCollapsed: vi.fn(),
95-
setAutoCondenseContext: vi.fn(),
96-
setAutoCondenseContextPercent: vi.fn(),
97-
...overrides,
98-
}) as ExtensionStateContextType
99-
100-
const renderWithProviders = (component: React.ReactElement, stateOverrides?: Partial<ExtensionStateContextType>) => {
101-
const mockState = createMockExtensionState(stateOverrides)
26+
// Test-specific extension state context that only provides the values needed by FollowUpSuggest
27+
interface TestExtensionState {
28+
autoApprovalEnabled: boolean
29+
alwaysAllowFollowupQuestions: boolean
30+
followupAutoApproveTimeoutMs: number
31+
}
32+
33+
const TestExtensionStateContext = createContext<TestExtensionState | undefined>(undefined)
10234

35+
// Mock the useExtensionState hook to use our test context
36+
vi.mock("@src/context/ExtensionStateContext", () => ({
37+
useExtensionState: () => {
38+
const context = useContext(TestExtensionStateContext)
39+
if (!context) {
40+
throw new Error("useExtensionState must be used within TestExtensionStateProvider")
41+
}
42+
return context
43+
},
44+
}))
45+
46+
// Test provider that only provides the specific values needed by FollowUpSuggest
47+
const TestExtensionStateProvider: React.FC<{
48+
children: React.ReactNode
49+
value: TestExtensionState
50+
}> = ({ children, value }) => {
51+
return <TestExtensionStateContext.Provider value={value}>{children}</TestExtensionStateContext.Provider>
52+
}
53+
54+
// Helper function to render component with test providers
55+
const renderWithTestProviders = (component: React.ReactElement, extensionState: TestExtensionState) => {
10356
return render(
104-
<ExtensionStateContext.Provider value={mockState}>
57+
<TestExtensionStateProvider value={extensionState}>
10558
<TooltipProvider>{component}</TooltipProvider>
106-
</ExtensionStateContext.Provider>,
59+
</TestExtensionStateProvider>,
10760
)
10861
}
10962

@@ -113,6 +66,13 @@ describe("FollowUpSuggest", () => {
11366
const mockOnSuggestionClick = vi.fn()
11467
const mockOnUnmount = vi.fn()
11568

69+
// Default test state with auto-approval enabled
70+
const defaultTestState: TestExtensionState = {
71+
autoApprovalEnabled: true,
72+
alwaysAllowFollowupQuestions: true,
73+
followupAutoApproveTimeoutMs: 3000, // 3 seconds for testing
74+
}
75+
11676
beforeEach(() => {
11777
vi.clearAllMocks()
11878
vi.useFakeTimers()
@@ -123,42 +83,45 @@ describe("FollowUpSuggest", () => {
12383
})
12484

12585
it("should display countdown timer when auto-approval is enabled", () => {
126-
renderWithProviders(
86+
renderWithTestProviders(
12787
<FollowUpSuggest
12888
suggestions={mockSuggestions}
12989
onSuggestionClick={mockOnSuggestionClick}
13090
ts={123}
13191
onUnmount={mockOnUnmount}
13292
/>,
93+
defaultTestState,
13394
)
13495

13596
// Should show initial countdown (3 seconds)
13697
expect(screen.getByText(/3s/)).toBeInTheDocument()
13798
})
13899

139100
it("should not display countdown timer when isAnswered is true", () => {
140-
renderWithProviders(
101+
renderWithTestProviders(
141102
<FollowUpSuggest
142103
suggestions={mockSuggestions}
143104
onSuggestionClick={mockOnSuggestionClick}
144105
ts={123}
145106
onUnmount={mockOnUnmount}
146107
isAnswered={true}
147108
/>,
109+
defaultTestState,
148110
)
149111

150112
// Should not show countdown
151113
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
152114
})
153115

154116
it("should clear interval and call onUnmount when component unmounts", () => {
155-
const { unmount } = renderWithProviders(
117+
const { unmount } = renderWithTestProviders(
156118
<FollowUpSuggest
157119
suggestions={mockSuggestions}
158120
onSuggestionClick={mockOnSuggestionClick}
159121
ts={123}
160122
onUnmount={mockOnUnmount}
161123
/>,
124+
defaultTestState,
162125
)
163126

164127
// Unmount the component
@@ -169,32 +132,158 @@ describe("FollowUpSuggest", () => {
169132
})
170133

171134
it("should not show countdown when auto-approval is disabled", () => {
172-
renderWithProviders(
135+
const testState: TestExtensionState = {
136+
...defaultTestState,
137+
autoApprovalEnabled: false,
138+
}
139+
140+
renderWithTestProviders(
173141
<FollowUpSuggest
174142
suggestions={mockSuggestions}
175143
onSuggestionClick={mockOnSuggestionClick}
176144
ts={123}
177145
onUnmount={mockOnUnmount}
178146
/>,
179-
{ autoApprovalEnabled: false },
147+
testState,
180148
)
181149

182150
// Should not show countdown
183151
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
184152
})
185153

186154
it("should not show countdown when alwaysAllowFollowupQuestions is false", () => {
187-
renderWithProviders(
155+
const testState: TestExtensionState = {
156+
...defaultTestState,
157+
alwaysAllowFollowupQuestions: false,
158+
}
159+
160+
renderWithTestProviders(
161+
<FollowUpSuggest
162+
suggestions={mockSuggestions}
163+
onSuggestionClick={mockOnSuggestionClick}
164+
ts={123}
165+
onUnmount={mockOnUnmount}
166+
/>,
167+
testState,
168+
)
169+
170+
// Should not show countdown
171+
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
172+
})
173+
174+
it("should use custom timeout value from extension state", () => {
175+
const testState: TestExtensionState = {
176+
...defaultTestState,
177+
followupAutoApproveTimeoutMs: 5000, // 5 seconds
178+
}
179+
180+
renderWithTestProviders(
181+
<FollowUpSuggest
182+
suggestions={mockSuggestions}
183+
onSuggestionClick={mockOnSuggestionClick}
184+
ts={123}
185+
onUnmount={mockOnUnmount}
186+
/>,
187+
testState,
188+
)
189+
190+
// Should show initial countdown (5 seconds)
191+
expect(screen.getByText(/5s/)).toBeInTheDocument()
192+
})
193+
194+
it("should render suggestions without countdown when both auto-approval settings are disabled", () => {
195+
const testState: TestExtensionState = {
196+
autoApprovalEnabled: false,
197+
alwaysAllowFollowupQuestions: false,
198+
followupAutoApproveTimeoutMs: 3000,
199+
}
200+
201+
renderWithTestProviders(
188202
<FollowUpSuggest
189203
suggestions={mockSuggestions}
190204
onSuggestionClick={mockOnSuggestionClick}
191205
ts={123}
192206
onUnmount={mockOnUnmount}
193207
/>,
194-
{ alwaysAllowFollowupQuestions: false },
208+
testState,
195209
)
196210

211+
// Should render suggestions
212+
expect(screen.getByText("First suggestion")).toBeInTheDocument()
213+
expect(screen.getByText("Second suggestion")).toBeInTheDocument()
214+
197215
// Should not show countdown
198216
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
199217
})
218+
219+
it("should not render when no suggestions are provided", () => {
220+
const { container } = renderWithTestProviders(
221+
<FollowUpSuggest
222+
suggestions={[]}
223+
onSuggestionClick={mockOnSuggestionClick}
224+
ts={123}
225+
onUnmount={mockOnUnmount}
226+
/>,
227+
defaultTestState,
228+
)
229+
230+
// Component should not render anything
231+
expect(container.firstChild).toBeNull()
232+
})
233+
234+
it("should not render when onSuggestionClick is not provided", () => {
235+
const { container } = renderWithTestProviders(
236+
<FollowUpSuggest suggestions={mockSuggestions} ts={123} onUnmount={mockOnUnmount} />,
237+
defaultTestState,
238+
)
239+
240+
// Component should not render anything
241+
expect(container.firstChild).toBeNull()
242+
})
243+
244+
it("should stop countdown when user manually responds (isAnswered becomes true)", () => {
245+
const { rerender } = renderWithTestProviders(
246+
<FollowUpSuggest
247+
suggestions={mockSuggestions}
248+
onSuggestionClick={mockOnSuggestionClick}
249+
ts={123}
250+
onUnmount={mockOnUnmount}
251+
isAnswered={false}
252+
/>,
253+
defaultTestState,
254+
)
255+
256+
// Initially should show countdown
257+
expect(screen.getByText(/3s/)).toBeInTheDocument()
258+
259+
// Simulate user manually responding by setting isAnswered to true
260+
rerender(
261+
<TestExtensionStateProvider value={defaultTestState}>
262+
<TooltipProvider>
263+
<FollowUpSuggest
264+
suggestions={mockSuggestions}
265+
onSuggestionClick={mockOnSuggestionClick}
266+
ts={123}
267+
onUnmount={mockOnUnmount}
268+
isAnswered={true}
269+
/>
270+
</TooltipProvider>
271+
</TestExtensionStateProvider>,
272+
)
273+
274+
// Countdown should no longer be visible immediately after isAnswered becomes true
275+
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
276+
277+
// Advance timer to ensure countdown doesn't restart or continue
278+
vi.advanceTimersByTime(5000)
279+
280+
// onSuggestionClick should not have been called (auto-selection stopped)
281+
expect(mockOnSuggestionClick).not.toHaveBeenCalled()
282+
283+
// Countdown should still not be visible
284+
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
285+
286+
// Verify onUnmount was called when the countdown was stopped
287+
expect(mockOnUnmount).toHaveBeenCalled()
288+
})
200289
})

0 commit comments

Comments
 (0)