diff --git a/apps/mcp-server/src/tui/components/HeaderBar.spec.tsx b/apps/mcp-server/src/tui/components/HeaderBar.spec.tsx index 738bc30f..3697cf4c 100644 --- a/apps/mcp-server/src/tui/components/HeaderBar.spec.tsx +++ b/apps/mcp-server/src/tui/components/HeaderBar.spec.tsx @@ -3,7 +3,7 @@ import { describe, it, expect } from 'vitest'; import { render } from 'ink-testing-library'; import { HeaderBar } from './HeaderBar'; -const FIXED_NOW = new Date('2026-03-18T14:30:45Z').getTime(); +const FIXED_NOW = new Date(2026, 2, 18, 14, 30, 45).getTime(); // local 14:30:45 describe('tui/components/HeaderBar', () => { it('should render title with neon branding', () => { diff --git a/apps/mcp-server/src/tui/components/live.pure.spec.ts b/apps/mcp-server/src/tui/components/live.pure.spec.ts index b6e07e2a..e08619e5 100644 --- a/apps/mcp-server/src/tui/components/live.pure.spec.ts +++ b/apps/mcp-server/src/tui/components/live.pure.spec.ts @@ -40,6 +40,11 @@ describe('tui/components/live.pure', () => { const now = start + 725_000; // 12분 5초 expect(formatElapsed(start, now)).toBe('12m 5s'); }); + + it('startedAt > now (clock drift) → "0s"로 클램프', () => { + const now = 1000000; + expect(formatElapsed(now + 5000, now)).toBe('0s'); + }); }); describe('formatRelativeTime', () => { @@ -68,6 +73,11 @@ describe('tui/components/live.pure', () => { expect(formatRelativeTime(now - 3600000, now)).toBe('1h ago'); expect(formatRelativeTime(now - 7200000, now)).toBe('2h ago'); }); + + it('timestamp > now (clock drift) → "just now"로 클램프', () => { + const now = 1000000; + expect(formatRelativeTime(now + 5000, now)).toBe('just now'); + }); }); describe('spinnerFrame', () => { @@ -177,21 +187,19 @@ describe('tui/components/live.pure', () => { }); describe('formatTimeWithSeconds', () => { - it('자정 → "00:00:00"', () => { - // 2026-01-01T00:00:00Z = 1767225600000 - const midnight = new Date('2026-01-01T00:00:00Z').getTime(); - // UTC 기준 - expect(formatTimeWithSeconds(midnight)).toBe('00:00:00'); + it('로컬 시간 기준으로 HH:MM:SS 형식 반환', () => { + const d = new Date(2026, 0, 1, 14, 23, 45); // local 14:23:45 + expect(formatTimeWithSeconds(d.getTime())).toBe('14:23:45'); }); - it('오후 시간 → "14:23:45"', () => { - const t = new Date('2026-01-01T14:23:45Z').getTime(); - expect(formatTimeWithSeconds(t)).toBe('14:23:45'); + it('한 자리 시/분/초 → 0으로 패딩', () => { + const d = new Date(2026, 0, 1, 3, 5, 9); // local 03:05:09 + expect(formatTimeWithSeconds(d.getTime())).toBe('03:05:09'); }); - it('한 자리 시/분/초 → 0으로 패딩', () => { - const t = new Date('2026-01-01T03:05:09Z').getTime(); - expect(formatTimeWithSeconds(t)).toBe('03:05:09'); + it('자정 → "00:00:00"', () => { + const d = new Date(2026, 0, 1, 0, 0, 0); // local midnight + expect(formatTimeWithSeconds(d.getTime())).toBe('00:00:00'); }); }); }); diff --git a/apps/mcp-server/src/tui/components/live.pure.ts b/apps/mcp-server/src/tui/components/live.pure.ts index f67ee0d1..678bd0d8 100644 --- a/apps/mcp-server/src/tui/components/live.pure.ts +++ b/apps/mcp-server/src/tui/components/live.pure.ts @@ -5,7 +5,7 @@ export interface ActivitySample { /** Format elapsed time as "Xm Ys" or "Ys". */ export function formatElapsed(startedAt: number, now: number): string { - const totalSec = Math.floor((now - startedAt) / 1000); + const totalSec = Math.max(0, Math.floor((now - startedAt) / 1000)); const min = Math.floor(totalSec / 60); const sec = totalSec % 60; return min > 0 ? `${min}m ${sec}s` : `${sec}s`; @@ -13,7 +13,7 @@ export function formatElapsed(startedAt: number, now: number): string { /** Format relative time as "just now", "Ns ago", "Nm ago", "Nh ago". */ export function formatRelativeTime(timestamp: number, now: number): string { - const diffSec = Math.floor((now - timestamp) / 1000); + const diffSec = Math.max(0, Math.floor((now - timestamp) / 1000)); if (diffSec <= 2) return 'just now'; if (diffSec < 60) return `${diffSec}s ago`; const diffMin = Math.floor(diffSec / 60); @@ -63,11 +63,11 @@ export function computeThroughput(samples: ActivitySample[]): string { return `${(totalCalls / durationMin).toFixed(1)}/min`; } -/** Format a UTC timestamp as "HH:MM:SS". */ +/** Format a timestamp as local "HH:MM:SS". */ export function formatTimeWithSeconds(now: number): string { const d = new Date(now); - const h = String(d.getUTCHours()).padStart(2, '0'); - const m = String(d.getUTCMinutes()).padStart(2, '0'); - const s = String(d.getUTCSeconds()).padStart(2, '0'); + const h = String(d.getHours()).padStart(2, '0'); + const m = String(d.getMinutes()).padStart(2, '0'); + const s = String(d.getSeconds()).padStart(2, '0'); return `${h}:${m}:${s}`; }