test: fix timezone issues in EventDisplay tests

- Set explicit UTC timezone globally in jest.config.ts and setupTests.ts
- Mock Intl.DateTimeFormat to use UTC by default while preserving prototype
- Replace regex patterns with exact values for declarative assertions
- Remove complex date formatting computations in tests
- Use fixed dates (2025-01-15T10:00:00.000Z) for consistent test results
- Fix EventDisplay.test.tsx to use exact time values instead of regex
This commit is contained in:
lenhanphung
2025-10-10 15:04:03 +07:00
committed by Benoit TELLIER
parent 59e7992cfe
commit 192ab17789
3 changed files with 59 additions and 46 deletions
+29 -46
View File
@@ -17,8 +17,7 @@ import {
describe("Event Preview Display", () => { describe("Event Preview Display", () => {
const mockOnClose = jest.fn(); const mockOnClose = jest.fn();
const day = new Date(); const day = new Date("2025-01-15T10:00:00.000Z"); // Fixed date in UTC: Jan 15, 2025 10:00 AM UTC
const RealDateToLocaleString = Date.prototype.toLocaleString;
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -102,13 +101,6 @@ describe("Event Preview Display", () => {
}; };
it("renders correctly event data", () => { it("renders correctly event data", () => {
jest.spyOn(Date.prototype, "toLocaleString").mockImplementation(function (
this: Date,
locales?: Intl.LocalesArgument,
options?: Intl.DateTimeFormatOptions | undefined
): string {
return RealDateToLocaleString.call(this, "en-UK", options);
});
renderWithProviders( renderWithProviders(
<EventPreviewModal <EventPreviewModal
open={true} open={true}
@@ -118,19 +110,20 @@ describe("Event Preview Display", () => {
/>, />,
preloadedState preloadedState
); );
const weekday = day.toLocaleString("en-UK", { weekday: "long" });
const month = day.toLocaleString("en-UK", { month: "long" });
const dayOfMonth = day.getDate().toString();
// With UTC timezone, we can assert exact values
// Date: January 15, 2025 10:00 AM UTC
expect(screen.getByText("Test Event")).toBeInTheDocument(); expect(screen.getByText("Test Event")).toBeInTheDocument();
expect(screen.getByText(new RegExp(weekday, "i"))).toBeInTheDocument(); expect(screen.getByText(/Wednesday/i)).toBeInTheDocument();
expect( expect(screen.getByText(/January/i)).toBeInTheDocument();
screen.getByText(new RegExp(`\\b${dayOfMonth}\\b ${month}`)) expect(screen.getByText(/15/)).toBeInTheDocument();
).toBeInTheDocument();
// Check time range is displayed (format may vary by locale/implementation) // Check time is displayed with exact values
// Use regex to match time pattern since exact format depends on component implementation // Format: "Wednesday, January 15, 2025 at 10:00 AM" and " 10:00 AM" are in separate elements
expect(screen.getByText(/\d{2}:\d{2} \d{2}:\d{2}/)).toBeInTheDocument(); expect(
screen.getByText(/Wednesday, January 15, 2025 at 10:00 AM/)
).toBeInTheDocument();
expect(screen.getByText(/ 10:00 AM/)).toBeInTheDocument();
expect(screen.getByText("Calendar")).toBeInTheDocument(); expect(screen.getByText("Calendar")).toBeInTheDocument();
}); });
@@ -569,7 +562,7 @@ describe("Event Preview Display", () => {
describe("Event Full Display", () => { describe("Event Full Display", () => {
const mockOnClose = jest.fn(); const mockOnClose = jest.fn();
const day = new Date(); const day = new Date("2025-01-15T10:00:00.000Z"); // Fixed date in UTC: Jan 15, 2025 10:00 AM UTC
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -645,20 +638,12 @@ describe("Event Full Display", () => {
it("renders correctly event data with fixed timezone", () => { it("renders correctly event data with fixed timezone", () => {
// Use fixed timezone UTC for consistent test results across all environments // Use fixed timezone UTC for consistent test results across all environments
// Calculate expected local time from UTC time for exact value matching
const fixedDate = new Date("2025-01-15T10:00:00.000Z"); // 10AM UTC const fixedDate = new Date("2025-01-15T10:00:00.000Z"); // 10AM UTC
const endDate = new Date(fixedDate.getTime() + 3600000); // 11AM UTC const endDate = new Date(fixedDate.getTime() + 3600000); // 11AM UTC
// Format expected values in local timezone (what formatLocalDateTime produces) // With UTC timezone set, formatLocalDateTime produces predictable values
const pad = (n: number) => n.toString().padStart(2, "0"); const expectedStart = "2025-01-15T10:00"; // 10:00 AM UTC
const formatLocal = (date: Date) => { const expectedEnd = "2025-01-15T11:00"; // 11:00 AM UTC
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
date.getDate()
)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
};
const expectedStart = formatLocal(fixedDate);
const expectedEnd = formatLocal(endDate);
const stateWithFixedDate = { const stateWithFixedDate = {
...preloadedState, ...preloadedState,
@@ -693,11 +678,10 @@ describe("Event Full Display", () => {
expect(screen.getByDisplayValue("Test Event")).toBeInTheDocument(); expect(screen.getByDisplayValue("Test Event")).toBeInTheDocument();
// Use exact equality assertions with calculated local time values // Exact value assertions with declarative expected values
const startInput = screen.getByLabelText("Start"); const startInput = screen.getByLabelText("Start");
expect(startInput).toBeInTheDocument(); expect(startInput).toBeInTheDocument();
expect(startInput).toHaveAttribute("type", "datetime-local"); expect(startInput).toHaveAttribute("type", "datetime-local");
// Exact value assertion - formatLocalDateTime converts UTC to local time
expect(startInput.getAttribute("value")).toBe(expectedStart); expect(startInput.getAttribute("value")).toBe(expectedStart);
const endInput = screen.getByLabelText("End"); const endInput = screen.getByLabelText("End");
@@ -1161,7 +1145,7 @@ describe("Event Full Display", () => {
.mockImplementation((payload) => () => Promise.resolve(payload) as any); .mockImplementation((payload) => () => Promise.resolve(payload) as any);
const spyRemove = jest.spyOn(eventThunks, "removeEvent"); const spyRemove = jest.spyOn(eventThunks, "removeEvent");
const day = new Date(); const testDate = new Date("2025-01-15T10:00:00.000Z");
const preloadedTwoCals = { const preloadedTwoCals = {
...preloadedState, ...preloadedState,
calendars: { calendars: {
@@ -1176,8 +1160,8 @@ describe("Event Full Display", () => {
id: "event1", id: "event1",
title: "Test Event", title: "Test Event",
calId: "667037022b752d0026472254/cal1", calId: "667037022b752d0026472254/cal1",
start: day.toISOString(), start: testDate.toISOString(),
end: day.toISOString(), end: testDate.toISOString(),
organizer: { cn: "test", cal_address: "test@test.com" }, organizer: { cn: "test", cal_address: "test@test.com" },
attendee: [{ cn: "test", cal_address: "test@test.com" }], attendee: [{ cn: "test", cal_address: "test@test.com" }],
}, },
@@ -1229,16 +1213,15 @@ describe("Event Full Display", () => {
// Mock getEvent API call to avoid API errors in test // Mock getEvent API call to avoid API errors in test
const EventApi = require("../../../src/features/Events/EventApi"); const EventApi = require("../../../src/features/Events/EventApi");
const testDate = new Date("2025-01-15T10:00:00.000Z");
jest.spyOn(EventApi, "getEvent").mockResolvedValue({ jest.spyOn(EventApi, "getEvent").mockResolvedValue({
uid: "base", uid: "base",
title: "Recurring event", title: "Recurring event",
calId: "667037022b752d0026472254/cal1", calId: "667037022b752d0026472254/cal1",
start: new Date().toISOString(), start: testDate.toISOString(),
end: new Date().toISOString(), end: testDate.toISOString(),
repetition: { freq: "daily", interval: 1 }, repetition: { freq: "daily", interval: 1 },
}); });
const day = new Date();
const preloadedRecurrence = { const preloadedRecurrence = {
...preloadedState, ...preloadedState,
calendars: { calendars: {
@@ -1252,8 +1235,8 @@ describe("Event Full Display", () => {
uid: "base", uid: "base",
calId: "667037022b752d0026472254/cal1", calId: "667037022b752d0026472254/cal1",
title: "Recurring event", title: "Recurring event",
start: day.toISOString(), start: testDate.toISOString(),
end: day.toISOString(), end: testDate.toISOString(),
organizer: { cal_address: "test@test.com" }, organizer: { cal_address: "test@test.com" },
attendee: [{ cal_address: "test@test.com", cn: "Test" }], attendee: [{ cal_address: "test@test.com", cn: "Test" }],
repetition: { freq: "daily", interval: 1 }, repetition: { freq: "daily", interval: 1 },
@@ -1262,8 +1245,8 @@ describe("Event Full Display", () => {
uid: "base/20250101", uid: "base/20250101",
calId: "667037022b752d0026472254/cal1", calId: "667037022b752d0026472254/cal1",
title: "Recurring event", title: "Recurring event",
start: day.toISOString(), start: testDate.toISOString(),
end: day.toISOString(), end: testDate.toISOString(),
organizer: { cal_address: "test@test.com" }, organizer: { cal_address: "test@test.com" },
attendee: [{ cal_address: "test@test.com", cn: "Test" }], attendee: [{ cal_address: "test@test.com", cn: "Test" }],
}, },
@@ -1271,8 +1254,8 @@ describe("Event Full Display", () => {
uid: "base/20250201", uid: "base/20250201",
calId: "667037022b752d0026472254/cal1", calId: "667037022b752d0026472254/cal1",
title: "Recurring event", title: "Recurring event",
start: day.toISOString(), start: testDate.toISOString(),
end: day.toISOString(), end: testDate.toISOString(),
organizer: { cal_address: "test@test.com" }, organizer: { cal_address: "test@test.com" },
attendee: [{ cal_address: "test@test.com", cn: "Test" }], attendee: [{ cal_address: "test@test.com", cn: "Test" }],
}, },
+3
View File
@@ -1,5 +1,8 @@
import type { Config } from "jest"; import type { Config } from "jest";
// Set timezone to UTC for consistent test results across all environments
process.env.TZ = "UTC";
const config: Config = { const config: Config = {
collectCoverage: true, collectCoverage: true,
coverageDirectory: "coverage", coverageDirectory: "coverage",
+27
View File
@@ -4,6 +4,33 @@ import { TextEncoder } from "util";
global.TextEncoder = TextEncoder; global.TextEncoder = TextEncoder;
// Set timezone to UTC for consistent test results across all environments
process.env.TZ = "UTC";
// Mock Intl.DateTimeFormat to use UTC timezone by default
const OriginalDateTimeFormat = Intl.DateTimeFormat;
// Create a constructor function that preserves prototype
const MockedDateTimeFormat: any = function (
this: any,
locales?: string | string[],
options?: Intl.DateTimeFormatOptions
) {
// Only set timeZone to UTC if not explicitly specified
const modifiedOptions = options
? { timeZone: "UTC", ...options } // timeZone comes first so options can override it
: { timeZone: "UTC" };
return new OriginalDateTimeFormat(locales, modifiedOptions);
};
// Preserve the prototype so jest.spyOn works on methods
MockedDateTimeFormat.prototype = OriginalDateTimeFormat.prototype;
// Preserve static methods
Object.setPrototypeOf(MockedDateTimeFormat, OriginalDateTimeFormat);
global.Intl.DateTimeFormat = MockedDateTimeFormat;
jest.mock("openid-client", () => ({ jest.mock("openid-client", () => ({
discovery: jest.fn(), discovery: jest.fn(),
randomPKCECodeVerifier: jest.fn(), randomPKCECodeVerifier: jest.fn(),