This commit is contained in:
@@ -1,16 +1,31 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { dateKey, isTaskView, moveDueDate } from './mvp-utils'
|
||||
import { dateKey, habitWeek, isHabitComplete, isTaskView, numericHabitInputValue } from './mvp-utils'
|
||||
|
||||
describe('MVP view utilities', () => {
|
||||
it('normalizes to UTC so stored due_at stays stable in every local timezone', () => {
|
||||
const moved = moveDueDate('2026-09-05T14:30:00+08:00', '2026-09-09')
|
||||
expect(moved).toBe('2026-09-09T14:30:00.000Z')
|
||||
const newly = moveDueDate(null, '2026-09-09')
|
||||
expect(newly).toBe('2026-09-09T09:00:00.000Z')
|
||||
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(['calendar', 'habits', 'settings', 'trash'].filter(isTaskView)).toEqual([])
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,26 +5,6 @@ export function dateKey(date: Date) {
|
||||
return `${y}-${m}-${d}`
|
||||
}
|
||||
|
||||
export function calendarRange(date: Date) {
|
||||
const first = new Date(date.getFullYear(), date.getMonth(), 1)
|
||||
const from = new Date(first)
|
||||
const mondayOffset = (first.getDay() + 6) % 7
|
||||
from.setDate(first.getDate() - mondayOffset)
|
||||
const to = new Date(from)
|
||||
to.setDate(from.getDate() + 41)
|
||||
return { from: dateKey(from), to: dateKey(to) }
|
||||
}
|
||||
|
||||
export function moveDueDate(current: string | null, day: string) {
|
||||
const source = current ? new Date(current) : null
|
||||
const hours = source && !Number.isNaN(source.valueOf()) ? source.getHours() : 9
|
||||
const minutes = source && !Number.isNaN(source.valueOf()) ? source.getMinutes() : 0
|
||||
const date = new Date(`${day}T00:00:00`)
|
||||
date.setHours(hours, minutes, 0, 0)
|
||||
const offsetMs = date.getTimezoneOffset() * 60_000
|
||||
return new Date(date.getTime() - offsetMs).toISOString()
|
||||
}
|
||||
|
||||
export function habitWeek(now = new Date()) {
|
||||
const monday = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
monday.setDate(monday.getDate() - ((monday.getDay() + 6) % 7))
|
||||
@@ -43,3 +23,14 @@ export function mergePage<T>(page: T[] | { items?: T[]; next_cursor?: string | n
|
||||
export function isTaskView(view: string) {
|
||||
return view === 'tasks' || view === 'today' || view === 'upcoming'
|
||||
}
|
||||
|
||||
export function isHabitComplete(kind: string | undefined, value: number | boolean | undefined, target = 1) {
|
||||
if (kind === 'numeric') return Number(value ?? 0) >= target
|
||||
return Boolean(value)
|
||||
}
|
||||
|
||||
export function numericHabitInputValue(input: number | string | undefined) {
|
||||
if (input === undefined || input === '') return null
|
||||
const value = Number(input)
|
||||
return Number.isFinite(value) && value >= 0 ? value : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user