diff --git a/gnome/prefs.js b/gnome/prefs.js index 8d80679..b0d13f9 100644 --- a/gnome/prefs.js +++ b/gnome/prefs.js @@ -26,7 +26,7 @@ const PERIODS = [ { id: 'week', label: '7 Days' }, { id: '30days', label: '30 Days' }, { id: 'month', label: 'Month' }, - { id: 'all', label: 'All Time' }, + { id: 'all', label: '6 Months' }, ]; export default class CodeBurnPreferences extends ExtensionPreferences { diff --git a/src/cli-date.ts b/src/cli-date.ts index b3d502d..7dfd06f 100644 --- a/src/cli-date.ts +++ b/src/cli-date.ts @@ -113,7 +113,7 @@ export function getDateRange(period: string): { range: DateRange; label: string return { range: { start, end }, label: 'Last 30 Days' } } case 'all': { - const start = new Date(now.getFullYear(), now.getMonth() - ALL_TIME_MONTHS, now.getDate()) + const start = new Date(now.getFullYear(), now.getMonth() - ALL_TIME_MONTHS, 1) return { range: { start, end }, label: 'Last 6 months' } } default: { diff --git a/src/dashboard.tsx b/src/dashboard.tsx index 16aea07..3193b5a 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -13,7 +13,7 @@ import { dateKey } from './day-aggregator.js' import { CompareView } from './compare.js' import { getPlanUsageOrNull, type PlanUsage } from './plan-usage.js' import { planDisplayName } from './plans.js' -import { getDateRange as getDateRangeShared, PERIODS, PERIOD_LABELS, type Period } from './cli-date.js' +import { getDateRange, PERIODS, PERIOD_LABELS, type Period } from './cli-date.js' import { join } from 'path' import { patchStdoutForWindows } from './ink-win.js' @@ -96,7 +96,7 @@ function gradientColor(pct: number): string { } function getPeriodRange(period: Period): { start: Date; end: Date } { - return getDateRangeShared(period).range + return getDateRange(period).range } type Layout = { dashWidth: number; wide: boolean; halfWidth: number; barWidth: number } diff --git a/tests/cli-date.test.ts b/tests/cli-date.test.ts index f2f7404..e30096d 100644 --- a/tests/cli-date.test.ts +++ b/tests/cli-date.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest' +import { afterEach, describe, it, expect, vi } from 'vitest' import { getDateRange, PERIODS, @@ -7,6 +7,10 @@ import { type Period, } from '../src/cli-date.js' +afterEach(() => { + vi.useRealTimers() +}) + describe('getDateRange', () => { it('"all" is bounded to the last 6 months, not epoch', () => { const { range, label } = getDateRange('all') @@ -18,27 +22,26 @@ describe('getDateRange', () => { // dashboard bug) or any pre-2000 date. expect(range.start.getFullYear()).toBeGreaterThan(2000) - // Roughly 6 months back. Accept 5-7 months to absorb end-of-month - // clamping (e.g. on May 31, JS rolls Nov 31 -> Dec 1, shifting the - // computed month forward by one). const monthsDiff = (now.getFullYear() - range.start.getFullYear()) * 12 + (now.getMonth() - range.start.getMonth()) - expect(monthsDiff).toBeGreaterThanOrEqual(5) - expect(monthsDiff).toBeLessThanOrEqual(7) + expect(monthsDiff).toBe(6) + expect(range.start.getDate()).toBe(1) // End is today, end of day. expect(range.end.getHours()).toBe(23) expect(range.end.getMinutes()).toBe(59) }) - it('CLI and dashboard agree on "all" semantics (no Date(0) drift)', () => { - const a = getDateRange('all') - const b = getDateRange('all') - expect(a.range.start.getTime()).toBe(b.range.start.getTime()) - expect(a.label).toBe(b.label) - // Regression guard: must never silently fall back to epoch. - expect(a.range.start.getFullYear()).toBeGreaterThan(2000) + it('"all" does not overflow past the target month at end-of-month', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(2026, 7, 31, 12, 0, 0)) + + const { range } = getDateRange('all') + + expect(range.start.getFullYear()).toBe(2026) + expect(range.start.getMonth()).toBe(1) + expect(range.start.getDate()).toBe(1) }) it('"week" returns the last 7 days', () => {