date-groups.test.ts3.4 KBView on GitHub import { act, renderHook } from '@testing-library/react';
import {
DATE_GROUP_ORDER,
getDateGroup,
startOfLocalDay,
useLocalDayStart,
} from '@/modules/threads/threadList/utils/date-groups';
// Local-midnight boundary for a fixed "now". All fixtures below are expressed
// in local time so the assertions hold in any TZ the suite runs under.
const local = (y: number, m: number, d: number, h = 0, min = 0) =>
new Date(y, m - 1, d, h, min).toISOString();
const todayStart = startOfLocalDay(new Date(2026, 6, 21, 10, 30)); // Jul 21 2026, local
describe('getDateGroup', () => {
it('buckets by calendar day, not a rolling 24 hours', () => {
// 18:55 yesterday is under 24h before 10:30 today, but it is still a
// different calendar day — the case that put a Jul 20 thread under "Today".
expect(getDateGroup(local(2026, 7, 20, 18, 55), todayStart)).toBe('Yesterday');
// …and 00:05 today is only ~10h earlier yet belongs to "Today".
expect(getDateGroup(local(2026, 7, 21, 0, 5), todayStart)).toBe('Today');
});
it('treats local midnight as the boundary', () => {
expect(getDateGroup(local(2026, 7, 20, 23, 59), todayStart)).toBe('Yesterday');
expect(getDateGroup(local(2026, 7, 21, 0, 0), todayStart)).toBe('Today');
});
it('covers the remaining buckets', () => {
expect(getDateGroup(local(2026, 7, 21, 23, 59), todayStart)).toBe('Today');
expect(getDateGroup(local(2026, 7, 19, 12, 0), todayStart)).toBe('Last 7 days');
expect(getDateGroup(local(2026, 7, 15, 12, 0), todayStart)).toBe('Last 7 days');
expect(getDateGroup(local(2026, 7, 14, 23, 59), todayStart)).toBe('Older');
});
it('falls back to Older for missing or unparseable dates', () => {
expect(getDateGroup(undefined, todayStart)).toBe('Older');
expect(getDateGroup('not-a-date', todayStart)).toBe('Older');
});
it('exposes every bucket in render order', () => {
expect(DATE_GROUP_ORDER).toEqual(['Today', 'Yesterday', 'Last 7 days', 'Older']);
});
});
// The headline fix: a tab left open across midnight must re-emit the day boundary, or a
// thread received yesterday evening keeps rendering under "Today" the next morning.
describe('useLocalDayStart', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('re-emits the new local midnight when the clock rolls over', () => {
// 30s before local midnight on Jul 21.
jest.setSystemTime(new Date(2026, 6, 21, 23, 59, 30));
const { result } = renderHook(() => useLocalDayStart());
expect(result.current.getTime()).toBe(startOfLocalDay(new Date(2026, 6, 21)).getTime());
// Cross local midnight: the armed timeout (fires ~1s past the boundary) runs.
act(() => {
jest.advanceTimersByTime(31_000 + 1_000);
});
expect(result.current.getTime()).toBe(startOfLocalDay(new Date(2026, 6, 22)).getTime());
});
it('does not churn the boundary when the tab wakes on the same day', () => {
jest.setSystemTime(new Date(2026, 6, 21, 9, 0, 0));
const { result } = renderHook(() => useLocalDayStart());
const first = result.current;
// A visibility re-check mid-morning recomputes the same day — the functional
// setState must return the prior reference so it doesn't re-bucket the list.
act(() => {
document.dispatchEvent(new Event('visibilitychange'));
});
expect(result.current).toBe(first);
});
});