Skip to content

Commit a83dcec

Browse files
committed
fix: progress tests pass on Bun by injecting context directly
Replace vi.mock-based context override with direct ctx parameter injection in createStoreProgress/createRestoreProgress. The vi.mock for getCliContext was not being applied in Bun's module loader, causing pipe-mode detection to return a no-op tracker. Also adds CLAUDE.md with zero-tolerance test policy.
1 parent 30a89da commit a83dcec

3 files changed

Lines changed: 44 additions & 17 deletions

File tree

CLAUDE.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# CLAUDE.md — Project Instructions
2+
3+
## Quality Policy
4+
5+
**Zero tolerance for failing tests.** Pre-existing failures are not acceptable. If any test fails on any supported runtime (Node, Bun, Deno), it must be fixed before proceeding. Never skip, ignore, or defer a failing test.
6+
7+
## Supported Runtimes
8+
9+
All code must pass on all three runtimes:
10+
- Node.js 22.x (primary)
11+
- Bun (via Docker: `docker compose run --build --rm test-bun`)
12+
- Deno (via Docker: `docker compose run --build --rm test-deno`)
13+
14+
## Test Commands
15+
16+
- `npm test` — unit tests (Node)
17+
- `npx eslint .` — lint (0 errors required)
18+
- `docker compose run --build --rm test-bun bunx vitest run test/unit` — Bun unit tests
19+
- `docker compose run --build --rm test-deno deno run -A npm:vitest run test/unit` — Deno unit tests
20+
- `docker compose run --build --rm test-bun bunx vitest run test/integration` — Bun integration tests
21+
- `docker compose run --build --rm test-deno deno run -A npm:vitest run test/integration` — Deno integration tests
22+
23+
## Release Checklist
24+
25+
Before tagging a release, ALL of the following must pass:
26+
1. `npx eslint .` — 0 errors
27+
2. `npm test` — all tests pass (Node)
28+
3. Bun unit + integration tests pass
29+
4. Deno unit + integration tests pass
30+
5. `npm pack --dry-run` — clean
31+
6. `npx jsr publish --dry-run --allow-dirty` — clean

