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 / register.test.ts
843 lines · 23.3 KBRaw
1import type { Args, ResultOf } from 'claude-code'
2import { describe, expect, mock, test, tier } from 'claude-code/testing'
3
4import Limits from '../hooks/limits'
5import Names from '../hooks/names'
6import Fixtures from './fixtures'
7
8tier('builtin')
9
10describe('register', () => {
11 test('the start asks nothing of git and registers /diff', async ($, on) => {
12 const world = Fixtures.inRepository(on)
13
14 await $.session.start(Fixtures.SESSION)
15
16 expect(world.runs, "no git, as the built-in's start runs none").toEqual([])
17
18 expect(await $.command.run(Fixtures.DIFF), '/diff was registered').toEqual({
19 text: 'Diff panel shown',
20 })
21 })
22
23 test("git runs at the built-in's moments and no others", async ($, on) => {
24 const world = Fixtures.inRepository(on)
25
26 let read = 0
27
28 function spawnedSince(): string[] {
29 const words = world.runs
30 .slice(read)
31 .map(run => Fixtures.gitWordOf(run.argv))
32 .filter(word => word !== Fixtures.POLL_WORD)
33
34 read = world.runs.length
35
36 return words
37 }
38
39 const edit = () =>
40 $.tool.call({
41 tool: 'Edit',
42 file_path: '/work/app.ts',
43 old_string: '1',
44 new_string: '2',
45 })
46
47 on('tool.call', () => ({ result: 'done' }))
48 on('turn.complete', ($, e) => ({ text: e.answer }))
49 on('command.run', { command: 'clear' }, () => ({}))
50
51 await $.session.start(Fixtures.SESSION)
52 await $.ui.render(Fixtures.hintAt(Limits.AUTO_OPEN_MIN_COLUMNS - 1))
53 await world.clock.advance(Fixtures.SETTLE_MS)
54
55 expect(spawnedSince(), 'the start and the footer: nothing').toEqual([])
56
57 await edit()
58 await world.clock.advance(Fixtures.SETTLE_MS)
59
60 expect(
61 spawnedSince(),
62 'an edit with no room for a pane: nothing, the width is read first',
63 ).toEqual([])
64
65 await $.command.run(Fixtures.DIFF)
66 await world.clock.advance(Fixtures.SETTLE_MS)
67
68 expect(spawnedSince(), '/diff: found, then one fetch for the pane').toEqual(
69 [
70 'rev-parse --show-toplevel',
71 'status',
72 'diff --shortstat',
73 'diff --numstat',
74 'ls-files',
75 'diff -- app.ts',
76 ],
77 )
78
79 await edit()
80 await world.clock.advance(Fixtures.SETTLE_MS)
81
82 expect(spawnedSince(), 'an edit, the pane open: one fetch').toEqual([
83 'diff --shortstat',
84 'diff --numstat',
85 'ls-files',
86 'diff -- app.ts',
87 ])
88
89 await $.tool.call({ tool: 'Bash', command: 'make' })
90 await world.clock.advance(Fixtures.SETTLE_MS)
91
92 expect(spawnedSince(), 'a shell command, the pane open: one fetch').toEqual(
93 ['diff --shortstat', 'diff --numstat', 'ls-files', 'diff -- app.ts'],
94 )
95
96 await $.turn.complete({
97 answer: 'done',
98 durationMs: 1,
99 isAborted: false,
100 turnId: 't1',
101 reason: 'answer',
102 })
103
104 await world.clock.advance(Fixtures.SETTLE_MS)
105
106 expect(spawnedSince(), "a turn's end: nothing").toEqual([])
107
108 await $.command.run(Fixtures.DIFF)
109 await edit()
110 await $.tool.call({ tool: 'Bash', command: 'make' })
111 await world.clock.advance(Fixtures.SETTLE_MS)
112
113 expect(spawnedSince(), 'the pane closed by hand: nothing').toEqual([])
114
115 await $.command.run(Fixtures.CLEAR)
116 await $.command.run(Fixtures.DIFF)
117 await world.clock.advance(Fixtures.SETTLE_MS)
118
119 expect(spawnedSince()[0], '/clear forgot the repository').toBe(
120 'rev-parse --show-toplevel',
121 )
122 })
123
124 test('/diff whose probe never answers probes once more', async ($, on) => {
125 const probes: (readonly string[])[] = []
126 const clock = Fixtures.startsSession(on)
127
128 on('process.run', async ($, e) => {
129 probes.push(e.argv)
130
131 if (probes.length === 1) {
132 await clock.sleep(Limits.GIT_TIMEOUT_MS)
133
134 return { deny: Fixtures.GIT_HUNG }
135 }
136
137 return { value: Fixtures.NOT_A_REPOSITORY }
138 })
139
140 await $.session.start(Fixtures.SESSION)
141
142 const ran = $.command.run(Fixtures.DIFF)
143
144 await clock.settle()
145
146 expect(probes, "this /diff's probe, unanswered yet").toHaveLength(1)
147
148 await clock.advance(Limits.GIT_TIMEOUT_MS)
149
150 expect(await ran).toEqual({
151 text: expect.stringContaining("isn't in a git repository"),
152 })
153
154 expect(probes, 'then one more, which answered').toHaveLength(2)
155 })
156
157 test('two /diff typed together probe the repository once', async ($, on) => {
158 const world = Fixtures.inRepository(on)
159
160 await $.session.start(Fixtures.SESSION)
161
162 const [first, second] = await Promise.all([
163 $.command.run(Fixtures.DIFF),
164 $.command.run(Fixtures.DIFF),
165 ])
166
167 const probes = world.runs.filter(run =>
168 run.argv.includes('--show-toplevel'),
169 )
170
171 expect(first.text).toMatch(/^Diff panel (shown|hidden)$/)
172 expect(second.text).toMatch(/^Diff panel (shown|hidden)$/)
173 expect(probes, 'the second joined the probe in flight').toHaveLength(1)
174 })
175
176 test('outside a repository /diff says so, opens nothing', async ($, on) => {
177 const opened: string[] = []
178
179 Fixtures.startsSession(on)
180 on('process.run', () => ({ value: Fixtures.NOT_A_REPOSITORY }))
181
182 on('ui.open', ($, e, next) => {
183 opened.push(e.id)
184
185 return next(e)
186 })
187
188 await $.session.start(Fixtures.SESSION)
189
190 const { text } = await $.command.run(Fixtures.DIFF)
191
192 expect(text).toContain("isn't in a git repository")
193 expect(opened).toEqual([])
194 })
195
196 test('a git that never answers is not "no repository"', async ($, on) => {
197 Fixtures.startsSession(on)
198 on('process.run', () => ({ deny: Fixtures.GIT_HUNG }))
199
200 await $.session.start(Fixtures.SESSION)
201
202 const { text } = await $.command.run(Fixtures.DIFF)
203
204 expect(text).toContain("git didn't answer")
205 })
206
207 test('when the built-in holds /diff, the mod stands down', async ($, on) => {
208 const logged: string[] = []
209 const runs: Args<'process.run'>[] = []
210
211 mock.clock(on)
212 on('session.start', ($, e) => ({ cwd: e.cwd }))
213 on('command.register', () => ({ deny: Fixtures.BUILTIN_HOLDS }))
214 on('command.run', () => ({ text: 'the built-in /diff ran' }))
215 on('tool.call', () => ({ result: 'edited' }))
216
217 on('process.run', ($, e) => {
218 runs.push(e)
219
220 return { value: Fixtures.gitIn(e.argv) }
221 })
222
223 on('ui.log', ($, e) => {
224 logged.push(e.text)
225
226 return { value: undefined }
227 })
228
229 await $.session.start(Fixtures.SESSION)
230
231 expect(await $.command.run(Fixtures.DIFF)).toEqual({
232 text: 'the built-in /diff ran',
233 })
234
235 await $.tool.call({
236 tool: 'Edit',
237 file_path: '/work/app.ts',
238 old_string: '1',
239 new_string: '2',
240 })
241
242 expect(logged).toEqual([])
243 expect(runs, 'idle: no git for /diff or the edit').toEqual([])
244 })
245
246 test('a refusal the built-in did not cause is said aloud', async ($, on) => {
247 const logged: string[] = []
248
249 mock.clock(on)
250 on('session.start', ($, e) => ({ cwd: e.cwd }))
251
252 on('command.register', () => ({
253 deny: '32 commands are registered already',
254 }))
255
256 on('ui.log', ($, e) => {
257 logged.push(e.text)
258
259 return { value: undefined }
260 })
261
262 await $.session.start(Fixtures.SESSION)
263
264 expect(logged).toEqual([
265 'could not register /diff: diff: $.command.register: 32 commands are ' +
266 'registered already; the diff panel is unavailable this session',
267 ])
268 })
269
270 test('/diff opens the pane, unfocused, and says so', async ($, on) => {
271 const world = Fixtures.inRepository(on)
272
273 await $.session.start(Fixtures.SESSION)
274
275 expect(world.runs, 'the start ran nothing').toEqual([])
276
277 expect(await $.command.run(Fixtures.DIFF)).toEqual({
278 text: 'Diff panel shown',
279 })
280
281 expect(world.runs[0]?.argv, '/diff found the repository first').toContain(
282 '--show-toplevel',
283 )
284
285 expect(world.opened).toEqual([
286 { id: 'diff', title: 'Diff', holdToasts: true },
287 ])
288
289 await world.clock.advance(Fixtures.SETTLE_MS)
290
291 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
292
293 expect(drawn).toContain('1 file changed')
294 expect(drawn).toContain('app.ts')
295 })
296
297 test('off fullscreen, /diff opens the dialog, focused', async ($, on) => {
298 const world = Fixtures.inRepository(on)
299
300 await $.session.start(Fixtures.SESSION)
301
302 expect(await $.command.run(Fixtures.DIALOG_DIFF)).toEqual({})
303
304 expect(world.opened[0]).toEqual({
305 id: 'diff',
306 title: 'Diff',
307 holdToasts: true,
308 closeOnEscape: true,
309 rows: expect.any(Number),
310 focus: true,
311 })
312
313 expect(await $.command.run(Fixtures.DIALOG_DIFF)).toEqual({
314 text: 'Diff dialog dismissed',
315 })
316 })
317
318 test('/diff again closes the pane and says so', async ($, on) => {
319 const world = Fixtures.inRepository(on)
320
321 await $.session.start(Fixtures.SESSION)
322 await $.command.run(Fixtures.DIFF)
323
324 expect(await $.command.run(Fixtures.DIFF)).toEqual({
325 text: 'Diff panel hidden',
326 })
327
328 expect(world.closed.map(pane => pane.id)).toEqual(['diff'])
329 })
330
331 test('a wide terminal opens the pane at the first edit', async ($, on) => {
332 const world = Fixtures.inRepository(on)
333
334 on('tool.call', () => ({ result: 'edited' }))
335
336 await $.session.start(Fixtures.SESSION)
337 await $.ui.render(Fixtures.HINT)
338
339 await $.tool.call({
340 tool: 'Edit',
341 file_path: '/work/app.ts',
342 old_string: '1',
343 new_string: '2',
344 })
345
346 await world.clock.advance(Fixtures.SETTLE_MS)
347
348 expect(world.opened.map(pane => pane.id)).toEqual(['diff'])
349 })
350
351 test('on the main screen the first edit opens nothing, and /diff still opens the dialog', async ($, on) => {
352 const world = Fixtures.inRepository(on)
353
354 on('tool.call', () => ({ result: 'edited' }))
355
356 await $.session.start(Fixtures.SESSION)
357 await $.ui.render(Fixtures.MAIN_SCREEN_HINT)
358
359 await $.tool.call({
360 tool: 'Edit',
361 file_path: '/work/app.ts',
362 old_string: '1',
363 new_string: '2',
364 })
365
366 await world.clock.advance(Fixtures.SETTLE_MS)
367
368 expect(world.opened, 'a pane there would be an unasked dialog').toEqual([])
369 expect(world.runs, 'so nothing was asked of the repository').toEqual([])
370
371 expect(await $.command.run(Fixtures.DIALOG_DIFF)).toEqual({})
372
373 expect(
374 world.opened[0],
375 "the person's /diff opens the dialog, not dismisses one",
376 ).toMatchObject({ id: 'diff', focus: true })
377 })
378
379 test('a surface that does not say whether it docks a pane opens nothing at the first edit', async ($, on) => {
380 const world = Fixtures.inRepository(on)
381
382 on('tool.call', () => ({ result: 'edited' }))
383
384 await $.session.start(Fixtures.SESSION)
385 await $.ui.render(Fixtures.UNSAID_HINT)
386
387 await $.tool.call({
388 tool: 'Edit',
389 file_path: '/work/app.ts',
390 old_string: '1',
391 new_string: '2',
392 })
393
394 await world.clock.advance(Fixtures.SETTLE_MS)
395
396 expect(world.opened, 'unknown is not a dock').toEqual([])
397
398 await $.ui.render(Fixtures.HINT)
399
400 await $.tool.call({
401 tool: 'Edit',
402 file_path: '/work/app.ts',
403 old_string: '2',
404 new_string: '3',
405 })
406
407 await world.clock.advance(Fixtures.SETTLE_MS)
408
409 expect(
410 world.opened.map(pane => pane.id),
411 'once a drawing says the layout docks, the next edit opens it',
412 ).toEqual(['diff'])
413 })
414
415 test('an edit that failed or was refused opens nothing', async ($, on) => {
416 const world = Fixtures.inRepository(on)
417
418 const answers: ResultOf['tool.call'][] = [
419 { isError: true, result: 'no such text' },
420 { deny: 'not allowed' },
421 ]
422
423 const edit = () =>
424 $.tool.call({
425 tool: 'Edit',
426 file_path: '/work/app.ts',
427 old_string: '1',
428 new_string: '2',
429 })
430
431 on('tool.call', () => answers.shift() ?? { result: 'edited' })
432
433 await $.session.start(Fixtures.SESSION)
434 await $.ui.render(Fixtures.HINT)
435 await edit()
436 await edit()
437 await world.clock.advance(Fixtures.SETTLE_MS)
438
439 expect(world.opened, 'neither edit landed').toEqual([])
440 expect(world.runs, 'so nothing asked after the repository').toEqual([])
441
442 await edit()
443 await world.clock.advance(Fixtures.SETTLE_MS)
444
445 expect(
446 world.opened.map(pane => pane.id),
447 'the third did',
448 ).toEqual(['diff'])
449 })
450
451 test("only the main loop's edit opens the pane", async ($, on) => {
452 const world = Fixtures.inRepository(on)
453
454 const edit = (agentId?: string) =>
455 $.tool.call({
456 tool: 'Write',
457 file_path: '/work/app.ts',
458 content: '2',
459 ...(agentId !== undefined && { agentId }),
460 })
461
462 on('tool.call', () => ({ result: 'written' }))
463
464 await $.session.start(Fixtures.SESSION)
465 await $.ui.render(Fixtures.HINT)
466 await edit('a-explorer')
467 await world.clock.advance(Fixtures.SETTLE_MS)
468
469 expect(world.opened, 'the built-in checkpoints no subagent edit').toEqual(
470 [],
471 )
472
473 expect(world.runs, 'so nothing was asked of the repository').toEqual([])
474
475 await edit()
476 await world.clock.advance(Fixtures.SETTLE_MS)
477
478 expect(
479 world.opened.map(pane => pane.id),
480 "the main loop's",
481 ).toEqual(['diff'])
482 })
483
484 test('checkpointing off in settings: edits open nothing', async ($, on) => {
485 const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, {
486 settings: { fileCheckpointingEnabled: false },
487 })
488
489 on('tool.call', () => ({ result: 'edited' }))
490
491 await $.session.start(Fixtures.SESSION)
492 await $.ui.render(Fixtures.HINT)
493
494 await $.tool.call({
495 tool: 'Edit',
496 file_path: '/work/app.ts',
497 old_string: '1',
498 new_string: '2',
499 })
500
501 await world.clock.advance(Fixtures.SETTLE_MS)
502
503 expect(world.opened, 'the built-in opens on a checkpoint alone').toEqual([])
504
505 expect(world.runs, 'so nothing was asked of the repository').toEqual([])
506
507 expect(await $.command.run(Fixtures.DIFF)).toEqual({
508 text: 'Diff panel shown',
509 })
510 })
511
512 test('checkpointing off by variable: edits open nothing', async ($, on) => {
513 const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, {
514 env: { CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING: 'true' },
515 })
516
517 on('tool.call', () => ({ result: 'edited' }))
518
519 await $.session.start(Fixtures.SESSION)
520 await $.ui.render(Fixtures.HINT)
521
522 await $.tool.call({
523 tool: 'Edit',
524 file_path: '/work/app.ts',
525 old_string: '1',
526 new_string: '2',
527 })
528
529 await world.clock.advance(Fixtures.SETTLE_MS)
530
531 expect(world.opened).toEqual([])
532 })
533
534 test('an open left waiting is withdrawn; /diff opens it', async ($, on) => {
535 let isNarrow = true
536
537 const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, {
538 stored: { [Names.STORE_OPEN_KEY]: true },
539 isLeftWaiting: () => isNarrow,
540 })
541
542 const edit = () =>
543 $.tool.call({
544 tool: 'Edit',
545 file_path: '/work/app.ts',
546 old_string: '1',
547 new_string: '2',
548 })
549
550 on('tool.call', () => ({ result: 'edited' }))
551
552 await $.session.start(Fixtures.SESSION)
553 await $.ui.render(Fixtures.hintAt(Limits.AUTO_OPEN_MIN_COLUMNS - 1))
554 await edit()
555 await world.clock.advance(Fixtures.SETTLE_MS)
556
557 expect(
558 world.waiting.map(pane => pane.id),
559 'the edit opened',
560 ).toEqual(['diff'])
561
562 expect(
563 world.closed.map(pane => pane.id),
564 'and withdrew the pane the engine left waiting, so no resize seats it',
565 ).toEqual(['diff'])
566
567 expect(
568 world.runs.map(run => Fixtures.gitWordOf(run.argv)),
569 'the repository was found, nothing fetched for a pane no one sees',
570 ).toEqual(['rev-parse --show-toplevel'])
571
572 isNarrow = false
573
574 expect(
575 await $.command.run(Fixtures.diffAt(Limits.AUTO_OPEN_MIN_COLUMNS - 1)),
576 '/diff opens it, where a pane believed open would have been hidden',
577 ).toEqual({ text: 'Diff panel shown' })
578
579 await $.command.run(Fixtures.diffAt(Limits.AUTO_OPEN_MIN_COLUMNS - 1))
580 await edit()
581 await world.clock.advance(Fixtures.SETTLE_MS)
582
583 expect(
584 world.opened.map(pane => pane.id),
585 'closed by hand, no edit opens it again',
586 ).toEqual(['diff'])
587 })
588
589 test('an unplaced open is tried again at the next edit', async ($, on) => {
590 let isNarrow = true
591
592 const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, {
593 stored: { [Names.STORE_OPEN_KEY]: true },
594 isLeftWaiting: () => isNarrow,
595 })
596
597 const edit = () =>
598 $.tool.call({
599 tool: 'Edit',
600 file_path: '/work/app.ts',
601 old_string: '1',
602 new_string: '2',
603 })
604
605 on('tool.call', () => ({ result: 'edited' }))
606
607 await $.session.start(Fixtures.SESSION)
608 await $.ui.render(Fixtures.hintAt(Limits.AUTO_OPEN_MIN_COLUMNS - 1))
609 await edit()
610 await world.clock.advance(Fixtures.SETTLE_MS)
611
612 expect(world.opened, 'the first try waited and was withdrawn').toEqual([])
613
614 isNarrow = false
615
616 await edit()
617 await world.clock.advance(Fixtures.SETTLE_MS)
618
619 expect(
620 world.opened.map(pane => pane.id),
621 'the next edit opened it, as the built-in reads the width again then',
622 ).toEqual(['diff'])
623 })
624
625 test('/clear closes the pane it finds open', async ($, on) => {
626 const world = Fixtures.inRepository(on)
627
628 on('command.run', { command: 'clear' }, () => ({}))
629
630 await $.session.start(Fixtures.SESSION)
631 await $.command.run(Fixtures.DIFF)
632 await $.command.run(Fixtures.CLEAR)
633
634 expect(world.closed.map(pane => pane.id)).toEqual(['diff'])
635 })
636
637 test('ask attaches the hunks on screen and calls no tool', async ($, on) => {
638 const world = Fixtures.inRepository(on, Fixtures.oneSecret())
639 const called: Args<'tool.call'>[] = []
640
641 on('tool.call', ($, e) => {
642 called.push(e)
643
644 return { result: 'called' }
645 })
646
647 on('prompt.submit', ($, e) => ({ text: e.text, context: e.context }))
648
649 await $.session.start(Fixtures.WORKTREE_SESSION)
650 await $.command.run(Fixtures.DIFF)
651 await world.clock.advance(Fixtures.SETTLE_MS)
652 await $.ui.render(Fixtures.PANE)
653 await $.ui.press({ plugin: 'diff', key: 'ask:.env' })
654 await world.clock.settle()
655
656 const armed = Fixtures.jsonOf(await $.ui.render(Fixtures.PANE))
657
658 const submitted = await $.prompt.submit(Fixtures.typedPromptOf('why?'))
659
660 expect(armed).toContain('asked ✓')
661 expect(called).toEqual([])
662
663 expect(world.statuses.at(-2)).toBe(
664 '.env rides your next prompt (press asked ✓ to drop it)',
665 )
666
667 expect(submitted).toMatchObject({
668 context: [
669 'The user attached the diff of .env from the diff pane to this ' +
670 'prompt:\n@@ -1 +1 @@\n-KEY=old\n+KEY=new',
671 ],
672 })
673 })
674
675 test('an ask with no room in the context is dropped', async ($, on) => {
676 const world = Fixtures.inRepository(on, Fixtures.oneSecret())
677 const full = 'x'.repeat(Limits.PROMPT_CONTEXT_MAX_CHARS)
678 const reached: (readonly string[] | undefined)[] = []
679
680 on('prompt.submit', ($, e) => {
681 reached.push(e.context)
682
683 return { text: e.text }
684 })
685
686 await $.session.start(Fixtures.WORKTREE_SESSION)
687 await $.command.run(Fixtures.DIFF)
688 await world.clock.advance(Fixtures.SETTLE_MS)
689 await $.ui.render(Fixtures.PANE)
690 await $.ui.press({ plugin: 'diff', key: 'ask:.env' })
691 await world.clock.settle()
692
693 await $.prompt.submit(Fixtures.typedPromptOf('why?', [full]))
694 await $.prompt.submit(Fixtures.typedPromptOf('and now?'))
695
696 expect(reached).toEqual([[full], undefined])
697
698 expect(world.statuses.at(-1)).toBe(
699 ".env's diff did not fit in the prompt and was dropped",
700 )
701 })
702
703 test('a worktree opens on the base it kept', async ($, on) => {
704 const world = Fixtures.inRepository(on, Fixtures.oneSecret(), {
705 stored: { 'base:/main/wt': 'uncommitted' },
706 })
707
708 await $.session.start(Fixtures.WORKTREE_SESSION)
709 await $.command.run(Fixtures.DIFF)
710 await world.clock.advance(Fixtures.SETTLE_MS)
711
712 expect(
713 Fixtures.stringsOf(await $.ui.render(Fixtures.PANE)),
714 "the built-in's base line under the header",
715 ).toContain('uncommitted (vs HEAD)')
716 })
717
718 test('no repository is an answer kept until /clear', async ($, on) => {
719 const { 'rev-parse --path-format=absolute': worktree = '', ...notYet } =
720 Fixtures.oneSecret()
721
722 const script: Record<string, string> = notYet
723 const world = Fixtures.inRepository(on, script)
724
725 const probesOf = () =>
726 world.runs.filter(run => run.argv.includes('--show-toplevel')).length
727
728 on('command.run', { command: 'clear' }, () => ({}))
729
730 await $.session.start(Fixtures.WORKTREE_SESSION)
731
732 const before = await $.command.run(Fixtures.DIFF)
733
734 script['rev-parse --path-format=absolute'] = worktree
735
736 const again = await $.command.run(Fixtures.DIFF)
737 const probesKept = probesOf()
738
739 await $.command.run(Fixtures.CLEAR)
740
741 const after = await $.command.run(Fixtures.DIFF)
742
743 await world.clock.advance(Fixtures.SETTLE_MS)
744 await $.ui.render(Fixtures.PANE)
745
746 script['rev-parse --path-format=absolute'] = Fixtures.ELSEWHERE_LINES
747
748 const third = $.command.run(Fixtures.DIFF)
749
750 await world.clock.advance(Fixtures.SETTLE_MS)
751 await third
752
753 expect(before.text).toContain("isn't in a git repository")
754
755 expect(again.text, "kept, as the built-in's answer is").toContain(
756 "isn't in a git repository",
757 )
758
759 expect(probesKept, 'one probe answered both').toBe(1)
760 expect(after.text, '/clear forgot it').toBe('Diff panel shown')
761 expect(world.opened[0]?.id).toBe('diff')
762 expect(probesOf(), 'then one more, pinned from there on').toBe(2)
763
764 expect(
765 world.runs.some(run => run.argv.includes('--git-dir=/else/.git')),
766 ).toBe(false)
767
768 expect(
769 world.runs
770 .filter(run => run.argv.includes('--numstat'))
771 .every(run => run.argv[1] === Fixtures.PINNED_LEAD[1]),
772 ).toBe(true)
773 })
774
775 test('a repository that appears later opens on first edit', async ($, on) => {
776 const { 'rev-parse --path-format=absolute': worktree = '', ...notYet } =
777 Fixtures.oneSecret()
778
779 const script: Record<string, string> = notYet
780 const world = Fixtures.inRepository(on, script)
781
782 on('tool.call', () => ({ result: 'edited' }))
783
784 await $.session.start(Fixtures.WORKTREE_SESSION)
785 await $.ui.render(Fixtures.HINT)
786
787 script['rev-parse --path-format=absolute'] = worktree
788
789 await $.tool.call({
790 tool: 'Edit',
791 file_path: '/main/wt/.env',
792 old_string: 'old',
793 new_string: 'new',
794 })
795
796 await world.clock.advance(Fixtures.SETTLE_MS)
797
798 expect(world.opened.map(pane => pane.id)).toEqual(['diff'])
799 })
800
801 test('a git that cannot start is an answer, not a timeout', async ($, on) => {
802 const runs: Args<'process.run'>[] = []
803
804 Fixtures.startsSession(on)
805
806 on('process.run', ($, e) => {
807 runs.push(e)
808
809 return e.argv.includes('--show-toplevel')
810 ? { deny: Fixtures.FAILED_START }
811 : { value: Fixtures.gitIn(e.argv, Fixtures.oneSecret()) }
812 })
813
814 await $.session.start(Fixtures.WORKTREE_SESSION)
815
816 const { text } = await $.command.run(Fixtures.DIFF)
817 const probes = runs.filter(run => run.argv.includes('--show-toplevel'))
818
819 expect(text).toContain("isn't in a git repository")
820 expect(probes, "this /diff's alone; no retry").toHaveLength(1)
821 })
822
823 test('/diff on a narrow terminal: the resize line', async ($, on) => {
824 const world = Fixtures.inRepository(on, Fixtures.oneSecret())
825
826 await $.session.start(Fixtures.WORKTREE_SESSION)
827
828 const narrow = await $.command.run(
829 Fixtures.diffAt(Limits.OPEN_MIN_COLUMNS - 1),
830 )
831
832 const openedNarrow = [...world.opened]
833
834 await $.command.run(Fixtures.diffAt(Limits.OPEN_MIN_COLUMNS))
835
836 expect(narrow.text).toContain(
837 'Resize your terminal to at least 110 columns to show the diff panel',
838 )
839
840 expect(openedNarrow).toEqual([])
841 expect(world.opened.map(pane => pane.id)).toEqual([Names.PANE_ID])
842 })
843})