Skip to content

Commit 870ae54

Browse files
Merge picker mouse-reporting fix
2 parents 7753fd5 + c79e421 commit 870ae54

3 files changed

Lines changed: 100 additions & 2 deletions

File tree

src/tui-opentui/list-modal.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,15 @@ export async function runListModal(
4141
): Promise<string | null> {
4242
const renderer = config.createRenderer
4343
? await config.createRenderer()
44-
: await createCliRenderer({ exitOnCtrlC: false, targetFps: 30 })
44+
: await createCliRenderer({
45+
exitOnCtrlC: false,
46+
targetFps: 30,
47+
// Same trade as the product host (CL-5540): reporting off by default
48+
// so the terminal owns drag-select and its own copy in these satellite
49+
// pickers too.
50+
useMouse: false,
51+
enableMouseMovement: false,
52+
})
4553

4654
const shell = createAppShell(renderer, { title: config.title, run: "idle" })
4755

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* CL-5540: the onboarding provider picker and the satellite list modals
3+
* (session resume, session mode) mount their own renderer and must disable
4+
* DEC mouse reporting the same way the product host does, or the terminal
5+
* never gets button-1 drags to run its own text selection. These tests mock
6+
* `@opentui/core` so the real (non-test-injected) `createCliRenderer` branch
7+
* runs, and assert on the options it was actually called with.
8+
*/
9+
import { afterAll, describe, expect, mock, test } from "bun:test"
10+
import type { Harness } from "./harness.js"
11+
12+
type CapturedRendererOptions = {
13+
readonly useMouse?: boolean
14+
readonly enableMouseMovement?: boolean
15+
}
16+
17+
const capturedOptions: CapturedRendererOptions[] = []
18+
const mountedHarnesses: Harness[] = []
19+
20+
// The mock must be registered before anything (including this file's own
21+
// helpers) does a real `@opentui/core` import, or that import wins the module
22+
// cache and the mock never takes effect. Every dependency below is loaded
23+
// with a dynamic `import()` after `mock.module` for that reason.
24+
const realCore = await import("@opentui/core")
25+
26+
mock.module("@opentui/core", () => ({
27+
...realCore,
28+
createCliRenderer: async (options: CapturedRendererOptions) => {
29+
capturedOptions.push(options)
30+
const { createHarness } = await import("./harness.js")
31+
const harness = await createHarness({ width: 80, height: 24 })
32+
mountedHarnesses.push(harness)
33+
return harness.renderer
34+
},
35+
}))
36+
37+
// `mock.module` replaces the shared module cache for the whole test process,
38+
// not just this file — every other test that imports `@opentui/core` runs in
39+
// the same process. Put the real module back once this file is done so a
40+
// later un-injected `createCliRenderer` caller does not silently get this
41+
// fake harness renderer instead.
42+
afterAll(() => {
43+
mock.module("@opentui/core", () => realCore)
44+
})
45+
46+
const { runListModal } = await import("./list-modal.js")
47+
const { runProviderSetup } = await import("./provider-setup.js")
48+
49+
async function waitForMount(): Promise<void> {
50+
for (let i = 0; i < 100 && capturedOptions.length === 0; i++) {
51+
await new Promise((resolve) => setTimeout(resolve, 10))
52+
}
53+
}
54+
55+
describe("default renderer mount disables DEC mouse reporting (CL-5540)", () => {
56+
test("runListModal", async () => {
57+
capturedOptions.length = 0
58+
// Fire-and-forget: the mounted renderer never resolves this promise in
59+
// this test (nothing presses a key), so only await the mount itself.
60+
void runListModal({
61+
title: "resume session",
62+
options: [{ id: "s-1", label: "First session" }],
63+
})
64+
await waitForMount()
65+
expect(capturedOptions).toHaveLength(1)
66+
expect(capturedOptions[0]?.useMouse).toBe(false)
67+
expect(capturedOptions[0]?.enableMouseMovement).toBe(false)
68+
mountedHarnesses.pop()?.destroy()
69+
})
70+
71+
test("runProviderSetup", async () => {
72+
capturedOptions.length = 0
73+
void runProviderSetup({
74+
onSubmit: async () => undefined,
75+
showTelemetryNotice: false,
76+
})
77+
await waitForMount()
78+
expect(capturedOptions).toHaveLength(1)
79+
expect(capturedOptions[0]?.useMouse).toBe(false)
80+
expect(capturedOptions[0]?.enableMouseMovement).toBe(false)
81+
mountedHarnesses.pop()?.destroy()
82+
})
83+
})

src/tui-opentui/provider-setup.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,14 @@ export async function runProviderSetup(
583583
): Promise<boolean> {
584584
const renderer = config.createRenderer
585585
? await config.createRenderer()
586-
: await createCliRenderer({ exitOnCtrlC: false, targetFps: 30 })
586+
: await createCliRenderer({
587+
exitOnCtrlC: false,
588+
targetFps: 30,
589+
// Same trade as the product host (CL-5540): reporting off by default
590+
// so the terminal owns drag-select and its own copy during onboarding.
591+
useMouse: false,
592+
enableMouseMovement: false,
593+
})
587594

588595
const choices = providerChoices()
589596
const values: ProviderFormValues = {

0 commit comments

Comments
 (0)