Files

403 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)')
})
})