import { describe, expect, test } from 'bun:test' import { formatFileSize, formatSecondsShort, formatDuration, formatNumber, formatTokens, formatRelativeTime, formatRelativeTimeAgo, formatLogMetadata, } from '../format' describe('formatFileSize', () => { test('formats bytes', () => { expect(formatFileSize(500)).toBe('500 bytes') }) test('formats kilobytes', () => { expect(formatFileSize(1536)).toBe('1.5KB') }) test('formats megabytes', () => { expect(formatFileSize(1.5 * 1024 * 1024)).toBe('1.5MB') }) test('formats gigabytes', () => { expect(formatFileSize(2 * 1024 * 1024 * 1024)).toBe('2GB') }) test('removes trailing .0', () => { expect(formatFileSize(1024)).toBe('1KB') }) }) describe('formatSecondsShort', () => { test('formats milliseconds to seconds', () => { expect(formatSecondsShort(1234)).toBe('1.2s') }) test('formats zero', () => { expect(formatSecondsShort(0)).toBe('0.0s') }) test('formats sub-second', () => { expect(formatSecondsShort(500)).toBe('0.5s') }) }) describe('formatDuration', () => { test('formats 0 as 0s', () => { expect(formatDuration(0)).toBe('0s') }) test('formats seconds', () => { expect(formatDuration(5000)).toBe('5s') }) test('formats minutes and seconds', () => { expect(formatDuration(125000)).toBe('2m 5s') }) test('formats hours', () => { expect(formatDuration(3661000)).toBe('1h 1m 1s') }) test('formats days', () => { expect(formatDuration(90000000)).toBe('1d 1h 0m') }) test('hideTrailingZeros removes zero components', () => { expect(formatDuration(3600000, { hideTrailingZeros: true })).toBe('1h') expect(formatDuration(60000, { hideTrailingZeros: true })).toBe('1m') }) test('mostSignificantOnly returns largest unit', () => { expect(formatDuration(90000000, { mostSignificantOnly: true })).toBe('1d') expect(formatDuration(3661000, { mostSignificantOnly: true })).toBe('1h') }) }) describe('formatNumber', () => { test('formats small numbers as-is', () => { expect(formatNumber(900)).toBe('900') }) test('formats thousands with k suffix', () => { expect(formatNumber(1321)).toBe('1.3k') }) test('formats millions', () => { expect(formatNumber(1500000)).toBe('1.5m') }) test('formats 0 as-is', () => { expect(formatNumber(0)).toBe('0') }) test('formats billions', () => { expect(formatNumber(1500000000)).toBe('1.5b') }) }) describe('formatTokens', () => { test('removes .0 from formatted number', () => { expect(formatTokens(1000)).toBe('1k') }) test('formats small numbers', () => { expect(formatTokens(500)).toBe('500') }) test('formats 1000 without .0', () => { expect(formatTokens(1000)).toBe('1k') }) test('formats 1500 as 1.5k', () => { expect(formatTokens(1500)).toBe('1.5k') }) }) describe('formatRelativeTime', () => { const now = new Date('2026-01-15T12:00:00Z') test('formats seconds ago', () => { const date = new Date('2026-01-15T11:59:30Z') expect(formatRelativeTime(date, { now })).toBe('30s ago') }) test('formats minutes ago', () => { const date = new Date('2026-01-15T11:55:00Z') expect(formatRelativeTime(date, { now })).toBe('5m ago') }) test('formats future time', () => { const date = new Date('2026-01-15T13:00:00Z') expect(formatRelativeTime(date, { now })).toBe('in 1h') }) test('handles zero difference', () => { expect(formatRelativeTime(now, { now })).toBe('0s ago') }) test('formats hours ago', () => { const date = new Date('2026-01-15T09:00:00Z') expect(formatRelativeTime(date, { now })).toBe('3h ago') }) test('formats days ago', () => { const date = new Date('2026-01-13T12:00:00Z') expect(formatRelativeTime(date, { now })).toBe('2d ago') }) test('formats weeks ago', () => { const date = new Date('2026-01-01T12:00:00Z') expect(formatRelativeTime(date, { now })).toBe('2w ago') }) }) describe('formatRelativeTimeAgo', () => { const now = new Date('2026-01-15T12:00:00Z') test("formats past date with 'ago' suffix", () => { const date = new Date('2026-01-15T11:59:30Z') const result = formatRelativeTimeAgo(date, { now }) expect(result).toBe('30s ago') }) test("formats future date without 'ago' suffix", () => { const date = new Date('2026-01-15T13:00:00Z') const result = formatRelativeTimeAgo(date, { now }) expect(result).toBe('in 1h') }) test('formats minutes ago', () => { const date = new Date('2026-01-15T11:55:00Z') const result = formatRelativeTimeAgo(date, { now }) expect(result).toBe('5m ago') }) test('formats hours ago', () => { const date = new Date('2026-01-15T09:00:00Z') const result = formatRelativeTimeAgo(date, { now }) expect(result).toBe('3h ago') }) test('formats days ago', () => { const date = new Date('2026-01-13T12:00:00Z') const result = formatRelativeTimeAgo(date, { now }) expect(result).toBe('2d ago') }) test('handles date equal to now as past', () => { // date === now, treated as past (not future) const result = formatRelativeTimeAgo(now, { now }) expect(result).toBe('0s ago') }) test('uses numeric always for past dates', () => { // Should always use numeric format for past dates const date = new Date('2026-01-15T11:59:00Z') const result = formatRelativeTimeAgo(date, { now }) expect(result).toContain('ago') }) test("future date does not contain 'ago'", () => { const date = new Date('2026-01-15T14:00:00Z') const result = formatRelativeTimeAgo(date, { now }) expect(result).not.toContain('ago') }) }) describe('formatLogMetadata', () => { // Use a date very recently in the past so it always shows "Xs ago" or similar const modified = new Date(Date.now() - 5 * 60 * 1000) // 5 minutes ago test('includes relative time and message count', () => { const result = formatLogMetadata({ modified, messageCount: 10, }) expect(result).toContain('ago') expect(result).toContain('10 messages') }) test('uses fileSize instead of messageCount when provided', () => { const result = formatLogMetadata({ modified, messageCount: 5, fileSize: 1536, }) expect(result).toContain('1.5KB') expect(result).not.toContain('messages') }) test('includes gitBranch when provided', () => { const result = formatLogMetadata({ modified, messageCount: 3, gitBranch: 'main', }) expect(result).toContain('main') }) test('omits gitBranch when not provided', () => { const result = formatLogMetadata({ modified, messageCount: 3, }) // Should not have a dangling separator from missing branch expect(result).not.toMatch(/^ · | · $/) }) test('includes tag when provided', () => { const result = formatLogMetadata({ modified, messageCount: 3, tag: 'my-tag', }) expect(result).toContain('#my-tag') }) test('includes agentSetting when provided', () => { const result = formatLogMetadata({ modified, messageCount: 3, agentSetting: 'custom-agent', }) expect(result).toContain('@custom-agent') }) test('includes prNumber when provided', () => { const result = formatLogMetadata({ modified, messageCount: 3, prNumber: 42, }) expect(result).toContain('#42') }) test('includes prRepository with prNumber when both provided', () => { const result = formatLogMetadata({ modified, messageCount: 3, prNumber: 99, prRepository: 'owner/repo', }) expect(result).toContain('owner/repo#99') }) test("parts are joined with ' · ' separator", () => { const result = formatLogMetadata({ modified, messageCount: 5, gitBranch: 'feat/x', }) expect(result).toContain(' · ') }) })