spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / diff / tests / views.test.ts
312 lines · 10.5 KBRaw
1import { describe, expect, test, tier } from 'claude-code/testing'
2
3import Fixtures from './fixtures'
4
5tier('builtin')
6
7describe('views', () => {
8 test('docked, every file has its hunks under the list', async ($, on) => {
9 const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
10
11 await $.session.start(Fixtures.SESSION)
12 await $.command.run(Fixtures.DIFF)
13 await world.clock.advance(Fixtures.SETTLE_MS)
14
15 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
16
17 expect(drawn).toContain('2 files changed +3 -1')
18
19 expect(drawn, "a closing empty row, as the built-in's").toContain(
20 '+const a = 2\n ',
21 )
22
23 expect(drawn, 'none after the last file').toMatch(/\+export const c = 2$/)
24 expect(drawn).not.toContain('❯')
25 })
26
27 test('a failed hunks read blanks every body, not the list', async ($, on) => {
28 const world = Fixtures.inRepository(on, Fixtures.TWO_FILES, {
29 hunksRefusal: () => Fixtures.GIT_HUNG,
30 })
31
32 await $.session.start(Fixtures.SESSION)
33 await $.command.run(Fixtures.DIFF)
34 await world.clock.advance(Fixtures.SETTLE_MS)
35
36 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
37
38 expect(drawn).toContain('2 files changed +3 -1')
39 expect(drawn).toContain('app.ts')
40 expect(drawn).toContain('lib.ts')
41 expect(drawn.match(/Diff unavailable/g), 'once a file').toHaveLength(2)
42 expect(drawn).not.toContain('const a')
43 })
44
45 test('the read after a failed one draws the hunks again', async ($, on) => {
46 let refusal: string | null = Fixtures.GIT_HUNG
47
48 const world = Fixtures.inRepository(on, Fixtures.TWO_FILES, {
49 hunksRefusal: () => refusal,
50 })
51
52 on('tool.call', () => ({ result: 'ran' }))
53
54 await $.session.start(Fixtures.SESSION)
55 await $.command.run(Fixtures.DIFF)
56 await world.clock.advance(Fixtures.SETTLE_MS)
57
58 expect(
59 Fixtures.textOf(await $.ui.render(Fixtures.PANE)),
60 'the failed read blanked the hunks first',
61 ).toContain('Diff unavailable')
62
63 refusal = null
64
65 await $.tool.call({ tool: 'Bash', command: 'make' })
66 await world.clock.advance(Fixtures.SETTLE_MS)
67
68 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
69
70 expect(drawn).not.toContain('Diff unavailable')
71 expect(drawn).toContain('+const a = 2')
72 expect(drawn).toContain('+export const c = 2')
73 })
74
75 test('inline, a row opens that file alone', async ($, on) => {
76 const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
77
78 await $.session.start(Fixtures.SESSION)
79 await $.command.run(Fixtures.DIALOG_DIFF)
80 await world.clock.advance(Fixtures.SETTLE_MS)
81 await $.ui.render(Fixtures.INLINE_PANE)
82
83 expect(await $.ui.press({ plugin: 'diff', key: 'file:lib.ts' })).toEqual({
84 element: 'file:lib.ts',
85 })
86
87 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
88
89 expect(drawn).toContain('+export const c = 2')
90 expect(drawn).not.toContain('+const a = 2')
91 expect(drawn).not.toContain('app.ts')
92 expect(drawn).toContain('\u2191/\u2193 to scroll \u00b7 Esc to back')
93 })
94
95 test('docked, a wheel tick moves the body, not the list', async ($, on) => {
96 const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
97
98 await $.session.start(Fixtures.SESSION)
99 await $.command.run(Fixtures.DIFF)
100 await world.clock.advance(Fixtures.SETTLE_MS)
101
102 const before = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
103
104 expect(before, 'listed, and named over its body').toContain('file0.ts')
105 expect(before.split('file0.ts')).toHaveLength(3)
106
107 expect(await $.ui.scroll(Fixtures.WHEEL_TICK)).toEqual({})
108
109 const after = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
110
111 expect(after.split('file0.ts'), 'its name row scrolled away').toHaveLength(
112 2,
113 )
114
115 expect(after).toContain('-const v = 0')
116 expect(after).toContain('10 files changed +10 -10')
117 expect(after, 'the list stays').not.toContain('more above')
118 })
119
120 test('docked, the wheel over a long list moves the list', async ($, on) => {
121 const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
122
123 await $.session.start(Fixtures.SESSION)
124 await $.command.run(Fixtures.DIFF)
125 await world.clock.advance(Fixtures.SETTLE_MS)
126 await $.ui.render(Fixtures.PANE)
127
128 expect(await $.ui.scroll(Fixtures.WHEEL_OVER_LIST)).toEqual({})
129
130 const after = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
131
132 expect(after).toContain('\u2191 1 more above')
133 expect(after).toContain('file8.ts')
134 expect(after.split('file0.ts'), 'the body stays').toHaveLength(2)
135 })
136
137 test('past eight files the docked list scrolls by key', async ($, on) => {
138 const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
139
140 await $.session.start(Fixtures.SESSION)
141 await $.command.run(Fixtures.DIFF)
142 await world.clock.advance(Fixtures.SETTLE_MS)
143
144 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
145
146 expect(drawn).toContain('10 files changed +10 -10')
147 expect(drawn).toContain('file7.ts')
148 expect(drawn).toContain('\u2193 2 more below (opt+\u2193 to scroll)')
149 expect(drawn).not.toContain('file8.ts')
150
151 expect(await $.ui.press({ plugin: 'diff', key: 'list-down' })).toEqual({
152 element: 'list-down',
153 })
154
155 const scrolled = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
156
157 expect(scrolled).toContain('\u2191 1 more above')
158 expect(scrolled).toContain('file8.ts')
159 expect(scrolled).toContain('\u2193 1 more below')
160 })
161
162 test('a rename lists as git prints it, and reads no body', async ($, on) => {
163 const world = Fixtures.inRepository(on, Fixtures.RENAMED)
164
165 await $.session.start(Fixtures.SESSION)
166 await $.command.run(Fixtures.DIFF)
167 await world.clock.advance(Fixtures.SETTLE_MS)
168
169 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
170
171 expect(drawn).toContain('1 file changed')
172 expect(drawn).toContain('docs/{notes.txt => renamed-notes.txt}')
173 expect(drawn).toContain('No diff content')
174
175 expect(
176 world.runs.filter(run => run.argv.includes('docs/renamed-notes.txt')),
177 ).toEqual([])
178 })
179
180 test('inline, the dialog lists every file and its keys', async ($, on) => {
181 const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
182
183 await $.session.start(Fixtures.SESSION)
184 await $.command.run(Fixtures.DIALOG_DIFF)
185 await world.clock.advance(Fixtures.SETTLE_MS)
186
187 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
188
189 expect(drawn).toContain('Uncommitted changes (git diff HEAD)')
190 expect(drawn).toContain('2 files changed +3 -1')
191 expect(drawn).toContain('\u276f app.ts')
192 expect(drawn).toContain(' lib.ts')
193 expect(drawn).not.toContain('+const a = 2')
194
195 expect(drawn).toContain(
196 '\u2191/\u2193 to select \u00b7 Enter to view \u00b7 Esc to close',
197 )
198
199 expect(world.opened.at(-1), 'sized to its rows once listed').toEqual({
200 id: 'diff',
201 title: 'Diff',
202 holdToasts: true,
203 closeOnEscape: true,
204 rows: 8,
205 })
206 })
207
208 test('narrow under the fullscreen layout, the resize line', async ($, on) => {
209 const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
210
211 await $.session.start(Fixtures.SESSION)
212 await $.command.run(Fixtures.DIFF)
213 await world.clock.advance(Fixtures.SETTLE_MS)
214
215 expect(
216 Fixtures.textOf(await $.ui.render(Fixtures.NARROW_INLINE_PANE)),
217 ).toBe(
218 'Resize your terminal to at least 110 columns to show the diff panel',
219 )
220 })
221
222 test('off fullscreen, the window follows the walk', async ($, on) => {
223 const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
224
225 await $.session.start(Fixtures.SESSION)
226 await $.command.run(Fixtures.DIALOG_DIFF)
227 await world.clock.advance(Fixtures.SETTLE_MS)
228
229 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
230
231 expect(drawn).toContain('\u276f file0.ts')
232 expect(drawn).toContain(' file4.ts')
233 expect(drawn).not.toContain('file5.ts')
234 expect(drawn).toContain(' \u2193 5 more files')
235 expect(await $.ui.focus(Fixtures.ringOnto('file:file3.ts'))).toEqual({})
236
237 expect(
238 world.focused.map(focus => focus.element),
239 'its row once centred',
240 ).toEqual(['file:file2.ts'])
241
242 const walked = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
243
244 expect(walked).toContain(' \u2191 1 more file')
245 expect(walked).toContain('\u276f file3.ts')
246 expect(walked).toContain(' file5.ts')
247 expect(walked).toContain(' \u2193 4 more files')
248 expect(walked).not.toContain('file0.ts')
249 })
250
251 test('the walk follows under the name a build registers', async ($, on) => {
252 const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
253
254 await $.session.start(Fixtures.SESSION)
255 await $.command.run(Fixtures.DIALOG_DIFF)
256 await world.clock.advance(Fixtures.SETTLE_MS)
257 await $.ui.render(Fixtures.INLINE_PANE)
258
259 expect(
260 await $.ui.focus(Fixtures.ringOnto('file:file3.ts', 'cc-plugin-diff')),
261 ).toEqual({})
262
263 expect(
264 world.focused.map(focus => focus.element),
265 'its row once centred',
266 ).toEqual(['file:file2.ts'])
267 })
268
269 test('off fullscreen, the walk stops at the last file', async ($, on) => {
270 const world = Fixtures.inRepository(on, Fixtures.MANY_FILES)
271
272 await $.session.start(Fixtures.SESSION)
273 await $.command.run(Fixtures.DIALOG_DIFF)
274 await world.clock.advance(Fixtures.SETTLE_MS)
275 await $.ui.render(Fixtures.INLINE_PANE)
276 await $.ui.focus(Fixtures.ringOnto('file:file3.ts'))
277 await $.ui.render(Fixtures.INLINE_PANE)
278 await $.ui.focus(Fixtures.ringOnto('file:file5.ts'))
279 await $.ui.render(Fixtures.INLINE_PANE)
280 await $.ui.focus(Fixtures.ringOnto('file:file7.ts'))
281 await $.ui.render(Fixtures.INLINE_PANE)
282 await $.ui.focus(Fixtures.ringOnto('file:file9.ts'))
283
284 const last = Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))
285
286 expect(last).toContain('\u276f file9.ts')
287 expect(last).toContain(' \u2191 5 more files')
288
289 expect(
290 await $.ui.focus(Fixtures.ringOnto('file:file5.ts')),
291 'the ring would wrap to the first row drawn; it stays',
292 ).toEqual({})
293
294 expect(Fixtures.textOf(await $.ui.render(Fixtures.INLINE_PANE))).toContain(
295 '\u276f file9.ts',
296 )
297
298 expect(world.focused, 'the wrap never reached the engine').toHaveLength(4)
299 })
300
301 test('off fullscreen, the dialog draws at any width', async ($, on) => {
302 const world = Fixtures.inRepository(on, Fixtures.TWO_FILES)
303
304 await $.session.start(Fixtures.SESSION)
305 await $.command.run(Fixtures.DIALOG_DIFF)
306 await world.clock.advance(Fixtures.SETTLE_MS)
307
308 expect(
309 Fixtures.textOf(await $.ui.render(Fixtures.NARROW_INLINE_PANE)),
310 ).toContain('Uncommitted changes (git diff HEAD)')
311 })
312})