feat : helpers purs de plage de dates (#MUI-33)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
57
app/components/malio/date/composables/dateRange.test.ts
Normal file
57
app/components/malio/date/composables/dateRange.test.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import {describe, expect, it} from 'vitest'
|
||||||
|
import {dayRangeRole, normalizeRange, resolveRangeBounds} from './dateRange'
|
||||||
|
|
||||||
|
describe('dateRange', () => {
|
||||||
|
describe('normalizeRange', () => {
|
||||||
|
it('keeps an already ordered pair', () => {
|
||||||
|
expect(normalizeRange('2026-05-19', '2026-05-25')).toEqual({start: '2026-05-19', end: '2026-05-25'})
|
||||||
|
})
|
||||||
|
it('swaps a reversed pair', () => {
|
||||||
|
expect(normalizeRange('2026-05-25', '2026-05-19')).toEqual({start: '2026-05-19', end: '2026-05-25'})
|
||||||
|
})
|
||||||
|
it('handles an equal pair', () => {
|
||||||
|
expect(normalizeRange('2026-05-19', '2026-05-19')).toEqual({start: '2026-05-19', end: '2026-05-19'})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resolveRangeBounds', () => {
|
||||||
|
it('returns null without a start', () => {
|
||||||
|
expect(resolveRangeBounds(null, null, null)).toBeNull()
|
||||||
|
})
|
||||||
|
it('returns a single-point range when only start is set', () => {
|
||||||
|
expect(resolveRangeBounds('2026-05-19', null, null)).toEqual({lo: '2026-05-19', hi: '2026-05-19'})
|
||||||
|
})
|
||||||
|
it('orders start and committed end', () => {
|
||||||
|
expect(resolveRangeBounds('2026-05-19', '2026-05-25', null)).toEqual({lo: '2026-05-19', hi: '2026-05-25'})
|
||||||
|
})
|
||||||
|
it('uses preview when end is not set', () => {
|
||||||
|
expect(resolveRangeBounds('2026-05-19', null, '2026-05-22')).toEqual({lo: '2026-05-19', hi: '2026-05-22'})
|
||||||
|
})
|
||||||
|
it('inverts when preview is before start', () => {
|
||||||
|
expect(resolveRangeBounds('2026-05-19', null, '2026-05-10')).toEqual({lo: '2026-05-10', hi: '2026-05-19'})
|
||||||
|
})
|
||||||
|
it('prioritises committed end over preview', () => {
|
||||||
|
expect(resolveRangeBounds('2026-05-19', '2026-05-25', '2026-05-30')).toEqual({lo: '2026-05-19', hi: '2026-05-25'})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('dayRangeRole', () => {
|
||||||
|
const bounds = {lo: '2026-05-19', hi: '2026-05-25'}
|
||||||
|
it('returns none without bounds', () => {
|
||||||
|
expect(dayRangeRole('2026-05-20', null)).toBe('none')
|
||||||
|
})
|
||||||
|
it('returns single when lo === hi and matches', () => {
|
||||||
|
expect(dayRangeRole('2026-05-19', {lo: '2026-05-19', hi: '2026-05-19'})).toBe('single')
|
||||||
|
expect(dayRangeRole('2026-05-20', {lo: '2026-05-19', hi: '2026-05-19'})).toBe('none')
|
||||||
|
})
|
||||||
|
it('returns start, end and in-range', () => {
|
||||||
|
expect(dayRangeRole('2026-05-19', bounds)).toBe('start')
|
||||||
|
expect(dayRangeRole('2026-05-25', bounds)).toBe('end')
|
||||||
|
expect(dayRangeRole('2026-05-22', bounds)).toBe('in-range')
|
||||||
|
})
|
||||||
|
it('returns none outside the bounds', () => {
|
||||||
|
expect(dayRangeRole('2026-05-10', bounds)).toBe('none')
|
||||||
|
expect(dayRangeRole('2026-05-30', bounds)).toBe('none')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
31
app/components/malio/date/composables/dateRange.ts
Normal file
31
app/components/malio/date/composables/dateRange.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
export type DateRangeValue = {start: string; end: string}
|
||||||
|
|
||||||
|
export function normalizeRange(a: string, b: string): DateRangeValue {
|
||||||
|
return a <= b ? {start: a, end: b} : {start: b, end: a}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveRangeBounds(
|
||||||
|
start: string | null,
|
||||||
|
end: string | null,
|
||||||
|
preview: string | null,
|
||||||
|
): {lo: string; hi: string} | null {
|
||||||
|
if (!start) return null
|
||||||
|
const other = end ?? preview
|
||||||
|
if (!other) return {lo: start, hi: start}
|
||||||
|
return start <= other ? {lo: start, hi: other} : {lo: other, hi: start}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DayRangeRole = 'none' | 'single' | 'start' | 'end' | 'in-range'
|
||||||
|
|
||||||
|
export function dayRangeRole(
|
||||||
|
iso: string,
|
||||||
|
bounds: {lo: string; hi: string} | null,
|
||||||
|
): DayRangeRole {
|
||||||
|
if (!bounds) return 'none'
|
||||||
|
const {lo, hi} = bounds
|
||||||
|
if (lo === hi) return iso === lo ? 'single' : 'none'
|
||||||
|
if (iso === lo) return 'start'
|
||||||
|
if (iso === hi) return 'end'
|
||||||
|
if (iso > lo && iso < hi) return 'in-range'
|
||||||
|
return 'none'
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user