Skip to content

Commit c83e96c

Browse files
test(editor)(#1918 u161): pin the finalizeSession claim window through reset's own tab closure
Two cancellations joining one teardown: the row at src/integrations/editor/DiffViewProvider.ts asks that a caller arriving while a finalization is running wait for it instead of reading the claim. The case landed earlier reaches finalizeSession with a stand-in callback. This one runs the real reset(), so the claim is set by the method that sets it and the deferred step is the tab closure reset() awaits. Measured: at this head the spec reports Tests 176 passed (176); with only the order of the two checks in finalizeSession reverted it reports 2 failed | 174 passed (176), naming this case among them. One src test file and five webview test files also aim cases at 26 of the 54 non-killed mutants recorded in this lane's held report u148-reports-p1: 24 are killed when the recorded mutation is applied to the source and the named spec is run; 2 (the format default auto) pass under the mutation because the switch's default arm answers both spellings. Nothing outside test paths changed: 1 modified spec and 5 new specs, and the lint prune ran with src/eslint-suppressions.json restored to its head blob afterwards.
1 parent 7f600ee commit c83e96c

6 files changed

Lines changed: 414 additions & 0 deletions

File tree

‎src/integrations/editor/__tests__/DiffViewProvider.spec.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4818,6 +4818,40 @@ describe("DiffViewProvider", () => {
48184818
expect(events).toEqual(["teardown-start", "teardown-end", "joiner-resolved"])
48194819
expect(diffViewProvider["sessionFinalizationClaimed"]).toBe(true)
48204820
})
4821+
4822+
// The finding's own interleaving, run through the real reset() rather than a stand-in callback:
4823+
// reset() sets sessionFinalizationClaimed in its synchronous prefix and then awaits the tab
4824+
// closure (closeOwnDiffView below). A second caller that arrives inside that window has to wait
4825+
// for the closure the first caller started, and it must not run a second tab close of its own.
4826+
it("keeps a caller that joined the claim window waiting for reset's own tab closure", async () => {
4827+
const events: string[] = []
4828+
let releaseTabClose: () => void = () => {}
4829+
const tabCloseHeld = new Promise<void>((resolve) => {
4830+
releaseTabClose = resolve
4831+
})
4832+
4833+
diffViewProvider["relPath"] = "mock-target-file.ts"
4834+
diffViewProvider["closeOwnDiffView"] = vi.fn(async () => {
4835+
events.push("tab-close-start")
4836+
await tabCloseHeld
4837+
events.push("tab-close-end")
4838+
})
4839+
4840+
const owner = diffViewProvider["finalizeSession"](() => diffViewProvider.reset())
4841+
const joiner = diffViewProvider["finalizeSession"](() => {
4842+
events.push("joiner-ran-reset")
4843+
return diffViewProvider.reset()
4844+
}).then(() => {
4845+
events.push("joiner-resolved")
4846+
})
4847+
4848+
releaseTabClose()
4849+
await Promise.all([owner, joiner])
4850+
4851+
expect(events).toEqual(["tab-close-start", "tab-close-end", "joiner-resolved"])
4852+
expect(diffViewProvider["closeOwnDiffView"]).toHaveBeenCalledTimes(1)
4853+
expect(diffViewProvider["sessionFinalizationClaimed"]).toBe(true)
4854+
})
48214855
})
48224856

