ReviewOS

also looking at this

loghqorg/loghq

feat(analytics): presets-and-calendar reporting-period picker

#6
Merged glennmichael123 wants to merge feat/date-range-picker into main
3 files +500 -5
tests/unit/date-range-picker.test.tsadded+75-0
Changes to tests/unit/date-range-picker.test.ts
@@ -0,0 +1,75 @@
1import { describe, expect, test } from 'bun:test'
2import { readFileSync } from 'node:fs'
3import { join } from 'node:path'
4
5const ROOT = join(import.meta.dir, '../..')
6const component = readFileSync(join(ROOT, 'resources/components/DateRangePicker.stx'), 'utf8')
7const analytics = readFileSync(join(ROOT, 'resources/views/analytics.stx'), 'utf8')
8
9// The date math ships inside the component's client script; the #region markers
10// fence off the part that touches no runtime, and this evaluates exactly that
11// text, so the tests run against the code the page runs.
12const pureSlice = component.match(/\/\/ #region pure\n([\s\S]*?)\n\/\/ #endregion pure/)?.[1] ?? ''
13const pure = new Function(`${pureSlice}\nreturn { pickYmd, pickShift, pickShiftMonth, pickGrid, pickOrder, pickPreset, pickQuery }`)() as Record<string, (...a: any[]) => any>
14
15describe('loghq date range picker: grid + day math', () => {
16 test('six weeks starting on the Sunday on or before the 1st', () => {
17 const cells = pure.pickGrid('2026-06', '2026-09-16')
18 expect(cells).toHaveLength(42)
19 expect(cells[0].ymd).toBe('2026-05-31')
20 expect(cells.filter((c: any) => c.inMonth)).toHaveLength(30)
21 })
22 test('leap February and future-day flag', () => {
23 expect(pure.pickGrid('2028-02', '2028-09-16').filter((c: any) => c.inMonth)).toHaveLength(29)
24 const cells = pure.pickGrid('2026-09', '2026-09-16')
25 expect(cells.find((c: any) => c.ymd === '2026-09-17').future).toBe(true)
26 expect(cells.find((c: any) => c.ymd === '2026-09-16').future).toBe(false)
27 })
28 test('day shifts survive DST and year ends', () => {
29 expect(pure.pickShift('2026-03-08', 1)).toBe('2026-03-09')
30 expect(pure.pickShift('2026-11-01', 1)).toBe('2026-11-02')
31 expect(pure.pickShift('2026-01-01', -1)).toBe('2025-12-31')
32 expect(pure.pickShiftMonth('2026-12', 1)).toBe('2027-01')
33 })
34})
35
36describe('loghq date range picker: presets match loghq vocab', () => {
37 const today = '2026-09-16'
38 test('loghq native spans go out as ?range=', () => {
39 for (const key of ['7d', '30d', '90d', '180d'])
40 expect(pure.pickPreset(key, today)).toEqual({ range: key })
41 })
42 test('loghq does not offer 24h, 1y or all', () => {
43 for (const key of ['24h', '1y', 'all'])
44 expect(pure.pickPreset(key, today)).toBeNull()
45 expect(component).not.toContain("pickApply('all')")
46 expect(component).not.toContain("pickApply('1y')")
47 expect(component).toContain("pickApply('180d')")
48 })
49 test('calendar presets resolve to local day bounds', () => {
50 expect(pure.pickPreset('today', today)).toEqual({ from: '2026-09-16', to: '2026-09-16' })
51 expect(pure.pickPreset('last-month', today)).toEqual({ from: '2026-08-01', to: '2026-08-31' })
52 expect(pure.pickPreset('this-year', today)).toEqual({ from: '2026-01-01', to: '2026-09-16' })
53 })
54})
55
56describe('loghq date range picker: URL + wiring', () => {
57 test('a custom span keeps project and drops the preset', () => {
58 const next = new URLSearchParams(pure.pickQuery('?project=p1&range=90d', { from: '2026-09-01', to: '2026-09-16' }))
59 expect(next.get('project')).toBe('p1')
60 expect(next.get('from')).toBe('2026-09-01')
61 expect(next.get('to')).toBe('2026-09-16')
62 expect(next.has('range')).toBe(false)
63 })
64 test('a preset drops any custom span', () => {
65 const next = new URLSearchParams(pure.pickQuery('?from=2026-09-01&to=2026-09-16', { range: '30d' }))
66 expect(next.get('range')).toBe('30d')
67 expect(next.has('from')).toBe(false)
68 })
69 test('analytics.stx mounts the picker, reads custom bounds, and neutralizes the pills under a custom range', () => {
70 expect(analytics).toContain('<DateRangePicker :summary="rangeLabel" :active="isCustom" />')
71 expect(analytics).toContain('const isCustom = isDay(qFrom) && isDay(qTo)')
72 expect(analytics).toContain('active: !isCustom && r.key === activeRange.key')
73 expect(analytics).toMatch(/if \(isCustom\) \{\s*fromDay = customFrom\s*toDay = customTo/)
74 })
75})