Files
dodo/frontend/src/lib/mvp-utils.test.ts
T
bboysoul 798725ad29
ci / gitleaks (push) Successful in 18s
ci / docker (push) Successful in 3m49s
fix: persist completed item visibility
2026-09-08 11:24:39 +08:00

138 lines
6.6 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { calendarModeLabel, clampFabPosition, countdownDayText, countdownKindLabel, dateKey, defaultView, habitButtonNotice, habitButtonValue, habitWeek, isFabDrag, isHabitComplete, isHabitScheduledToday, isTaskView, nextHabitSwipeValue, nextTotalAfterLocalTaskAdd, numericHabitInputValue, previousHabitSwipeValue, quickTaskFields, readCountdownCache, readHabitGridCache, readStoredBoolean, shouldToggleRowSwipe, writeCountdownCache, writeHabitGridCache, writeStoredBoolean } from './mvp-utils'
describe('MVP view utilities', () => {
it('formats a local date as YYYY-MM-DD', () => {
expect(dateKey(new Date(2026, 8, 5))).toBe('2026-09-05')
})
it('returns the monday-to-sunday week containing the requested day', () => {
expect(habitWeek(new Date(2026, 8, 5)).map(dateKey)).toEqual([
'2026-08-31', '2026-09-01', '2026-09-02', '2026-09-03', '2026-09-04', '2026-09-05', '2026-09-06',
])
})
it('identifies only task-backed views as task data loaders', () => {
expect(['tasks', 'today', 'upcoming'].filter(isTaskView)).toEqual(['tasks', 'today', 'upcoming'])
expect(['habits', 'settings', 'trash'].filter(isTaskView)).toEqual([])
})
it('only completes numeric habits when progress reaches the target', () => {
expect(isHabitComplete('numeric', 3, 5)).toBe(false)
expect(isHabitComplete('numeric', 5, 5)).toBe(true)
expect(isHabitComplete('boolean', 1, 5)).toBe(true)
})
it('does not invent a numeric value when the input is empty', () => {
expect(numericHabitInputValue(undefined)).toBeNull()
expect(numericHabitInputValue('')).toBeNull()
expect(numericHabitInputValue(4)).toBe(4)
})
it('uses today as the default landing view', () => {
expect(defaultView()).toBe('today')
})
it('quick-adds Today tasks to Inbox at noon in Asia/Shanghai', () => {
const now = new Date('2026-09-05T16:30:00.000Z')
expect(quickTaskFields('today', 'selected-list', 'inbox-list', now)).toEqual({
list_id: 'inbox-list',
due_at: '2026-09-06T12:00:00+08:00',
})
})
it('keeps quick-add tasks in the selected list outside Today', () => {
expect(quickTaskFields('tasks', 'selected-list', 'inbox-list', new Date('2026-09-05T16:30:00.000Z'))).toEqual({
list_id: 'selected-list',
})
})
it('only includes scheduled, unpaused habits today regardless of kind or completion', () => {
const day = '2026-09-06'
expect(isHabitScheduledToday({ kind: 'boolean', cells: [{ day, scheduled: true, paused: false, value: true }] }, day)).toBe(true)
expect(isHabitScheduledToday({ kind: 'numeric', cells: [{ day, scheduled: true, paused: false, value: 5 }] }, day)).toBe(true)
expect(isHabitScheduledToday({ kind: 'boolean', cells: [{ day, scheduled: false, paused: false, value: true }] }, day)).toBe(false)
expect(isHabitScheduledToday({ kind: 'numeric', cells: [{ day, scheduled: true, paused: true, value: 0 }] }, day)).toBe(false)
expect(isHabitScheduledToday({ kind: 'boolean', cells: [] }, day)).toBe(false)
})
it('increments count habits one swipe at a time and caps at target', () => {
expect(nextHabitSwipeValue('numeric', 2, 5)).toBe(3)
expect(nextHabitSwipeValue('numeric', 5, 5)).toBe(5)
expect(previousHabitSwipeValue('numeric', 3)).toBe(2)
expect(previousHabitSwipeValue('numeric', 0)).toBe(0)
})
it('resets a completed numeric habit when its round button is clicked again', () => {
expect(habitButtonValue('numeric', 2, 3)).toBe(3)
expect(habitButtonValue('numeric', 3, 3)).toBe(0)
expect(habitButtonValue('boolean', 0, 1)).toBe(1)
expect(habitButtonValue('boolean', 1, 1)).toBe(0)
})
it('uses reset wording when a completed numeric habit button returns to zero', () => {
expect(habitButtonNotice('numeric', 3, 0)).toBe('今日进度已重置')
expect(habitButtonNotice('numeric', 2, 3)).toBe('已记录一次 🎉')
expect(habitButtonNotice('numeric', 2, 1)).toBe('已减少一次')
expect(habitButtonNotice('boolean', 1, 0)).toBe('已取消完成')
})
it('persists boolean display preferences across page refreshes', () => {
const storage = new Map<string, string>()
const fakeStorage = {
getItem: (key: string) => storage.get(key) ?? null,
setItem: (key: string, value: string) => storage.set(key, value),
}
expect(readStoredBoolean(fakeStorage, 'dodo.show-completed', true)).toBe(true)
writeStoredBoolean(fakeStorage, 'dodo.show-completed', false)
expect(readStoredBoolean(fakeStorage, 'dodo.show-completed', true)).toBe(false)
storage.set('dodo.show-completed', 'invalid')
expect(readStoredBoolean(fakeStorage, 'dodo.show-completed', true)).toBe(true)
})
it('reuses the current week habit grid while refreshing in the background', () => {
const habits = [{ id: 'habit-1', name: '喝水' }]
writeHabitGridCache('2026-09-07', habits)
expect(readHabitGridCache('2026-09-07')).toEqual(habits)
expect(readHabitGridCache('2026-09-14')).toBeNull()
})
it('reuses countdown data while the page refreshes in the background', () => {
const active = [{ id: 'countdown-1', title: '旅行' }]
const archived = [{ id: 'countdown-2', title: '旧日期' }]
writeCountdownCache(active, archived)
expect(readCountdownCache()).toEqual({ items: active, archived })
})
it('renders countdown labels from date-only day values', () => {
expect(countdownDayText(8)).toBe('还有 8 天')
expect(countdownDayText(0)).toBe('就是今天')
expect(countdownDayText(-12)).toBe('已经 12 天')
expect(countdownKindLabel('countdown')).toBe('倒数日')
expect(countdownKindLabel('anniversary')).toBe('纪念日')
expect(countdownKindLabel('birthday')).toBe('生日')
expect(calendarModeLabel('solar')).toBe('公历')
expect(calendarModeLabel('lunar')).toBe('农历')
})
it('toggles a row for a deliberate mostly-horizontal swipe', () => {
expect(shouldToggleRowSwipe(78, 8)).toBe(true)
expect(shouldToggleRowSwipe(88, 28)).toBe(true)
expect(shouldToggleRowSwipe(-78, 8)).toBe(false)
expect(shouldToggleRowSwipe(30, 2)).toBe(false)
expect(shouldToggleRowSwipe(80, 60)).toBe(false)
})
it('keeps the draggable mobile add button inside the viewport', () => {
expect(clampFabPosition(-10, -20, 390, 844)).toEqual({ x: 14, y: 14 })
expect(clampFabPosition(500, 900, 390, 844)).toEqual({ x: 324, y: 718 })
expect(clampFabPosition(120, 300, 390, 844)).toEqual({ x: 120, y: 300 })
})
it('distinguishes tapping the add button from dragging it', () => {
expect(isFabDrag(3, 4)).toBe(false)
expect(isFabDrag(8, 0)).toBe(true)
expect(isFabDrag(6, 7)).toBe(true)
})
})