diff --git a/src/components/AgentChatView.client.test.tsx b/src/components/AgentChatView.client.test.tsx index f9f3bde9..bb782a8f 100644 --- a/src/components/AgentChatView.client.test.tsx +++ b/src/components/AgentChatView.client.test.tsx @@ -123,6 +123,9 @@ afterEach(() => { container.remove(); }); +// Exact counts are formatted in the host's locale, so the expected text is too. +const n = (value: number) => value.toLocaleString(); + describe('Codex chat view', () => { it('shows context capacity, remaining tokens, and over-limit usage independently of session totals', async () => { dispose = render(() => , container); @@ -132,7 +135,7 @@ describe('Codex chat view', () => { expect(container.querySelector('.codex-chat-context')?.textContent).toContain('Context —'); mocks.channel?.onmessage?.(state({ contextUsage: { usedTokens: 50000, maxTokens: 200000 } })); expect(meter()?.textContent).toContain('Context 25% · 150K left'); - expect(meter()?.title).toContain('50,000 of 200,000'); + expect(meter()?.title).toContain(`${n(50_000)} of ${n(200_000)}`); expect(meter()?.getAttribute('aria-valuenow')).toBe('50000'); mocks.channel?.onmessage?.(state({ contextUsage: { usedTokens: 190000, maxTokens: 200000 } })); expect(meter()?.dataset.level).toBe('high'); @@ -140,7 +143,7 @@ describe('Codex chat view', () => { expect(meter()?.textContent).toContain('Context 105% · 0 left'); expect(meter()?.dataset.level).toBe('full'); expect(meter()?.getAttribute('aria-valuenow')).toBe('200000'); - expect(meter()?.getAttribute('aria-valuetext')).toContain('210,000 of 200,000'); + expect(meter()?.getAttribute('aria-valuetext')).toContain(`${n(210_000)} of ${n(200_000)}`); mocks.channel?.onmessage?.(state({ contextUsage: { usedTokens: 0, maxTokens: 200000 } })); expect(meter()?.textContent).toContain('Context 0% · 200K left'); mocks.channel?.onmessage?.(state({ threadId: 'new-session' })); @@ -163,8 +166,8 @@ describe('Codex chat view', () => { }), ); expect(counter()?.textContent).toContain('12.5K tokens'); - expect(counter()?.title).toContain('12,500 tokens'); - expect(counter()?.title).toContain('12,000 input'); + expect(counter()?.title).toContain(`${n(12_500)} tokens`); + expect(counter()?.title).toContain(`${n(12_000)} input`); mocks.channel?.onmessage?.( state({ tokenUsage: { totalTokens: 0, inputTokens: 0, outputTokens: 0, scope: 'connection' }, diff --git a/src/components/SessionPicker.client.test.tsx b/src/components/SessionPicker.client.test.tsx index c6cba8f6..e58fc56b 100644 --- a/src/components/SessionPicker.client.test.tsx +++ b/src/components/SessionPicker.client.test.tsx @@ -151,14 +151,16 @@ describe('SessionPicker', () => { describe('relativeTime', () => { const now = Date.parse('2026-09-17T12:00:00Z'); + // The assertions match English wording. + const ago = (iso: string) => relativeTime(Date.parse(iso), now, 'en'); it('picks the largest fitting unit', () => { - expect(relativeTime(Date.parse('2026-09-15T12:00:00Z'), now)).toMatch(/day/); - expect(relativeTime(Date.parse('2026-09-17T09:00:00Z'), now)).toMatch(/hour/); - expect(relativeTime(Date.parse('2026-09-17T11:30:00Z'), now)).toMatch(/minute/); + expect(ago('2026-09-15T12:00:00Z')).toMatch(/day/); + expect(ago('2026-09-17T09:00:00Z')).toMatch(/hour/); + expect(ago('2026-09-17T11:30:00Z')).toMatch(/minute/); }); it('falls through to seconds for a very recent session', () => { - expect(relativeTime(Date.parse('2026-09-17T11:59:50Z'), now)).toMatch(/second/); + expect(ago('2026-09-17T11:59:50Z')).toMatch(/second/); }); }); diff --git a/src/components/SessionPicker.tsx b/src/components/SessionPicker.tsx index ffb350a4..3968a235 100644 --- a/src/components/SessionPicker.tsx +++ b/src/components/SessionPicker.tsx @@ -6,9 +6,10 @@ import { canResumeSessionId } from '../../electron/shared/session-resume'; import type { SessionRecord } from '../../electron/shared/session-record'; /** Relative age of a session, via the platform formatter rather than a table - * of thresholds. Exported for its test. */ -export function relativeTime(epochMs: number, now = Date.now()): string { - const format = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }); + * of thresholds. Exported for its test, which pins `locale` so the wording it + * checks does not depend on the host's default. */ +export function relativeTime(epochMs: number, now = Date.now(), locale?: string): string { + const format = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); const seconds = Math.round((epochMs - now) / 1000); const units: [Intl.RelativeTimeFormatUnit, number][] = [ ['day', 86_400],