Skip to content

Commit 86d9722

Browse files
Add unit tests for new functionality
1 parent e230a03 commit 86d9722

File tree

2 files changed

+221
-17
lines changed

2 files changed

+221
-17
lines changed

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
493493
case "action":
494494
switch (message.action!) {
495495
case "didBecomeVisible":
496-
console.log("Set view hidden = false")
497496
setViewHidden(false)
498497
break
499498
case "didBecomeInvisible":
500-
console.log("Set view hidden = true")
501499
setViewHidden(true)
502500
break
503501
}

webview-ui/src/components/chat/__tests__/ChatView.test.tsx

Lines changed: 221 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react"
2-
import { render, waitFor, act } from "@testing-library/react"
2+
import { render, waitFor, act, fireEvent } from "@testing-library/react"
33
import ChatView from "../ChatView"
44
import { ExtensionStateContextProvider } from "../../../context/ExtensionStateContext"
55
import { vscode } from "../../../utils/vscode"
@@ -60,25 +60,34 @@ interface ChatTextAreaProps {
6060
shouldDisableImages?: boolean
6161
}
6262

63-
const mockInputRef = React.createRef<HTMLInputElement>()
63+
interface ChatTextAreaHandle {
64+
focus: () => void
65+
current: HTMLInputElement | null
66+
}
67+
6468
const mockFocus = jest.fn()
6569

