Files
orca/src/shared/git-progress-records.test.ts
T
Brennan Benson 131986aa16 feat(worktree): show checkout progress while a worktree is created (#24093)
* feat(worktree): show checkout progress while a worktree is created

A local create spends most of its time in the checkout `git worktree add`
performs, and the card only said "Creating worktree…" for that whole time.
Git already writes its "Updating files: N%" meter into our stderr pipe, so
the add call reads it passively (same command line and environment) and the
create card shows "Checking out files… N%" with a thin bar. All six create
labels are translated so a create never switches language mid-flight.

Rebuilt on main from the version stacked on the create-gating branch. On
main a create can also claim a prepared checkout; that path never runs
`git worktree add`, so it keeps today's "Creating worktree…" label with no
bar, and a missed claim falls back to the plain add, which shows the bar.

* test(worktree): pin that finalizing a prepared checkout reports no checkout progress

Claiming a prepared checkout never runs `git worktree add`, so the create
card keeps its label with no bar; this pins that at the finalize step,
including the retarget `reset --hard`.

* test(worktree): keep only the checkout-progress tests that pin a contract

Drops unit tests that mirror the implementation; the real-git flow, the parser
captures, the cross-version Git contract and the create card test remain.

* test(worktree): pin the checkout meter's path from git to the create card

* test(worktree): merge the narrow-terminal capture's pipe chunks
2026-10-15 18:14:56 -07:00

90 lines
4.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { createGitProgressRecordReader, type GitProgressRecord } from './git-progress-records'
// Captured through a Node execFile pipe from `git worktree add` of a 25-file repo with
// GIT_PROGRESS_DELAY=0, chunked exactly as the pipe delivered them.
const GIT_2_25_5_CHUNKS: string[] = [
'Updating files: 4% (1/25)\rUpdating files: 8% (2/25)\rUpdating files: 12% (3/25)\r',
'Updating files: 16% (4/25)\rUpdating files: 20% (5/25)\rUpdating files: 24% (6/25)\rUpdating files: 28% (7/25)\rUpdating files: 32% (8/25)\rUpdating files: 36% (9/25)\rUpdating files: 40% (10/25)\r',
'Updating files: 44% (11/25)\r',
'Updating files: 48% (12/25)\rUpdating files: 52% (13/25)\rUpdating files: 56% (14/25)\rUpdating files: 60% (15/25)\rUpdating files: 64% (16/25)\rUpdating files: 68% (17/25)\rUpdating files: 72% (18/25)\rUpdating files: 76% (19/25)\rUpdating files: 80% (20/25)\rUpdating files: 84% (21/25)\rUpdating files: 88% (22/25)\rUpdating files: 92% (23/25)\rUpdating files: 96% (24/25)\rUpdating files: 100% (25/25)\rUpdating files: 100% (25/25), done.\n'
]
// Same capture with COLUMNS=20 (pipe chunks merged): git prints the title once, then bare counters.
const GIT_2_25_5_NARROW_CHUNKS: string[] = [
'Updating files: \n 4% (1/25)\r',
' 8% (2/25)\r 12% (3/25)\r 16% (4/25)\r 20% (5/25)\r 24% (6/25)\r 28% (7/25)\r 32% (8/25)\r 36% (9/25)\r 40% (10/25)\r 44% (11/25)\r 48% (12/25)\r 52% (13/25)\r 56% (14/25)\r 60% (15/25)\r 64% (16/25)\r 68% (17/25)\r 72% (18/25)\r 76% (19/25)\r 80% (20/25)\r 84% (21/25)\r 88% (22/25)\r 92% (23/25)\r 96% (24/25)\r 100% (25/25)\r',
' 100% (25/25), done.\n'
]
function readAll(chunks: string[]): GitProgressRecord[] {
const records: GitProgressRecord[] = []
const read = createGitProgressRecordReader('Updating files', (record) => records.push(record))
for (const chunk of chunks) {
read(chunk)
}
return records
}
function expectFullCheckout(records: GitProgressRecord[]): void {
expect(records.map((record) => record.completed)).toEqual([
...Array.from({ length: 25 }, (_, index) => index + 1),
25
])
expect(records.every((record) => record.total === 25)).toBe(true)
expect(records.at(-2)).toEqual({ percent: 100, completed: 25, total: 25, done: false })
expect(records.at(-1)).toEqual({ percent: 100, completed: 25, total: 25, done: true })
expect(records.filter((record) => record.done)).toHaveLength(1)
}
describe('createGitProgressRecordReader', () => {
it.each([
['2.25.5', GIT_2_25_5_CHUNKS],
['2.25.5 with a narrow COLUMNS', GIT_2_25_5_NARROW_CHUNKS]
])('reads every record git %s wrote, as the pipe chunked it', (_version, chunks) => {
expectFullCheckout(readAll(chunks))
})
it.each([
['2.25.5', GIT_2_25_5_CHUNKS],
['2.25.5 with a narrow COLUMNS', GIT_2_25_5_NARROW_CHUNKS]
])('reads the same records from git %s wherever a chunk boundary falls', (_version, chunks) => {
const stream = chunks.join('')
const expected = readAll([stream])
for (let cut = 1; cut < stream.length; cut += 1) {
expect(readAll([stream.slice(0, cut), stream.slice(cut)])).toEqual(expected)
}
expect(readAll([...stream])).toEqual(expected)
})
it('accepts CRLF line ends, even split between chunks', () => {
expect(readAll(['Updating files:\r', '\n 50% (1/2)\r\n 100% (2/2), done.\r\n'])).toEqual([
{ percent: 50, completed: 1, total: 2, done: false },
{ percent: 100, completed: 2, total: 2, done: true }
])
})
it('ignores other stderr, including other progress meters', () => {
expect(
readAll([
"Preparing worktree (new branch 'feature')\n",
'HEAD is now at 1234567 init\n',
'Filtering content: 50% (1/2)\r',
'hook: Updating files is not a record\n',
' 40% (2/5)\n',
'Updating files: 0% (0/0)\n'
])
).toEqual([])
})
it('keeps only a bounded tail of a line that never ends', () => {
const records: GitProgressRecord[] = []
const read = createGitProgressRecordReader('Updating files', (record) => records.push(record))
// The record's head falls out of the bounded buffer before its line ends.
read(`Updating files: 10% (1/10)${' '.repeat(300)}`)
read('\r')
expect(records).toEqual([])
read('Updating files: 20% (2/10)\r')
expect(records).toEqual([{ percent: 20, completed: 2, total: 10, done: false }])
})
})