You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ghost/apps/stats/test/utils/date-testing-utils.ts

138 lines
4.1 KiB

import {MockInstance, vi} from 'vitest';
/**
* Date testing utilities to provide consistent, reliable date mocking
* Addresses issues with timezone-dependent tests and timing-sensitive assertions
*/
// Fixed date for consistent testing
export const FIXED_DATE = new Date('2024-01-15T12:00:00.000Z');
export const FIXED_DATE_STRING = '2024-01-15';
/**
* Mock system date to a fixed point in time
* Prevents tests from failing due to timing issues across midnight
*/
export const mockSystemDate = (date: Date = FIXED_DATE): MockInstance => {
const mockDate = vi.spyOn(Date, 'now').mockReturnValue(date.getTime());
vi.setSystemTime(date);
return mockDate;
};
/**
* Restore system date to normal behavior
*/
export const restoreSystemDate = () => {
vi.useRealTimers();
vi.restoreAllMocks();
};
/**
* Calculate expected date range based on a fixed starting point
* This replaces the fragile moment().subtract() pattern
*/
export const getExpectedDateRange = (days: number, baseDate: Date = FIXED_DATE) => {
const startDate = new Date(baseDate);
startDate.setDate(startDate.getDate() - (days - 1));
return {
expectedDateFrom: startDate.toISOString().split('T')[0], // YYYY-MM-DD format
expectedDateTo: baseDate.toISOString().split('T')[0]
};
};
/**
* Create a date range for testing that spans specific days
*/
export const createDateRange = (startDaysAgo: number, endDaysAgo: number = 0, baseDate: Date = FIXED_DATE) => {
const startDate = new Date(baseDate);
const endDate = new Date(baseDate);
startDate.setDate(startDate.getDate() - startDaysAgo);
endDate.setDate(endDate.getDate() - endDaysAgo);
return {
startDate,
endDate,
startDateString: startDate.toISOString().split('T')[0],
endDateString: endDate.toISOString().split('T')[0]
};
};
/**
* Mock the getRangeDates function from @tryghost/shade
* Provides consistent date ranges for testing
*/
export const mockGetRangeDates = () => {
return (range: number) => {
const {expectedDateFrom, expectedDateTo} = getExpectedDateRange(range);
return {
startDate: new Date(expectedDateFrom + 'T00:00:00.000Z'),
endDate: new Date(expectedDateTo + 'T23:59:59.999Z'),
timezone: 'UTC'
};
};
};
/**
* Mock the formatQueryDate function from @tryghost/shade
* Provides consistent date formatting
*/
export const mockFormatQueryDate = () => {
return (date: Date) => date.toISOString().split('T')[0];
};
/**
* Common date test scenarios
*/
export const DATE_TEST_SCENARIOS = {
today: {range: 1, description: 'today'},
lastWeek: {range: 7, description: 'last 7 days'},
lastMonth: {range: 30, description: 'last 30 days'},
last3Months: {range: 90, description: 'last 3 months'},
yearToDate: {range: -1, description: 'year to date'}
} as const;
/**
* Setup date mocking for hook tests
* This should be called in beforeEach for hooks that use date functions
*/
export const setupDateMocking = () => {
const mockDate = mockSystemDate();
// Mock external date functions
vi.mock('@tryghost/shade', async () => {
const actual = await vi.importActual('@tryghost/shade') as Record<string, unknown>;
return {
...actual,
getRangeDates: vi.fn().mockImplementation(mockGetRangeDates()),
formatQueryDate: vi.fn().mockImplementation(mockFormatQueryDate())
};
});
return {
mockDate,
cleanup: restoreSystemDate
};
};
/**
* Test helper for date-dependent API calls
* Verifies that API calls were made with expected date parameters
*/
export const expectApiCallWithDateRange = (
mockApiCall: MockInstance,
range: number,
additionalParams: Record<string, unknown> = {}
) => {
const {expectedDateFrom, expectedDateTo} = getExpectedDateRange(range);
expect(mockApiCall).toHaveBeenCalledWith({
searchParams: {
date_from: expectedDateFrom,
date_to: expectedDateTo,
...additionalParams
},
enabled: expect.any(Boolean)
});
};