mirror of
https://github.com/anthropics/claude-code.git
synced 2026-10-21 06:20:37 +08:00
403 lines
13 KiB
TypeScript
403 lines
13 KiB
TypeScript
import { describe, expect, test, tier } from 'claude-code/testing'
|
||
|
||
import Fixtures from './fixtures'
|
||
|
||
tier('builtin')
|
||
|
||
describe('views', () => {
|
||
test('docked, every file has its hunks under the list', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(drawn).toContain('2 files changed +3 -1')
|
||
|
||
expect(drawn, "a closing empty row, as the built-in's").toContain(
|
||
'+const a = 2\n ',
|
||
)
|
||
|
||
expect(drawn, 'none after the last file').toMatch(/\+export const c = 2$/)
|
||
expect(drawn).not.toContain('❯')
|
||
})
|
||
|
||
test('a failed hunks read blanks every body, not the list', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.TWO_FILES, {
|
||
hunksRefusal: () => Fixtures.GIT_HUNG,
|
||
})
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(drawn).toContain('2 files changed +3 -1')
|
||
expect(drawn).toContain('app.ts')
|
||
expect(drawn).toContain('lib.ts')
|
||
expect(drawn.match(/Diff unavailable/g), 'once a file').toHaveLength(2)
|
||
expect(drawn).not.toContain('const a')
|
||
})
|
||
|
||
test('the read after a failed one draws the hunks again', async ($, on) => {
|
||
let refusal: string | null = Fixtures.GIT_HUNG
|
||
|
||
const world = Fixtures.inRepository(on, Fixtures.TWO_FILES, {
|
||
hunksRefusal: () => refusal,
|
||
})
|
||
|
||
on('tool.call', () => ({ result: 'ran' }))
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
expect(
|
||
Fixtures.textOf(await $.ui.render(Fixtures.PANE)),
|
||
'the failed read blanked the hunks first',
|
||
).toContain('Diff unavailable')
|
||
|
||
refusal = null
|
||
|
||
await $.tool.call({ tool: 'Bash', command: 'make' })
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(drawn).not.toContain('Diff unavailable')
|
||
expect(drawn).toContain('+const a = 2')
|
||
expect(drawn).toContain('+export const c = 2')
|
||
})
|
||
|
||
test('inline, a row opens that file alone', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
|
||
expect(await $.ui.press({ plugin: 'diff', key: 'file:lib.ts' })).toEqual({
|
||
element: 'file:lib.ts',
|
||
})
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
|
||
|
||
expect(drawn).toContain('+export const c = 2')
|
||
expect(drawn).not.toContain('+const a = 2')
|
||
expect(drawn).not.toContain('app.ts')
|
||
expect(drawn).toContain('\u2191/\u2193 to scroll \u00b7 Esc to back')
|
||
})
|
||
|
||
test('inline, a file edited before the session opens too', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MOVED_IN)
|
||
|
||
on('session.usage', () => ({ value: Fixtures.usageAt(Fixtures.SETTLE_MS) }))
|
||
Fixtures.oldFiles(on)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
expect(
|
||
Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE)),
|
||
"the dialog lists it beside the session's file",
|
||
).toContain('old.ts')
|
||
|
||
expect(await $.ui.press({ plugin: 'diff', key: 'file:old.ts' })).toEqual({
|
||
element: 'file:old.ts',
|
||
})
|
||
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
|
||
|
||
expect(
|
||
drawn,
|
||
'a listed row can be pressed, so its hunks were read',
|
||
).not.toContain('Loading diff')
|
||
|
||
expect(drawn).toContain('+b')
|
||
expect(drawn).not.toContain('+d')
|
||
})
|
||
|
||
test('inline, a file the docked pane hides opens too', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.WITH_LOCKFILE)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
|
||
expect(await $.ui.press({ plugin: 'diff', key: 'file:bun.lock' })).toEqual({
|
||
element: 'file:bun.lock',
|
||
})
|
||
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
|
||
|
||
expect(drawn, 'generated, and still a listed row').not.toContain(
|
||
'Loading diff',
|
||
)
|
||
|
||
expect(drawn).toContain('+"left": "1.0.1"')
|
||
})
|
||
|
||
test('inline, a window that moves reads only its new rows', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
|
||
|
||
on('session.usage', () => ({ value: Fixtures.usageAt(Fixtures.SETTLE_MS) }))
|
||
|
||
Fixtures.oldFiles(
|
||
on,
|
||
Array.from(
|
||
{ length: Fixtures.MANY_FILE_COUNT },
|
||
(_, at) => `file${at}.ts`,
|
||
),
|
||
)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.focus(Fixtures.ringOnto('file:file3.ts'))
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
await $.ui.press({ plugin: 'diff', key: 'file:file5.ts' })
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
expect(
|
||
world.runs
|
||
.filter(run => run.argv.includes('--raw'))
|
||
.map(run => run.argv.slice(run.argv.indexOf('--') + 1).join(' ')),
|
||
'the first five, the one the walk brought in, the two the press did',
|
||
).toEqual([
|
||
'file0.ts file1.ts file2.ts file3.ts file4.ts',
|
||
'file5.ts',
|
||
'file6.ts file7.ts',
|
||
])
|
||
})
|
||
|
||
test('docked, a wheel tick moves the body, not the list', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const before = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(before, 'listed, and named over its body').toContain('file0.ts')
|
||
expect(before.split('file0.ts')).toHaveLength(3)
|
||
|
||
expect(await $.ui.scroll(Fixtures.WHEEL_TICK)).toEqual({})
|
||
|
||
const after = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(after.split('file0.ts'), 'its name row scrolled away').toHaveLength(
|
||
2,
|
||
)
|
||
|
||
expect(after).toContain('-const v = 0')
|
||
expect(after).toContain('10 files changed +10 -10')
|
||
expect(after, 'the list stays').not.toContain('more above')
|
||
})
|
||
|
||
test('docked, the wheel over a long list moves the list', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.render(Fixtures.PANE)
|
||
|
||
expect(await $.ui.scroll(Fixtures.WHEEL_OVER_LIST)).toEqual({})
|
||
|
||
const after = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(after).toContain('\u2191 1 more above')
|
||
expect(after).toContain('file8.ts')
|
||
expect(after.split('file0.ts'), 'the body stays').toHaveLength(2)
|
||
})
|
||
|
||
test('past eight files the docked list scrolls by key', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(drawn).toContain('10 files changed +10 -10')
|
||
expect(drawn).toContain('file7.ts')
|
||
expect(drawn).toContain('\u2193 2 more below (opt+\u2193 to scroll)')
|
||
expect(drawn).not.toContain('file8.ts')
|
||
|
||
expect(await $.ui.press({ plugin: 'diff', key: 'list-down' })).toEqual({
|
||
element: 'list-down',
|
||
})
|
||
|
||
const scrolled = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(scrolled).toContain('\u2191 1 more above')
|
||
expect(scrolled).toContain('file8.ts')
|
||
expect(scrolled).toContain('\u2193 1 more below')
|
||
})
|
||
|
||
test('a rename lists as git prints it, and reads no body', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.RENAMED)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
|
||
|
||
expect(drawn).toContain('1 file changed')
|
||
expect(drawn).toContain('docs/{notes.txt => renamed-notes.txt}')
|
||
expect(drawn).toContain('No diff content')
|
||
|
||
expect(
|
||
world.runs.filter(run => run.argv.includes('docs/renamed-notes.txt')),
|
||
).toEqual([])
|
||
})
|
||
|
||
test('inline, the dialog lists every file and its keys', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
|
||
|
||
expect(drawn).toContain('Uncommitted changes (git diff HEAD)')
|
||
expect(drawn).toContain('2 files changed +3 -1')
|
||
expect(drawn).toContain('\u276f app.ts')
|
||
expect(drawn).toContain(' lib.ts')
|
||
expect(drawn).not.toContain('+const a = 2')
|
||
|
||
expect(drawn).toContain(
|
||
'\u2191/\u2193 to select \u00b7 Enter to view \u00b7 Esc to close',
|
||
)
|
||
|
||
expect(world.opened.at(-1), 'sized to its rows once listed').toEqual({
|
||
id: 'diff',
|
||
title: 'Diff',
|
||
closeOnEscape: true,
|
||
rows: 8,
|
||
})
|
||
})
|
||
|
||
test('narrow under the fullscreen layout, the resize line', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
expect(
|
||
Fixtures.textOf(await $.ui.render(Fixtures.NARROW_INLINE_PANE)),
|
||
).toBe(
|
||
'Resize your terminal to at least 110 columns to show the diff panel',
|
||
)
|
||
})
|
||
|
||
test('off fullscreen, the window follows the walk', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
|
||
|
||
expect(drawn).toContain('\u276f file0.ts')
|
||
expect(drawn).toContain(' file4.ts')
|
||
expect(drawn).not.toContain('file5.ts')
|
||
expect(drawn).toContain(' \u2193 5 more files')
|
||
expect(await $.ui.focus(Fixtures.ringOnto('file:file3.ts'))).toEqual({})
|
||
|
||
expect(
|
||
world.focused.map(focus => focus.element),
|
||
'its row once centred',
|
||
).toEqual(['file:file2.ts'])
|
||
|
||
const walked = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
|
||
|
||
expect(walked).toContain(' \u2191 1 more file')
|
||
expect(walked).toContain('\u276f file3.ts')
|
||
expect(walked).toContain(' file5.ts')
|
||
expect(walked).toContain(' \u2193 4 more files')
|
||
expect(walked).not.toContain('file0.ts')
|
||
})
|
||
|
||
test('the walk follows under the name a build registers', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
|
||
expect(
|
||
await $.ui.focus(Fixtures.ringOnto('file:file3.ts', 'cc-plugin-diff')),
|
||
).toEqual({})
|
||
|
||
expect(
|
||
world.focused.map(focus => focus.element),
|
||
'its row once centred',
|
||
).toEqual(['file:file2.ts'])
|
||
})
|
||
|
||
test('off fullscreen, the walk stops at the last file', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
await $.ui.focus(Fixtures.ringOnto('file:file3.ts'))
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
await $.ui.focus(Fixtures.ringOnto('file:file5.ts'))
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
await $.ui.focus(Fixtures.ringOnto('file:file7.ts'))
|
||
await $.ui.render(Fixtures.INLINE_PANE)
|
||
await $.ui.focus(Fixtures.ringOnto('file:file9.ts'))
|
||
|
||
const last = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
|
||
|
||
expect(last).toContain('\u276f file9.ts')
|
||
expect(last).toContain(' \u2191 5 more files')
|
||
|
||
expect(
|
||
await $.ui.focus(Fixtures.ringOnto('file:file5.ts')),
|
||
'the ring would wrap to the first row drawn; it stays',
|
||
).toEqual({})
|
||
|
||
expect(Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))).toContain(
|
||
'\u276f file9.ts',
|
||
)
|
||
|
||
expect(world.focused, 'the wrap never reached the engine').toHaveLength(4)
|
||
})
|
||
|
||
test('off fullscreen, the dialog draws at any width', async ($, on) => {
|
||
const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
|
||
|
||
await $.session.start(Fixtures.SESSION)
|
||
await $.command.run(Fixtures.DIALOG_DIFF)
|
||
await world.clock.advance(Fixtures.SETTLE_MS)
|
||
|
||
expect(
|
||
Fixtures.textOf(await $.ui.render(Fixtures.NARROW_INLINE_PANE)),
|
||
).toContain('Uncommitted changes (git diff HEAD)')
|
||
})
|
||
})
|