diff --git a/web/src/components/SaveStatus.vue b/web/src/components/SaveStatus.vue index d9697b8..a832fa3 100644 --- a/web/src/components/SaveStatus.vue +++ b/web/src/components/SaveStatus.vue @@ -8,7 +8,8 @@ const props = defineProps<{ defineEmits<{ retry: [] }>() -const HOUR_MS = 60 * 60 * 1000 +const MINUTE_MS = 60 * 1000 +const HOUR_MS = 60 * MINUTE_MS const TICK_MS = 15000 const now = ref(Date.now()) @@ -32,6 +33,18 @@ function formatClock(date: Date) { return `${hours}:${minutes}` } +function formatElapsed(diffMs: number) { + const minutes = Math.floor(diffMs / MINUTE_MS) + if (minutes < 60) { + return `${minutes} min` + } + const hours = Math.floor(minutes / 60) + if (hours < 24) { + return `${hours} h` + } + return `${Math.floor(hours / 24)} d` +} + const savedText = computed(() => { if (!props.savedAt) { return 'Saved' @@ -40,8 +53,7 @@ const savedText = computed(() => { if (diffMs < HOUR_MS) { return `Saved at ${formatClock(props.savedAt)}` } - const minutes = Math.floor(diffMs / 60000) - return minutes === 1 ? 'Saved 1 min ago' : `Saved ${minutes} min ago` + return `Saved ${formatElapsed(diffMs)} ago` }) diff --git a/web/src/components/__tests__/SaveStatus.spec.ts b/web/src/components/__tests__/SaveStatus.spec.ts index c9394dc..6a7f650 100644 --- a/web/src/components/__tests__/SaveStatus.spec.ts +++ b/web/src/components/__tests__/SaveStatus.spec.ts @@ -24,9 +24,15 @@ describe('Saved within the hour', () => { }) describe('Saved over an hour ago', () => { - it('reads "Saved n min ago"', () => { + it.each([ + ['2026-09-26T12:02:00Z', 'Saved 1 h ago'], + ['2026-09-26T12:32:00Z', 'Saved 1 h ago'], + ['2026-09-26T23:02:00Z', 'Saved 12 h ago'], + ['2026-09-27T11:02:00Z', 'Saved 1 d ago'], + ['2026-09-29T15:00:00Z', 'Saved 3 d ago'], + ])('at %s reads "%s"', (now, expected) => { vi.useFakeTimers() - vi.setSystemTime(new Date('2026-09-26T12:32:00Z')) + vi.setSystemTime(new Date(now)) const wrapper = mount(SaveStatus, { props: { state: 'saved', @@ -35,7 +41,7 @@ describe('Saved over an hour ago', () => { global: { plugins: [uiPlugin] }, }) - expect(wrapper.text()).toContain('Saved 90 min ago') + expect(wrapper.text()).toContain(expected) }) }) diff --git a/web/src/views/__tests__/EditorView.spec.ts b/web/src/views/__tests__/EditorView.spec.ts index 6e4115e..41fae9c 100644 --- a/web/src/views/__tests__/EditorView.spec.ts +++ b/web/src/views/__tests__/EditorView.spec.ts @@ -302,7 +302,7 @@ describe('A successful save returns to the Saved state with the new time', () => }) describe('SaveStatus switches to a relative time after an hour', () => { - it('reads "Saved n min ago" once more than an hour has passed', async () => { + it('reads "Saved n h ago" once more than an hour has passed', async () => { const wrapper = await mountEditor({ 'GET /v1/article/12': () => mockResponse(200, articleFixture()), }) @@ -313,7 +313,7 @@ describe('SaveStatus switches to a relative time after an hour', () => { await vi.advanceTimersByTimeAsync(20000) await flushPromises() - expect(wrapper.text()).toContain('Saved 90 min ago') + expect(wrapper.text()).toContain('Saved 1 h ago') }) })