6670
jest.mock("../ChatTextArea", () => {
6771
const mockReact = require("react")
72+
6873
return {
6974
__esModule: true,
7075
default: mockReact.forwardRef(function MockChatTextArea(
7176
props: ChatTextAreaProps,
72-
ref: React.ForwardedRef<{ focus: () => void }>,
77+
ref: React.ForwardedRef<ChatTextAreaHandle>,
7378
) {
74-
// Use useImperativeHandle to expose the mock focus method
75-
React.useImperativeHandle(ref, () => ({
76-
focus: mockFocus,
77-
}))
79+
const inputRef = mockReact.useRef(null)
80+
81+
mockReact.useImperativeHandle(ref, () => {
82+
if (inputRef.current) {
83+
inputRef.current.focus = mockFocus
84+
}
85+
return inputRef.current
86+
})
7887

7988
return (
8089
<div data-testid="chat-textarea">
81-
<input ref={mockInputRef} type="text" onChange={(e) => props.onSend(e.target.value)} />
90+
<input ref={inputRef} type="text" onChange={(e) => props.onSend(e.target.value)} />
8291
</div>
8392
)
8493
}),
@@ -151,6 +160,10 @@ const mockPostMessage = (state: Partial<ExtensionState>) => {
151160
)
152161
}
153162

163+
const sleep = (timeout: number) => {
164+
return act(() => new Promise((resolve) => setTimeout(resolve, timeout)))
165+
}
166+
154167
describe("ChatView - Auto Approval Tests", () => {
155168
beforeEach(() => {
156169
jest.clearAllMocks()
@@ -1102,12 +1115,6 @@ describe("ChatView - Focus Grabbing Tests", () => {
11021115
})
11031116

11041117
it("does not grab focus when follow-up question presented", async () => {
1105-
const sleep = async (timeout: number) => {
1106-
await act(async () => {
1107-
await new Promise((resolve) => setTimeout(resolve, timeout))
1108-
})
1109-
}
1110-
11111118
render(
11121119
<ExtensionStateContextProvider>
11131120
<ChatView
@@ -1145,7 +1152,7 @@ describe("ChatView - Focus Grabbing Tests", () => {
11451152
// wait for focus updates (can take 50msecs)
11461153
await sleep(100)
11471154

1148-
const FOCUS_CALLS_ON_INIT = 2
1155+
const FOCUS_CALLS_ON_INIT = 0
11491156
expect(mockFocus).toHaveBeenCalledTimes(FOCUS_CALLS_ON_INIT)
11501157

11511158
// Finish task, and send the followup ask message (streaming unfinished)
@@ -1195,4 +1202,203 @@ describe("ChatView - Focus Grabbing Tests", () => {
11951202
// focus() should not have been called again
11961203
expect(mockFocus).toHaveBeenCalledTimes(FOCUS_CALLS_ON_INIT)
11971204
})
1205+
1206+
it("grabs focus to restore focus when unhidden", async () => {
1207+
const { getByTestId, rerender } = render(
1208+
<ExtensionStateContextProvider>
1209+
<ChatView
1210+
isHidden={false}
1211+
showAnnouncement={false}
1212+
hideAnnouncement={() => {}}
1213+
showHistoryView={() => {}}
1214+
/>
1215+
</ExtensionStateContextProvider>,
1216+
)
1217+
1218+
const textAreaElement = getByTestId("chat-textarea").querySelector("input")
1219+
1220+
// simulate focus on textArea
1221+
if (textAreaElement) {
1222+
fireEvent.focus(textAreaElement)
1223+
}
1224+
1225+
await waitFor(() => {
1226+
expect(mockFocus).toHaveBeenCalledTimes(1)
1227+
})
1228+
1229+
rerender(
1230+
<ExtensionStateContextProvider>
1231+
<ChatView
1232+
isHidden={true}
1233+
showAnnouncement={false}
1234+
hideAnnouncement={() => {}}
1235+
showHistoryView={() => {}}
1236+
/>
1237+
</ExtensionStateContextProvider>,
1238+
)
1239+
1240+
expect(mockFocus).toHaveBeenCalledTimes(1)
1241+
1242+
rerender(
1243+
<ExtensionStateContextProvider>
1244+
<ChatView
1245+
isHidden={false}
1246+
showAnnouncement={false}
1247+
hideAnnouncement={() => {}}
1248+
showHistoryView={() => {}}
1249+
/>
1250+
</ExtensionStateContextProvider>,
1251+
)
1252+
1253+
await waitFor(() => {
1254+
expect(mockFocus).toHaveBeenCalledTimes(2)
1255+
})
1256+
})
1257+
1258+
it("does not grab focus when unhidden and not previously focused", async () => {
1259+
const { rerender } = render(
1260+
<ExtensionStateContextProvider>
1261+
<ChatView
1262+
isHidden={false}
1263+
showAnnouncement={false}
1264+
hideAnnouncement={() => {}}
1265+
showHistoryView={() => {}}
1266+
/>
1267+
</ExtensionStateContextProvider>,
1268+
)
1269+
1270+
rerender(
1271+
<ExtensionStateContextProvider>
1272+
<ChatView
1273+
isHidden={true}
1274+
showAnnouncement={false}
1275+
hideAnnouncement={() => {}}
1276+
showHistoryView={() => {}}
1277+
/>
1278+
</ExtensionStateContextProvider>,
1279+
)
1280+
1281+
rerender(
1282+
<ExtensionStateContextProvider>
1283+
<ChatView
1284+
isHidden={false}
1285+
showAnnouncement={false}
1286+
hideAnnouncement={() => {}}
1287+
showHistoryView={() => {}}
1288+
/>
1289+
</ExtensionStateContextProvider>,
1290+
)
1291+
1292+
await sleep(100)
1293+
expect(mockFocus).toHaveBeenCalledTimes(0)
1294+
})
1295+
1296+
const sendActionMessage = (action: string) => {
1297+
return act(() => {
1298+
const message = {
1299+
type: "action",
1300+
action,
1301+
}
1302+
window.postMessage(message, "*")
1303+
})
1304+
}
1305+
1306+
it("grabs focus to restore focus when extension goes invisible and becomes visible again", async () => {
1307+
const { findByTestId } = render(
1308+
<ExtensionStateContextProvider>
1309+
<ChatView
1310+
isHidden={false}
1311+
showAnnouncement={false}
1312+
hideAnnouncement={() => {}}
1313+
showHistoryView={() => {}}
1314+
/>
1315+
</ExtensionStateContextProvider>,
1316+
)
1317+
1318+
const textAreaElement = (await findByTestId("chat-textarea")).querySelector("input")
1319+
1320+
// simulate focus on textArea
1321+
if (textAreaElement) {
1322+
fireEvent.focus(textAreaElement)
1323+
}
1324+
1325+
await waitFor(() => {
1326+
expect(mockFocus).toHaveBeenCalledTimes(1)
1327+
})
1328+
1329+
await sendActionMessage("didBecomeInvisible")
1330+
expect(mockFocus).toHaveBeenCalledTimes(1)
1331+
1332+
// we need processing of message to complete before toggling visibility again.
1333+
await sleep(0)
1334+
1335+
await sendActionMessage("didBecomeVisible")
1336+
await waitFor(() => {
1337+
expect(mockFocus).toHaveBeenCalledTimes(2)
1338+
})
1339+
})
1340+
1341+
it("does not grab focus when unhidden and not previously focused", async () => {
1342+
render(
1343+
<ExtensionStateContextProvider>
1344+
<ChatView
1345+
isHidden={false}
1346+
showAnnouncement={false}
1347+
hideAnnouncement={() => {}}
1348+
showHistoryView={() => {}}
1349+
/>
1350+
</ExtensionStateContextProvider>,
1351+
)
1352+
1353+
await sendActionMessage("didBecomeInvisible")
1354+
expect(mockFocus).toHaveBeenCalledTimes(0)
1355+
1356+
// we need processing of message to complete before toggling visibility again.
1357+
await sleep(0)
1358+
1359+
await sendActionMessage("didBecomeVisible")
1360+
1361+
await sleep(100)
1362+
expect(mockFocus).toHaveBeenCalledTimes(0)
1363+
})
1364+
1365+
it("does not grab focus when extension becomes visible when previously focused then blurred", async () => {
1366+
const { findByTestId } = render(
1367+
<ExtensionStateContextProvider>
1368+
<ChatView
1369+
isHidden={false}
1370+
showAnnouncement={false}
1371+
hideAnnouncement={() => {}}
1372+
showHistoryView={() => {}}
1373+
/>
1374+
</ExtensionStateContextProvider>,
1375+
)
1376+
1377+
const textAreaElement = (await findByTestId("chat-textarea")).querySelector("input")
1378+
1379+
// simulate focus on textArea
1380+
if (textAreaElement) {
1381+
fireEvent.focus(textAreaElement)
1382+
}
1383+
1384+
await waitFor(() => {
1385+
expect(mockFocus).toHaveBeenCalledTimes(1)
1386+
})
1387+
1388+
// simulate blur on textArea
1389+
if (textAreaElement) {
1390+
fireEvent.blur(textAreaElement)
1391+
}
1392+
1393+
expect(mockFocus).toHaveBeenCalledTimes(1)
1394+
1395+
await sendActionMessage("didBecomeInvisible")
1396+
1397+
expect(mockFocus).toHaveBeenCalledTimes(1)
1398+
1399+
await sendActionMessage("didBecomeVisible")
1400+
1401+
await sleep(100)
1402+
expect(mockFocus).toHaveBeenCalledTimes(1)
1403+
})
11981404
})

0 commit comments

Comments
 (0)