bin/ui/progress.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ function formatBytes(bytes) {
3232
* @param {boolean} [options.quiet] - Suppress all progress output.
3333
* @returns {{ attach(observer: { on(event: string, fn: Function): void, removeListener(event: string, fn: Function): void }): void, detach(): void }}
3434
*/
35-
export function createStoreProgress({ filePath, chunkSize, quiet, fileSize: providedSize }) {
35+
export function createStoreProgress({ filePath, chunkSize, quiet, fileSize: providedSize, ctx: providedCtx }) {
3636
if (quiet) {
3737
return { attach() {}, detach() {} };
3838
}
3939

40-
const ctx = getCliContext();
40+
const ctx = providedCtx || getCliContext();
4141
if (ctx.mode === 'pipe') {
4242
return { attach() {}, detach() {} };
4343
}
@@ -60,12 +60,12 @@ export function createStoreProgress({ filePath, chunkSize, quiet, fileSize: prov
6060
* @param {boolean} [options.quiet] - Suppress all progress output.
6161
* @returns {{ attach(observer: { on(event: string, fn: Function): void, removeListener(event: string, fn: Function): void }): void, detach(): void }}
6262
*/
63-
export function createRestoreProgress({ totalChunks, quiet }) {
63+
export function createRestoreProgress({ totalChunks, quiet, ctx: providedCtx }) {
6464
if (quiet || totalChunks === 0) {
6565
return { attach() {}, detach() {} };
6666
}
6767

68-
const ctx = getCliContext();
68+
const ctx = providedCtx || getCliContext();
6969
if (ctx.mode === 'pipe') {
7070
return { attach() {}, detach() {} };
7171
}

test/unit/cli/progress.test.js

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import { describe, it, expect, vi } from 'vitest';
1+
import { describe, it, expect } from 'vitest';
22
import EventEmitterObserver from '../../../src/infrastructure/adapters/EventEmitterObserver.js';
3+
import { createStoreProgress, createRestoreProgress } from '../../../bin/ui/progress.js';
34
import { makeCtx } from './_testContext.js';
45

5-
vi.mock('../../../bin/ui/context.js', () => ({
6-
getCliContext: () => makeCtx('static'),
7-
}));
8-
9-
const { createStoreProgress, createRestoreProgress } = await import('../../../bin/ui/progress.js');
10-
116
const FILE_SIZE = 5 * 256 * 1024;
7+
const ctx = makeCtx('static');
128

139
describe('createStoreProgress', () => {
1410
it('returns no-op when quiet is true', () => {
15-
const p = createStoreProgress({ filePath: 'test.bin', chunkSize: 256 * 1024, quiet: true });
11+
const p = createStoreProgress({ filePath: 'test.bin', chunkSize: 256 * 1024, quiet: true, ctx });
1612
const emitter = new EventEmitterObserver();
1713
p.attach(emitter);
1814
emitter.metric('chunk', { action: 'stored', index: 0, size: 256 * 1024 });
@@ -21,7 +17,7 @@ describe('createStoreProgress', () => {
2117
});
2218

2319
it('attaches and detaches from EventEmitter', () => {
24-
const p = createStoreProgress({ filePath: 'test.bin', chunkSize: 256 * 1024, quiet: false, fileSize: FILE_SIZE });
20+
const p = createStoreProgress({ filePath: 'test.bin', chunkSize: 256 * 1024, quiet: false, fileSize: FILE_SIZE, ctx });
2521
const emitter = new EventEmitterObserver();
2622
p.attach(emitter);
2723
expect(emitter.listenerCount('chunk:stored')).toBe(1);
@@ -30,7 +26,7 @@ describe('createStoreProgress', () => {
3026
});
3127

3228
it('tracks chunk events without throwing', () => {
33-
const p = createStoreProgress({ filePath: 'test.bin', chunkSize: 256 * 1024, quiet: false, fileSize: FILE_SIZE });
29+
const p = createStoreProgress({ filePath: 'test.bin', chunkSize: 256 * 1024, quiet: false, fileSize: FILE_SIZE, ctx });
3430
const emitter = new EventEmitterObserver();
3531
p.attach(emitter);
3632
for (let i = 0; i < 5; i++) {
@@ -43,23 +39,23 @@ describe('createStoreProgress', () => {
4339

4440
describe('createRestoreProgress', () => {
4541
it('returns no-op when quiet is true', () => {
46-
const p = createRestoreProgress({ totalChunks: 5, quiet: true });
42+
const p = createRestoreProgress({ totalChunks: 5, quiet: true, ctx });
4743
const emitter = new EventEmitterObserver();
4844
p.attach(emitter);
4945
p.detach();
5046
expect(emitter.listenerCount('chunk:restored')).toBe(0);
5147
});
5248

5349
it('returns no-op for 0-chunk manifests', () => {
54-
const p = createRestoreProgress({ totalChunks: 0, quiet: false });
50+
const p = createRestoreProgress({ totalChunks: 0, quiet: false, ctx });
5551
const emitter = new EventEmitterObserver();
5652
p.attach(emitter);
5753
p.detach();
5854
expect(emitter.listenerCount('chunk:restored')).toBe(0);
5955
});
6056

6157
it('attaches and detaches from EventEmitter', () => {
62-
const p = createRestoreProgress({ totalChunks: 3, quiet: false });
58+
const p = createRestoreProgress({ totalChunks: 3, quiet: false, ctx });
6359
const emitter = new EventEmitterObserver();
6460
p.attach(emitter);
6561
expect(emitter.listenerCount('chunk:restored')).toBe(1);

0 commit comments

Comments
 (0)