mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
108 lines
5.5 KiB
TypeScript
108 lines
5.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
baseRange, computeScrollWindow, fixedScrollWindowState, freshScrollWindowState, growScrollWindow,
|
|
normalizeScrollWindowState, scrollWindowContains, SCROLL_WINDOW_MAX, SCROLL_WINDOW_STEP,
|
|
type ScrollWindowOptions,
|
|
} from '../calendar-scroll-window';
|
|
|
|
// #759: every calendar view keeps one window of days around the focused
|
|
// day; edges double, navigation inside the window does not reset it.
|
|
|
|
const opts: ScrollWindowOptions = { weekStartsOn: 1 };
|
|
const key = (d: Date) => d.toISOString().slice(0, 10);
|
|
// Local-midnight dates render in the test's timezone; compare by local fields.
|
|
const local = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
|
|
describe('baseRange', () => {
|
|
it('is the day itself, the week, the month grid, or 30 days for the agenda', () => {
|
|
const wed = new Date(2026, 8, 9); // Wednesday
|
|
expect(local(baseRange('day', wed, opts).start)).toBe('2026-09-09');
|
|
expect(local(baseRange('day', wed, opts).end)).toBe('2026-09-09');
|
|
expect(local(baseRange('week', wed, opts).start)).toBe('2026-09-07');
|
|
expect(local(baseRange('week', wed, opts).end)).toBe('2026-09-13');
|
|
expect(local(baseRange('month', wed, opts).start)).toBe('2026-08-31');
|
|
expect(local(baseRange('month', wed, opts).end)).toBe('2026-10-04');
|
|
expect(local(baseRange('agenda', wed, opts).start)).toBe('2026-09-09');
|
|
expect(local(baseRange('agenda', wed, opts).end)).toBe('2026-10-09');
|
|
});
|
|
|
|
it('uses the locale month grid when one is supplied', () => {
|
|
const grid = [new Date(2026, 7, 24), new Date(2026, 7, 25), new Date(2026, 9, 11)];
|
|
const range = baseRange('month', new Date(2026, 8, 9), { ...opts, monthGridDays: () => grid });
|
|
expect(local(range.start)).toBe('2026-08-24');
|
|
expect(local(range.end)).toBe('2026-10-11');
|
|
});
|
|
});
|
|
|
|
describe('computeScrollWindow', () => {
|
|
it('starts as the base range plus the initial days after it, snapped to whole weeks', () => {
|
|
const win = computeScrollWindow(freshScrollWindowState('month', new Date(2026, 8, 9)), opts);
|
|
expect(local(win.start)).toBe('2026-08-31');
|
|
// 2026-10-04 + 30 days = 2026-11-03 (Tuesday) -> end of that week
|
|
expect(local(win.end)).toBe('2026-11-08');
|
|
expect(win.canExtendStart).toBe(true);
|
|
expect(win.canExtendEnd).toBe(true);
|
|
});
|
|
|
|
it('is exactly one period when free scrolling is off (agenda keeps its 30 days)', () => {
|
|
const month = computeScrollWindow(fixedScrollWindowState('month', new Date(2026, 8, 9)), opts);
|
|
expect(local(month.start)).toBe('2026-08-31');
|
|
expect(local(month.end)).toBe('2026-10-04');
|
|
const week = computeScrollWindow(fixedScrollWindowState('week', new Date(2026, 8, 9)), opts);
|
|
expect([local(week.start), local(week.end)]).toEqual(['2026-09-07', '2026-09-13']);
|
|
const day = computeScrollWindow(fixedScrollWindowState('day', new Date(2026, 8, 9)), opts);
|
|
expect([local(day.start), local(day.end)]).toEqual(['2026-09-09', '2026-09-09']);
|
|
const agenda = computeScrollWindow(fixedScrollWindowState('agenda', new Date(2026, 8, 9)), opts);
|
|
expect([local(agenda.start), local(agenda.end)]).toEqual(['2026-09-09', '2026-10-09']);
|
|
});
|
|
|
|
it('does not snap the day view to weeks', () => {
|
|
const win = computeScrollWindow(freshScrollWindowState('day', new Date(2026, 8, 9)), opts);
|
|
expect(local(win.start)).toBe('2026-09-09');
|
|
expect(local(win.end)).toBe('2026-10-09');
|
|
});
|
|
|
|
it('reports the limits once a side reached its maximum', () => {
|
|
const state = { ...freshScrollWindowState('week', new Date(2026, 8, 9)), before: SCROLL_WINDOW_MAX.week };
|
|
expect(computeScrollWindow(state, opts).canExtendStart).toBe(false);
|
|
expect(computeScrollWindow(state, opts).canExtendEnd).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('growScrollWindow', () => {
|
|
it('doubles a side from the first step up to the cap and then stays put', () => {
|
|
let state = freshScrollWindowState('agenda', new Date(2026, 8, 9));
|
|
const seen: number[] = [];
|
|
for (let i = 0; i < 8; i++) {
|
|
state = growScrollWindow(state, 'before');
|
|
seen.push(state.before);
|
|
}
|
|
expect(seen).toEqual([SCROLL_WINDOW_STEP, 60, 120, 240, 365, 365, 365, 365]);
|
|
const capped = growScrollWindow(state, 'before');
|
|
expect(capped).toBe(state);
|
|
});
|
|
});
|
|
|
|
describe('normalizeScrollWindowState / scrollWindowContains', () => {
|
|
it('starts over when the view mode changed', () => {
|
|
const month = freshScrollWindowState('month', new Date(2026, 8, 9));
|
|
expect(normalizeScrollWindowState(month, 'month', new Date(2026, 0, 1))).toBe(month);
|
|
const week = normalizeScrollWindowState(month, 'week', new Date(2026, 0, 1));
|
|
expect(week.mode).toBe('week');
|
|
expect(week.anchorKey).toBe('2026-01-01');
|
|
});
|
|
|
|
it('knows whether a navigation target is already loaded', () => {
|
|
const win = computeScrollWindow(freshScrollWindowState('month', new Date(2026, 8, 9)), opts);
|
|
expect(scrollWindowContains(win, 'month', new Date(2026, 9, 15), opts)).toBe(true); // October grid ends Nov 1
|
|
expect(scrollWindowContains(win, 'month', new Date(2026, 10, 15), opts)).toBe(false); // November grid ends Dec 6
|
|
expect(scrollWindowContains(win, 'week', new Date(2026, 7, 31), opts)).toBe(true);
|
|
expect(scrollWindowContains(win, 'week', new Date(2026, 7, 30), opts)).toBe(false);
|
|
});
|
|
|
|
it('keys the anchor by calendar day', () => {
|
|
expect(freshScrollWindowState('day', new Date(2026, 8, 9, 23, 59)).anchorKey).toBe('2026-09-09');
|
|
expect(key(new Date(Date.UTC(2026, 8, 9)))).toBe('2026-09-09');
|
|
});
|
|
});
|