48234857
describe("userTouchedDiffEditor keep/close behavior", () => {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { render } from "@/utils/test-utils"
2+
3+
import CodebaseSearchResult from "../CodebaseSearchResult"
4+
5+
vi.mock("@/utils/vscode", () => ({ vscode: { postMessage: vi.fn() } }))
6+
7+
// The double only answers the key; the recorded call keeps both arguments so the case can assert on
8+
// the interpolation object the tooltip asks for. `_options` is unused by the double on purpose.
9+
const { tSpy } = vi.hoisted(() => ({ tSpy: vi.fn((key: string, _options?: Record<string, unknown>) => key) }))
10+
11+
vi.mock("@src/i18n/TranslationContext", () => ({
12+
useAppTranslation: () => ({ t: (key: string, options?: Record<string, unknown>) => tSpy(key, options) }),
13+
}))
14+
15+
describe("CodebaseSearchResult - the tooltip it asks the translator for", () => {
16+
it("asks for the result tooltip with the score rounded to three places", () => {
17+
render(
18+
<CodebaseSearchResult
19+
filePath="src/example.ts"
20+
score={0.9512}
21+
startLine={12}
22+
endLine={18}
23+
snippet="const example = true"
24+
language="typescript"
25+
/>,
26+
)
27+
28+
expect(tSpy).toHaveBeenCalledWith("chat:codebaseSearch.resultTooltip", { score: "0.951" })
29+
})
30+
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { render, screen } from "@/utils/test-utils"
2+
import type { QueuedMessage } from "@roo-code/types"
3+
4+
import { QueuedMessages } from "../QueuedMessages"
5+
6+
vi.mock("../common/Thumbnails", () => ({ default: () => null }))
7+
vi.mock("./Mention", () => ({ Mention: () => null }))
8+
9+
const { tSpy } = vi.hoisted(() => ({ tSpy: vi.fn((key: string) => key) }))
10+
11+
vi.mock("@src/i18n/TranslationContext", () => ({
12+
useAppTranslation: () => ({ t: (key: string) => tSpy(key) }),
13+
}))
14+
15+
describe("QueuedMessages - the heading it puts over the queue", () => {
16+
it("titles the queue and renders each queued message's text", () => {
17+
const queue: QueuedMessage[] = [{ id: "queued-1", text: "keep the diff open" } as QueuedMessage]
18+
19+
render(<QueuedMessages queue={queue} onRemove={vi.fn()} onUpdate={vi.fn()} />)
20+
21+
expect(tSpy).toHaveBeenCalledWith("chat:queuedMessages.title")
22+
expect(screen.getByTestId("queued-messages")).toHaveTextContent("chat:queuedMessages.title")
23+
})
24+
})
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import type { ReactNode } from "react"
2+
3+
import { providerIdentifiers, type ProviderSettings } from "@roo-code/types"
4+
5+
import { render, screen } from "@/utils/test-utils"
6+
7+
import { BedrockCustomArn } from "../BedrockCustomArn"
8+
9+
type SelectMockProps = {
10+
children: ReactNode
11+
value: string
12+
onValueChange: (value: string) => void
13+
disabled?: boolean
14+
}
15+
type TextFieldMockProps = {
16+
children: ReactNode
17+
value: string
18+
onInput: (event: { target: { value: string } }) => void
19+
placeholder?: string
20+
className?: string
21+
"data-testid"?: string
22+
}
23+
24+
vi.mock("@src/components/ui", () => ({
25+
Select: ({ children, value, onValueChange, disabled }: SelectMockProps) => (
26+
<select
27+
data-testid="base-model-select"
28+
value={value}
29+
disabled={disabled}
30+
onChange={(e) => onValueChange(e.target.value)}>
31+
{children}
32+
</select>
33+
),
34+
SelectContent: ({ children }: { children: ReactNode }) => <>{children}</>,
35+
SelectItem: ({ children, value }: { children: ReactNode; value: string }) => (
36+
<option value={value}>{children}</option>
37+
),
38+
SelectTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
39+
SelectValue: () => null,
40+
}))
41+
42+
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
43+
VSCodeTextField: ({ children, value, onInput, placeholder, "data-testid": testId }: TextFieldMockProps) => (
44+
<label>
45+
<input
46+
data-testid={testId ?? "text-field"}
47+
value={value}
48+
placeholder={placeholder}
49+
onChange={(e) => onInput({ target: { value: e.target.value } })}
50+
/>
51+
{children}
52+
</label>
53+
),
54+
}))
55+
56+
vi.mock("@src/i18n/TranslationContext", () => ({
57+
useAppTranslation: () => ({
58+
t: (key: string, options?: Record<string, unknown>) =>
59+
options ? Object.entries(options).reduce((acc: string, [k, v]) => `${acc} ${k}=${String(v)}`, key) : key,
60+
}),
61+
}))
62+
63+
describe("BedrockCustomArn - the memoised ARN validation", () => {
64+
it("reports no problem for a session with no ARN typed yet", () => {
65+
const apiConfiguration: ProviderSettings = {
66+
apiProvider: providerIdentifiers.bedrock,
67+
awsRegion: "us-east-1",
68+
}
69+
const setApiConfigurationField = vi.fn()
70+
71+
render(
72+
<BedrockCustomArn
73+
apiConfiguration={apiConfiguration}
74+
setApiConfigurationField={setApiConfigurationField}
75+
/>,
76+
)
77+
78+
expect(screen.queryByText("settings:providers.invalidArnFormat")).toBeNull()
79+
expect(screen.queryByText(/regionMismatch/)).toBeNull()
80+
})
81+
82+
it("re-validates when a different ARN arrives, including a region that contradicts the setting", () => {
83+
const base: ProviderSettings = { apiProvider: providerIdentifiers.bedrock, awsRegion: "us-east-1" }
84+
const setApiConfigurationField = vi.fn()
85+
const mismatchedArn =
86+
"arn:aws:bedrock:eu-west-1:123456789012:inference-profile/eu.anthropic.claude-3-7-sonnet-20250219-v1:0"
87+
88+
const { rerender } = render(
89+
<BedrockCustomArn apiConfiguration={base} setApiConfigurationField={setApiConfigurationField} />,
90+
)
91+
expect(screen.queryByText(/regionMismatch/)).toBeNull()
92+
93+
rerender(
94+
<BedrockCustomArn
95+
apiConfiguration={{ ...base, awsCustomArn: mismatchedArn }}
96+
setApiConfigurationField={setApiConfigurationField}
97+
/>,
98+
)
99+
100+
expect(
101+
screen.getByText("settings:validation.arn.regionMismatch arnRegion=eu-west-1 region=us-east-1"),
102+
).toBeDefined()
103+
})
104+
})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { formatDateOnly, formatTimeOnly, hourCycleOptions } from "../format"
2+
3+
// Wed 27 May 2026, 15:00 local - a time where a 12h/24h difference is visible.
4+
const timestamp = new Date(2026, 4, 27, 15, 0, 0).getTime()
5+
6+
describe("formatDateOnly - the default order is the auto order", () => {
7+
it("leaves an omitted order to the locale, the same as asking for auto", () => {
8+
expect(formatDateOnly(timestamp, "en")).toBe(formatDateOnly(timestamp, "en", "auto"))
9+
})
10+
11+
it("uses the fixed numeric orders when they are asked for", () => {
12+
expect(formatDateOnly(timestamp, "en", "mdy")).toBe("05/27/2026")
13+
expect(formatDateOnly(timestamp, "en", "dmy")).toBe("27/05/2026")
14+
expect(formatDateOnly(timestamp, "en", "ymd")).toBe("2026-05-27")
15+
expect(formatDateOnly(timestamp, "en")).not.toBe(formatDateOnly(timestamp, "en", "ymd"))
16+
})
17+
})
18+
19+
describe("formatTimeOnly - the default clock is the auto clock", () => {
20+
it("leaves an omitted format to the locale, the same as asking for auto", () => {
21+
expect(formatTimeOnly(timestamp, "en-US")).toBe(formatTimeOnly(timestamp, "en-US", "auto"))
22+
})
23+
24+
it("honours an explicit 24h clock over the locale default", () => {
25+
expect(formatTimeOnly(timestamp, "en-US", "24h")).not.toBe(formatTimeOnly(timestamp, "en-US", "auto"))
26+
expect(formatTimeOnly(timestamp, "en-US", "24h")).toContain("15:00")
27+
})
28+
})
29+
30+
describe("hourCycleOptions - what auto hands to the locale", () => {
31+
it("hands the locale nothing for auto, whether asked for or left out", () => {
32+
expect(hourCycleOptions()).toStrictEqual({})
33+
expect(hourCycleOptions("auto")).toStrictEqual({})
34+
})
35+
36+
it("maps the two explicit clocks to their hour-cycle options", () => {
37+
expect(hourCycleOptions("12h")).toStrictEqual({ hour12: true })
38+
expect(hourCycleOptions("24h")).toStrictEqual({ hourCycle: "h23" })
39+
})
40+
})

0 commit comments

Comments
 (0)