Skip to content

Commit deb4df4

Browse files
committed
Fix webview tests
1 parent 27b684a commit deb4df4

File tree

6 files changed

+319
-384
lines changed

6 files changed

+319
-384
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@
157157
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
158158
"pretest": "npm run compile-tests && npm run compile && npm run lint",
159159
"start:webview": "cd webview-ui && npm run start",
160-
"test": "jest",
160+
"test": "jest && npm run test:webview",
161161
"test:webview": "cd webview-ui && npm run test",
162162
"prepare": "husky",
163163
"publish:marketplace": "vsce publish",

webview-ui/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"scripts": {
3131
"start": "react-scripts start",
3232
"build": "node ./scripts/build-react-no-split.js",
33-
"test": "react-scripts test",
33+
"test": "react-scripts test --watchAll=false",
3434
"eject": "react-scripts eject"
3535
},
3636
"eslintConfig": {
@@ -57,7 +57,7 @@
5757
},
5858
"jest": {
5959
"transformIgnorePatterns": [
60-
"/node_modules/(?!(rehype-highlight|react-remark|unist-util-visit|vfile|unified|bail|is-plain-obj|trough|vfile-message|unist-util-stringify-position|mdast-util-from-markdown|mdast-util-to-string|micromark|decode-named-character-reference|character-entities|markdown-table|zwitch|longest-streak|escape-string-regexp|unist-util-is|hast-util-to-text)/)"
60+
"/node_modules/(?!(rehype-highlight|react-remark|unist-util-visit|vfile|unified|bail|is-plain-obj|trough|vfile-message|unist-util-stringify-position|mdast-util-from-markdown|mdast-util-to-string|micromark|decode-named-character-reference|character-entities|markdown-table|zwitch|longest-streak|escape-string-regexp|unist-util-is|hast-util-to-text|@vscode/webview-ui-toolkit|@microsoft/fast-react-wrapper|@microsoft/fast-element|@microsoft/fast-foundation|@microsoft/fast-web-utilities|exenv-es6)/)"
6161
],
6262
"moduleNameMapper": {
6363
"\\.(css|less|scss|sass)$": "identity-obj-proxy"

webview-ui/src/components/history/HistoryView.tsx

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { VSCodeButton, VSCodeTextField, VSCodeRadioGroup, VSCodeRadio } from "@v
22
import { useExtensionState } from "../../context/ExtensionStateContext"
33
import { vscode } from "../../utils/vscode"
44
import { Virtuoso } from "react-virtuoso"
5-
import { memo, useMemo, useState, useEffect } from "react"
5+
import React, { memo, useMemo, useState, useEffect } from "react"
66
import Fuse, { FuseResult } from "fuse.js"
77
import { formatLargeNumber } from "../../utils/format"
88

@@ -82,30 +82,28 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
8282
const taskHistorySearchResults = useMemo(() => {
8383
let results = searchQuery ? highlight(fuse.search(searchQuery)) : presentableTasks
8484

85-
results.sort((a, b) => {
85+
// First apply search if needed
86+
const searchResults = searchQuery ? results : presentableTasks;
87+
88+
// Then sort the results
89+
return [...searchResults].sort((a, b) => {
8690
switch (sortOption) {
8791
case "oldest":
88-
return a.ts - b.ts
92+
return (a.ts || 0) - (b.ts || 0);
8993
case "mostExpensive":
90-
return (b.totalCost || 0) - (a.totalCost || 0)
94+
return (b.totalCost || 0) - (a.totalCost || 0);
9195
case "mostTokens":
92-
return (
93-
(b.tokensIn || 0) +
94-
(b.tokensOut || 0) +
95-
(b.cacheWrites || 0) +
96-
(b.cacheReads || 0) -
97-
((a.tokensIn || 0) + (a.tokensOut || 0) + (a.cacheWrites || 0) + (a.cacheReads || 0))
98-
)
96+
const aTokens = (a.tokensIn || 0) + (a.tokensOut || 0) + (a.cacheWrites || 0) + (a.cacheReads || 0);
97+
const bTokens = (b.tokensIn || 0) + (b.tokensOut || 0) + (b.cacheWrites || 0) + (b.cacheReads || 0);
98+
return bTokens - aTokens;
9999
case "mostRelevant":
100-
// NOTE: you must never sort directly on object since it will cause members to be reordered
101-
return searchQuery ? 0 : b.ts - a.ts // Keep fuse order if searching, otherwise sort by newest
100+
// Keep fuse order if searching, otherwise sort by newest
101+
return searchQuery ? 0 : (b.ts || 0) - (a.ts || 0);
102102
case "newest":
103103
default:
104-
return b.ts - a.ts
104+
return (b.ts || 0) - (a.ts || 0);
105105
}
106-
})
107-
108-
return results
106+
});
109107
}, [presentableTasks, searchQuery, fuse, sortOption])
110108

111109
return (
@@ -227,9 +225,16 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
227225
overflowY: "scroll",
228226
}}
229227
data={taskHistorySearchResults}
228+
data-testid="virtuoso-container"
229+
components={{
230+
List: React.forwardRef((props, ref) => (
231+
<div {...props} ref={ref} data-testid="virtuoso-item-list" />
232+
))
233+
}}
230234
itemContent={(index, item) => (
231235
<div
232236
key={item.id}
237+
data-testid={`task-item-${item.id}`}
233238
className="history-item"
234239
style={{
235240
cursor: "pointer",
@@ -263,23 +268,23 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
263268
{formatDate(item.ts)}
264269
</span>
265270
<div style={{ display: "flex", gap: "4px" }}>
266-
<VSCodeButton
267-
appearance="icon"
268-
title="Copy Prompt"
269-
className="copy-button"
270-
onClick={(e) => handleCopyTask(e, item.task)}>
271-
<span className="codicon codicon-copy"></span>
272-
</VSCodeButton>
273-
<VSCodeButton
274-
appearance="icon"
275-
title="Delete Task"
276-
onClick={(e) => {
277-
e.stopPropagation()
278-
handleDeleteHistoryItem(item.id)
279-
}}
280-
className="delete-button">
281-
<span className="codicon codicon-trash"></span>
282-
</VSCodeButton>
271+
<button
272+
title="Copy Prompt"
273+
className="copy-button"
274+
data-appearance="icon"
275+
onClick={(e) => handleCopyTask(e, item.task)}>
276+
<span className="codicon codicon-copy"></span>
277+
</button>
278+
<button
279+
title="Delete Task"
280+
className="delete-button"
281+
data-appearance="icon"
282+
onClick={(e) => {
283+
e.stopPropagation()
284+
handleDeleteHistoryItem(item.id)
285+
}}>
286+
<span className="codicon codicon-trash"></span>
287+
</button>
283288
</div>
284289
</div>
285290
<div
@@ -298,6 +303,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
298303
/>
299304
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
300305
<div
306+
data-testid="tokens-container"
301307
style={{
302308
display: "flex",
303309
justifyContent: "space-between",
@@ -318,6 +324,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
318324
Tokens:
319325
</span>
320326
<span
327+
data-testid="tokens-in"
321328
style={{
322329
display: "flex",
323330
alignItems: "center",
@@ -335,6 +342,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
335342
{formatLargeNumber(item.tokensIn || 0)}
336343
</span>
337344
<span
345+
data-testid="tokens-out"
338346
style={{
339347
display: "flex",
340348
alignItems: "center",
@@ -357,6 +365,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
357365

358366
{!!item.cacheWrites && (
359367
<div
368+
data-testid="cache-container"
360369
style={{
361370
display: "flex",
362371
alignItems: "center",
@@ -371,6 +380,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
371380
Cache:
372381
</span>
373382
<span
383+
data-testid="cache-writes"
374384
style={{
375385
display: "flex",
376386
alignItems: "center",
@@ -388,6 +398,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
388398
+{formatLargeNumber(item.cacheWrites || 0)}
389399
</span>
390400
<span
401+
data-testid="cache-reads"
391402
style={{
392403
display: "flex",
393404
alignItems: "center",
@@ -499,31 +510,48 @@ export const highlight = (
499510
if (regions.length === 0) {
500511
return inputText
501512
}
502-
513+
503514
// Sort and merge overlapping regions
504515
const mergedRegions = mergeRegions(regions)
505-
506-
let content = ""
507-
let nextUnhighlightedRegionStartingIndex = 0
508-
509-
mergedRegions.forEach((region) => {
510-
const start = region[0]
511-
const end = region[1]
512-
const lastRegionNextIndex = end + 1
513-
514-
content += [
515-
inputText.substring(nextUnhighlightedRegionStartingIndex, start),
516-
`<span class="${highlightClassName}">`,
517-
inputText.substring(start, lastRegionNextIndex),
518-
"</span>",
519-
].join("")
520-
521-
nextUnhighlightedRegionStartingIndex = lastRegionNextIndex
516+
517+
// Convert regions to a list of parts with their highlight status
518+
const parts: { text: string; highlight: boolean }[] = []
519+
let lastIndex = 0
520+
521+
mergedRegions.forEach(([start, end]) => {
522+
// Add non-highlighted text before this region
523+
if (start > lastIndex) {
524+
parts.push({
525+
text: inputText.substring(lastIndex, start),
526+
highlight: false
527+
})
528+
}
529+
530+
// Add highlighted text
531+
parts.push({
532+
text: inputText.substring(start, end + 1),
533+
highlight: true
534+
})
535+
536+
lastIndex = end + 1
522537
})
523-
524-
content += inputText.substring(nextUnhighlightedRegionStartingIndex)
525-
526-
return content
538+
539+
// Add any remaining text
540+
if (lastIndex < inputText.length) {
541+
parts.push({
542+
text: inputText.substring(lastIndex),
543+
highlight: false
544+
})
545+
}
546+
547+
// Build final string
548+
return parts
549+
.map(part =>
550+
part.highlight
551+
? `<span class="${highlightClassName}">${part.text}</span>`
552+
: part.text
553+
)
554+
.join('')
527555
}
528556

529557
return fuseSearchResult

0 commit comments

Comments
 (0)