feat: collapse Today sections
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readTodaySectionCollapse, writeTodaySectionCollapse } from './today-section-collapse'
|
||||
|
||||
const defaults = { overdue: false, tasks: false, habits: false }
|
||||
|
||||
function fakeStorage(initial?: string) {
|
||||
const values = new Map<string, string>()
|
||||
if (initial !== undefined) values.set('dodo.today-section-collapse.v1', initial)
|
||||
return {
|
||||
getItem: (key: string) => values.get(key) ?? null,
|
||||
setItem: (key: string, value: string) => values.set(key, value),
|
||||
value: () => values.get('dodo.today-section-collapse.v1'),
|
||||
}
|
||||
}
|
||||
|
||||
describe('Today section collapse persistence', () => {
|
||||
it('defaults every section to expanded when storage is absent or malformed', () => {
|
||||
expect(readTodaySectionCollapse(fakeStorage(), 'dodo.today-section-collapse.v1')).toEqual(defaults)
|
||||
expect(readTodaySectionCollapse(fakeStorage('{bad'), 'dodo.today-section-collapse.v1')).toEqual(defaults)
|
||||
})
|
||||
|
||||
it('accepts only boolean fields and falls back invalid or missing fields independently', () => {
|
||||
expect(readTodaySectionCollapse(fakeStorage(JSON.stringify({ overdue: true, tasks: 'yes', extra: true })), 'dodo.today-section-collapse.v1')).toEqual({
|
||||
overdue: true,
|
||||
tasks: false,
|
||||
habits: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not break collapsing when storage rejects a write', () => {
|
||||
const storage = {
|
||||
getItem: () => null,
|
||||
setItem: () => { throw new Error('quota exceeded') },
|
||||
}
|
||||
expect(() => writeTodaySectionCollapse(storage, 'dodo.today-section-collapse.v1', defaults)).not.toThrow()
|
||||
})
|
||||
|
||||
it('writes all three independent section states under the single versioned key', () => {
|
||||
const storage = fakeStorage()
|
||||
writeTodaySectionCollapse(storage, 'dodo.today-section-collapse.v1', { overdue: true, tasks: false, habits: true })
|
||||
expect(storage.value()).toBe(JSON.stringify({ overdue: true, tasks: false, habits: true }))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
export type TodaySectionCollapse = {
|
||||
overdue: boolean
|
||||
tasks: boolean
|
||||
habits: boolean
|
||||
}
|
||||
|
||||
type StorageLike = Pick<Storage, 'getItem' | 'setItem'>
|
||||
|
||||
const expandedDefaults = (): TodaySectionCollapse => ({ overdue: false, tasks: false, habits: false })
|
||||
|
||||
export function readTodaySectionCollapse(storage: StorageLike, key: string): TodaySectionCollapse {
|
||||
try {
|
||||
const parsed = JSON.parse(storage.getItem(key) ?? '') as Partial<Record<keyof TodaySectionCollapse, unknown>>
|
||||
return {
|
||||
overdue: typeof parsed.overdue === 'boolean' ? parsed.overdue : false,
|
||||
tasks: typeof parsed.tasks === 'boolean' ? parsed.tasks : false,
|
||||
habits: typeof parsed.habits === 'boolean' ? parsed.habits : false,
|
||||
}
|
||||
} catch {
|
||||
return expandedDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
export function writeTodaySectionCollapse(storage: StorageLike, key: string, value: TodaySectionCollapse) {
|
||||
try {
|
||||
storage.setItem(key, JSON.stringify(value))
|
||||
} catch {
|
||||
// Storage can be unavailable (privacy mode/quota); collapsing must still work in memory.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user