* feat(v5): UI updates * fix: css file size * fix: e2e tests * fix: e2e tests * fix: e2e tests * chore: add theme toggle to react demo example * Update sources panel display, update dark theme * fix: lint * fix(askai): address ui review feedback * fix: pin icon positioning * fix(askai): improve a11y and dark-mode shimmer for thinking and error states - add role=alert/status and aria-hidden on error/thinking UI - support dark-mode shimmer gradients via CSS variables - respect prefers-reduced-motion for shimmer - handle null date in useRelativeFormattedDate with fallback translation
136 lines
4.4 KiB
TypeScript
136 lines
4.4 KiB
TypeScript
import { renderHook } from '@testing-library/react';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { useRelativeFormattedDate } from '../useRelativeFormattedDate';
|
|
|
|
const NOW = new Date('2024-01-15T12:00:00.000Z').getTime();
|
|
|
|
describe('useRelativeFormattedDate', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(NOW);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('returns days when elapsed time is at least one day', () => {
|
|
const start = new Date(NOW - 3 * 24 * 60 * 60 * 1000);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('3 days ago');
|
|
});
|
|
|
|
it('returns hours when elapsed time is at least one hour but less than a day', () => {
|
|
const start = new Date(NOW - 5 * 60 * 60 * 1000);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('5 hours ago');
|
|
});
|
|
|
|
it('returns minutes when elapsed time is at least one minute but less than an hour', () => {
|
|
const start = new Date(NOW - 42 * 60 * 1000);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('42 minutes ago');
|
|
});
|
|
|
|
it('returns seconds when elapsed time is at least one second but less than a minute', () => {
|
|
const start = new Date(NOW - 30 * 1000);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('30 seconds ago');
|
|
});
|
|
|
|
it('falls back to seconds for sub-second differences', () => {
|
|
const start = new Date(NOW - 500);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('0 seconds ago');
|
|
});
|
|
|
|
it('returns zero seconds when start and end are equal', () => {
|
|
const start = new Date(NOW);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('0 seconds ago');
|
|
});
|
|
|
|
it('returns null when start is null', () => {
|
|
const { result } = renderHook(() => useRelativeFormattedDate(null));
|
|
|
|
expect(result.current).toBeNull();
|
|
});
|
|
|
|
it('floors the elapsed value to the unit', () => {
|
|
// 2 hours and 59 minutes -> 2 hours
|
|
const start = new Date(NOW - (2 * 60 * 60 * 1000 + 59 * 60 * 1000));
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('2 hours ago');
|
|
});
|
|
|
|
it('uses the largest matching unit at boundaries', () => {
|
|
// Exactly one day
|
|
const start = new Date(NOW - 24 * 60 * 60 * 1000);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('1 day ago');
|
|
});
|
|
|
|
it('uses the absolute value when end is before start (future dates)', () => {
|
|
const start = new Date(NOW + 3 * 60 * 60 * 1000);
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
expect(result.current).toEqual('3 hours ago');
|
|
});
|
|
|
|
it('respects an explicit end value over Date.now()', () => {
|
|
const start = new Date('2024-01-01T00:00:00.000Z');
|
|
const end = new Date('2024-01-08T00:00:00.000Z').getTime();
|
|
const { result } = renderHook(() => useRelativeFormattedDate(start, end));
|
|
|
|
expect(result.current).toEqual('7 days ago');
|
|
});
|
|
|
|
it('returns a stable reference when inputs do not change', () => {
|
|
const start = new Date(NOW - 60 * 60 * 1000);
|
|
const end = NOW;
|
|
const { result, rerender } = renderHook(() => useRelativeFormattedDate(start, end));
|
|
|
|
const first = result.current;
|
|
rerender();
|
|
|
|
expect(result.current).toBe(first);
|
|
});
|
|
|
|
it('does not recompute when inputs change', () => {
|
|
const end = NOW;
|
|
const { result, rerender } = renderHook(({ start }) => useRelativeFormattedDate(start, end), {
|
|
initialProps: { start: new Date(NOW - 60 * 60 * 1000) },
|
|
});
|
|
|
|
const first = result.current;
|
|
|
|
expect(result.current).toEqual('1 hour ago');
|
|
|
|
rerender({ start: new Date(NOW - 2 * 24 * 60 * 60 * 1000) });
|
|
|
|
expect(result.current).toBe(first);
|
|
expect(result.current).toEqual('1 hour ago');
|
|
});
|
|
|
|
it('does not recompute when Date.now changes', () => {
|
|
const start = new Date(NOW - 60 * 60 * 1000);
|
|
const { result, rerender } = renderHook(() => useRelativeFormattedDate(start));
|
|
|
|
const first = result.current;
|
|
|
|
vi.setSystemTime(NOW + 2 * 24 * 60 * 60 * 1000);
|
|
rerender();
|
|
|
|
expect(result.current).toBe(first);
|
|
expect(result.current).toEqual('1 hour ago');
|
|
});
|
|
});
|