Skip to content

Commit eade981

Browse files
committed
test: follow the token speed move and rate-independent shimmer checks
1 parent 70bb887 commit eade981

4 files changed

Lines changed: 57 additions & 37 deletions

File tree

apps/pythinker-code/src/tui/runtime/footer/footer-model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ function selectActivityRow(state: FooterState): FooterActivityRowViewModel {
464464
});
465465
}
466466

467-
function selectStatusItemParts(
467+
export function selectStatusItemParts(
468468
state: FooterState,
469469
clockMs: number,
470470
statusLine: StatusLineConfig,

apps/pythinker-code/test/tui/components/dialogs/compaction.test.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,26 +81,34 @@ describe('CompactionComponent', () => {
8181
const firstHeader = firstRender.find((line) => strip(line).includes('Compacting conversation…'));
8282
const firstBar = firstRender.find((line) => strip(line).includes('▱'));
8383

84-
vi.advanceTimersByTime(BRAILLE_SPINNER_INTERVAL_MS);
85-
const animationRender = component.render(120);
86-
const animationHeader = animationRender.find((line) =>
87-
strip(line).includes('Compacting conversation…'),
88-
);
89-
const animationBar = animationRender.find((line) => strip(line).includes('▱'));
90-
9184
expect(firstHeader).toBeDefined();
9285
expect(firstBar).toBeDefined();
93-
expect(animationHeader).toBeDefined();
94-
expect(animationBar).toBeDefined();
95-
expect(strip(animationHeader)).toBe(strip(firstHeader));
96-
expect(animationHeader).not.toBe(firstHeader);
97-
expect(animationBar).toBe(firstBar);
86+
87+
const headerSamples = [firstHeader];
88+
const barSamples = [firstBar];
89+
const shimmerSampleCount = 12;
90+
for (let sample = 0; sample < shimmerSampleCount; sample++) {
91+
vi.advanceTimersByTime(BRAILLE_SPINNER_INTERVAL_MS);
92+
const render = component.render(120);
93+
headerSamples.push(
94+
render.find((line) => strip(line).includes('Compacting conversation…')),
95+
);
96+
barSamples.push(render.find((line) => strip(line).includes('▱')));
97+
}
98+
99+
expect(headerSamples.every((header) => header !== undefined)).toBe(true);
100+
expect(barSamples.every((bar) => bar !== undefined)).toBe(true);
101+
expect(new Set(headerSamples.map(strip))).toEqual(new Set([strip(firstHeader)]));
102+
expect(new Set(headerSamples).size).toBeGreaterThan(1);
103+
expect(new Set(barSamples)).toEqual(new Set([firstBar]));
98104
expect(strip(firstBar).trimEnd()).toBe(` ${'▰'.repeat(11)}${'▱'.repeat(29)} 27%`);
99105
expect(firstBar).toContain(currentTheme.fg('primary', '▰'.repeat(11)));
100106
expect(firstBar).not.toContain(currentTheme.fg('progressFill', '▰'.repeat(11)));
101107
expect(firstBar).toContain(currentTheme.fg('progressEmpty', '▱'.repeat(29)));
102108

103-
vi.advanceTimersByTime(1_000 - BRAILLE_SPINNER_INTERVAL_MS);
109+
vi.advanceTimersByTime(
110+
1_000 - BRAILLE_SPINNER_INTERVAL_MS * shimmerSampleCount,
111+
);
104112
const elapsedRender = component.render(120);
105113
const elapsedHeader = elapsedRender.find((line) =>
106114
strip(line).includes('Compacting conversation…'),

apps/pythinker-code/test/tui/components/messages/thinking.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,18 @@ describe('ThinkingComponent', () => {
8989
expect(strip(secondHeader ?? '')).toBe(`⠙ ${formatThinkingSpinnerLabel()}`);
9090

9191
vi.advanceTimersByTime(BRAILLE_SPINNER_INTERVAL_MS * (BRAILLE_SPINNER_FRAMES.length - 1));
92-
const shimmerHeader = component.render(80)[1];
93-
expect(strip(shimmerHeader ?? '')).toBe(strip(firstHeader ?? ''));
94-
expect(shimmerHeader).not.toBe(firstHeader);
92+
const fullCycleHeader = component.render(80)[1];
93+
expect(strip(fullCycleHeader ?? '')).toBe(strip(firstHeader ?? ''));
94+
95+
const shimmerHeaders = [firstHeader, fullCycleHeader];
96+
for (let sample = 0; sample < 3; sample++) {
97+
vi.advanceTimersByTime(BRAILLE_SPINNER_INTERVAL_MS * BRAILLE_SPINNER_FRAMES.length);
98+
shimmerHeaders.push(component.render(80)[1]);
99+
}
100+
expect(shimmerHeaders.map((header) => strip(header ?? ''))).toEqual(
101+
shimmerHeaders.map(() => strip(firstHeader ?? '')),
102+
);
103+
expect(new Set(shimmerHeaders).size).toBeGreaterThan(1);
95104

96105
component.finalize();
97106
requestRender.mockClear();

apps/pythinker-code/test/tui/controllers/session-event-handler-goal-queue.test.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
reduceFooterState,
1010
selectFooterViewModel,
1111
selectStatusBarExtras,
12+
selectStatusItemParts,
1213
type FooterEvent,
1314
} from '#/tui/runtime/footer/footer-model';
1415
import { SessionEventHandler } from '#/tui/controllers/session-event-handler';
@@ -180,6 +181,8 @@ function makeTokenSpeedHost() {
180181
footer,
181182
renderStatusBarExtras: () =>
182183
selectStatusBarExtras(footerState, Date.now(), DEFAULT_STATUS_LINE_CONFIG).join(' '),
184+
renderStatusBarModel: () =>
185+
selectStatusItemParts(footerState, Date.now(), DEFAULT_STATUS_LINE_CONFIG).model,
183186
};
184187
}
185188

@@ -447,7 +450,7 @@ describe('SessionEventHandler token speed', () => {
447450
])('updates a live estimate from $name and replaces it with exact usage', ({ event }) => {
448451
vi.useFakeTimers();
449452
vi.setSystemTime(0);
450-
const { host, footer, renderStatusBarExtras } = makeTokenSpeedHost();
453+
const { host, footer, renderStatusBarModel } = makeTokenSpeedHost();
451454
const handler = new SessionEventHandler(host);
452455
try {
453456
handler.handleEvent(
@@ -466,7 +469,7 @@ describe('SessionEventHandler token speed', () => {
466469
vi.setSystemTime(3_000);
467470
handler.handleEvent(event('x'.repeat(400)), vi.fn());
468471

469-
expect(renderStatusBarExtras()).toContain('~100.0 t/s');
472+
expect(renderStatusBarModel()).toContain('~100.0 t/s');
470473

471474
handler.handleEvent(
472475
{
@@ -485,8 +488,8 @@ describe('SessionEventHandler token speed', () => {
485488
},
486489
vi.fn(),
487490
);
488-
expect(renderStatusBarExtras()).toContain('42.0 t/s');
489-
expect(renderStatusBarExtras()).not.toContain('~42.0 t/s');
491+
expect(renderStatusBarModel()).toContain('42.0 t/s');
492+
expect(renderStatusBarModel()).not.toContain('~42.0 t/s');
490493
} finally {
491494
footer.dispose();
492495
}
@@ -495,7 +498,7 @@ describe('SessionEventHandler token speed', () => {
495498
it('keeps concurrent agent stream estimates separate', () => {
496499
vi.useFakeTimers();
497500
vi.setSystemTime(0);
498-
const { host, footer, renderStatusBarExtras } = makeTokenSpeedHost();
501+
const { host, footer, renderStatusBarModel } = makeTokenSpeedHost();
499502
const handler = new SessionEventHandler(host);
500503
const event = (agentId: string, delta: string) => ({
501504
type: 'assistant.delta' as const,
@@ -510,18 +513,18 @@ describe('SessionEventHandler token speed', () => {
510513
handler.handleEvent(event('agent-b', 'abcd'), vi.fn());
511514
vi.setSystemTime(1_000);
512515
handler.handleEvent(event('agent-a', 'x'.repeat(400)), vi.fn());
513-
expect(renderStatusBarExtras()).toContain('~100.0 t/s');
516+
expect(renderStatusBarModel()).toContain('~100.0 t/s');
514517

515518
vi.setSystemTime(1_500);
516519
handler.handleEvent(event('agent-b', 'x'.repeat(200)), vi.fn());
517-
expect(renderStatusBarExtras()).toContain('~50.0 t/s');
520+
expect(renderStatusBarModel()).toContain('~50.0 t/s');
518521
} finally {
519522
footer.dispose();
520523
}
521524
});
522525

523526
it('uses the latest valid main or child completed stream', () => {
524-
const { host, footer, renderStatusBarExtras } = makeTokenSpeedHost();
527+
const { host, footer, renderStatusBarModel } = makeTokenSpeedHost();
525528
const handler = new SessionEventHandler(host);
526529
try {
527530
handler.handleEvent(
@@ -541,7 +544,7 @@ describe('SessionEventHandler token speed', () => {
541544
},
542545
vi.fn(),
543546
);
544-
expect(renderStatusBarExtras()).toContain('42.0 t/s');
547+
expect(renderStatusBarModel()).toContain('42.0 t/s');
545548

546549
handler.handleEvent(
547550
{
@@ -560,7 +563,7 @@ describe('SessionEventHandler token speed', () => {
560563
},
561564
vi.fn(),
562565
);
563-
expect(renderStatusBarExtras()).toContain('50.0 t/s');
566+
expect(renderStatusBarModel()).toContain('50.0 t/s');
564567
} finally {
565568
footer.dispose();
566569
}
@@ -593,7 +596,7 @@ describe('SessionEventHandler token speed', () => {
593596
output: 10,
594597
}, Number.NaN],
595598
] as const)('ignores %s', (_label, usage, llmStreamDurationMs) => {
596-
const { host, footer, renderStatusBarExtras } = makeTokenSpeedHost();
599+
const { host, footer, renderStatusBarModel } = makeTokenSpeedHost();
597600
const handler = new SessionEventHandler(host);
598601
try {
599602
handler.handleEvent(
@@ -613,7 +616,7 @@ describe('SessionEventHandler token speed', () => {
613616
},
614617
vi.fn(),
615618
);
616-
expect(renderStatusBarExtras()).toContain('42.0 t/s');
619+
expect(renderStatusBarModel()).toContain('42.0 t/s');
617620

618621
handler.handleEvent(
619622
{
@@ -627,14 +630,14 @@ describe('SessionEventHandler token speed', () => {
627630
},
628631
vi.fn(),
629632
);
630-
expect(renderStatusBarExtras()).toContain('42.0 t/s');
633+
expect(renderStatusBarModel()).toContain('42.0 t/s');
631634
} finally {
632635
footer.dispose();
633636
}
634637
});
635638

636639
it('clears completed throughput when the turn ends', () => {
637-
const { host, footer, renderStatusBarExtras } = makeTokenSpeedHost();
640+
const { host, footer, renderStatusBarModel } = makeTokenSpeedHost();
638641
const handler = new SessionEventHandler(host);
639642
try {
640643
handler.handleEvent(
@@ -654,18 +657,18 @@ describe('SessionEventHandler token speed', () => {
654657
},
655658
vi.fn(),
656659
);
657-
expect(renderStatusBarExtras()).toContain('42.0 t/s');
660+
expect(renderStatusBarModel()).toContain('42.0 t/s');
658661

659662
handler.handleEvent(turnEndedEvent(), vi.fn());
660663

661-
expect(renderStatusBarExtras()).not.toContain('t/s');
664+
expect(renderStatusBarModel()).not.toContain('t/s');
662665
} finally {
663666
footer.dispose();
664667
}
665668
});
666669

667670
it('ignores replayed completion metrics and clears on runtime reset', () => {
668-
const { host, footer, renderStatusBarExtras } = makeTokenSpeedHost();
671+
const { host, footer, renderStatusBarModel } = makeTokenSpeedHost();
669672
const handler = new SessionEventHandler(host);
670673
try {
671674
handler.handleEvent(
@@ -685,7 +688,7 @@ describe('SessionEventHandler token speed', () => {
685688
},
686689
vi.fn(),
687690
);
688-
expect(renderStatusBarExtras()).toContain('10.0 t/s');
691+
expect(renderStatusBarModel()).toContain('10.0 t/s');
689692

690693
host.state.appState.isReplaying = true;
691694
handler.handleEvent(
@@ -705,10 +708,10 @@ describe('SessionEventHandler token speed', () => {
705708
},
706709
vi.fn(),
707710
);
708-
expect(renderStatusBarExtras()).toContain('10.0 t/s');
711+
expect(renderStatusBarModel()).toContain('10.0 t/s');
709712

710713
handler.resetRuntimeState();
711-
expect(renderStatusBarExtras()).not.toContain('t/s');
714+
expect(renderStatusBarModel()).not.toContain('t/s');
712715
} finally {
713716
footer.dispose();
714717
}

0 commit comments

Comments
 (0)