codeburn/tests/mouse-tracking.test.ts
Resham Joshi 2a7d9588d8
Some checks failed
CI / semgrep (push) Has been cancelled
Tests / test-platforms (windows-latest) (push) Has been cancelled
Tests / desktop (push) Has been cancelled
Tests / test (22) (push) Has been cancelled
Tests / test (22.13.0) (push) Has been cancelled
Tests / test (24) (push) Has been cancelled
Tests / test (26) (push) Has been cancelled
Tests / test-platforms (macos-latest) (push) Has been cancelled
fix: mouse tracking off by default so text selection works, m toggles it (#1562)
2026-09-25 20:17:07 +02:00

108 lines
3.5 KiB
TypeScript

import { PassThrough } from 'node:stream'
import React from 'react'
import { render } from 'ink'
import stripAnsi from 'strip-ansi'
import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest'
import { InteractiveDashboard } from '../src/dashboard.js'
import type { ProjectSummary } from '../src/types.js'
const MOUSE_TRACKING_ON = '\x1b[?1000h\x1b[?1006h'
const MOUSE_TRACKING_OFF = '\x1b[?1006l\x1b[?1000l'
const PROJECTS: ProjectSummary[] = [
{ project: 'proj', projectPath: 'proj', sessions: [], totalCostUSD: 0, totalSavingsUSD: 0, totalApiCalls: 0 },
]
// The viewport writes the tracking sequences to the process's own stdout (the
// terminal), not to Ink's injected stream, and only when both ends are a TTY.
let writes: string[]
let stdoutTTY: boolean | undefined
let stdinTTY: boolean | undefined
beforeEach(() => {
writes = []
stdoutTTY = process.stdout.isTTY
stdinTTY = process.stdin.isTTY
process.stdout.isTTY = true
process.stdin.isTTY = true
vi.spyOn(process.stdout, 'write').mockImplementation(chunk => { writes.push(String(chunk)); return true })
})
afterEach(() => {
vi.restoreAllMocks()
process.stdout.isTTY = stdoutTTY as boolean
process.stdin.isTTY = stdinTTY as boolean
})
function makeTui(columns = 120) {
const stdin = new PassThrough() as PassThrough & NodeJS.ReadStream
const stdout = new PassThrough({ highWaterMark: 4 * 1024 * 1024 }) as PassThrough & NodeJS.WriteStream
stdin.isTTY = true
stdin.setRawMode = () => stdin
stdin.ref = () => stdin
stdin.unref = () => stdin
stdout.isTTY = true
stdout.columns = columns
stdout.rows = 50
return { stdin, stdout }
}
async function mountDashboard(columns = 120) {
const { stdin, stdout } = makeTui(columns)
const frames: string[] = []
stdout.on('data', chunk => frames.push(stripAnsi(String(chunk))))
const app = render(React.createElement(InteractiveDashboard, {
initialProjects: PROJECTS,
initialPeriod: 'today',
initialProvider: 'all',
refreshSeconds: 0,
windowColumns: columns,
}), { stdin, stdout, debug: true, interactive: true, patchConsole: false })
await app.waitUntilRenderFlush()
return { app, stdin, frames }
}
describe('dashboard mouse tracking toggle (#951)', () => {
it('does not enable mouse tracking at mount, so text selection keeps working', async () => {
const { app } = await mountDashboard()
onTestFinished(() => app.unmount())
expect(writes.join('')).not.toContain(MOUSE_TRACKING_ON)
})
it('m enables tracking, and m again disables it without waiting for unmount', async () => {
const { app, stdin } = await mountDashboard()
onTestFinished(() => app.unmount())
stdin.write('m')
await app.waitUntilRenderFlush()
expect(writes.join('')).toContain(MOUSE_TRACKING_ON)
writes.length = 0
stdin.write('m')
await app.waitUntilRenderFlush()
expect(writes.join('')).toContain(MOUSE_TRACKING_OFF)
expect(writes.join('')).not.toContain(MOUSE_TRACKING_ON)
})
it('unmount after enabling leaves tracking off', async () => {
const { app, stdin } = await mountDashboard()
stdin.write('m')
await app.waitUntilRenderFlush()
writes.length = 0
app.unmount()
await app.waitUntilExit()
expect(writes.join('')).toContain(MOUSE_TRACKING_OFF)
})
it('shows the toggle in the key hints at 80 columns', async () => {
const { app, frames } = await mountDashboard(80)
onTestFinished(() => app.unmount())
expect(frames.join('')).toContain('m mouse')
})
})