Skip to content

Commit

Permalink
🪟 🔧 Fix DatePicker test timezones (#7116)
Browse files Browse the repository at this point in the history
  • Loading branch information
josephkmh committed Jun 27, 2023
1 parent 5e1fae2 commit c56c2cb
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 89 deletions.
2 changes: 1 addition & 1 deletion airbyte-webapp/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// eslint-disable-next-line jest/no-jest-import
import type { Config } from "jest";

const jestConfig: Config = {
Expand All @@ -17,6 +16,7 @@ const jestConfig: Config = {
"\\.svg$": "test-utils/mock-data/mockSvg.js",
},
setupFilesAfterEnv: ["./src/test-utils/setup-tests.ts"],
globalSetup: "./src/test-utils/global-setup.js",
};

export default jestConfig;
1 change: 0 additions & 1 deletion airbyte-webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@
"stylelint-config-standard": "^26.0.0",
"stylelint-config-standard-scss": "^5.0.0",
"tar": "^6.1.11",
"timezone-mock": "^1.3.4",
"tmpl": "^1.0.5",
"ts-node": "^10.8.1",
"typescript": "^5.0.0",
Expand Down
6 changes: 0 additions & 6 deletions airbyte-webapp/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

145 changes: 71 additions & 74 deletions airbyte-webapp/src/components/ui/DatePicker/DatePicker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import dayjs from "dayjs";
import timezoneMock from "timezone-mock";

import { TestWrapper } from "test-utils/testutils";

import { DatePicker, toEquivalentLocalTime } from "./DatePicker";

describe("Timezones", () => {
it("should always be US/Pacific", () => {
expect(new Date().getTimezoneOffset()).toBe(420);
});
});

describe(`${toEquivalentLocalTime.name}`, () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date("2000-01-01T00:00:00"));
});

afterAll(() => {
jest.useRealTimers();
});

// Seems silly, but dayjs has a bug when formatting years, so this is a useful test:
// https://github.com/iamkun/dayjs/issues/1745
it("handles a date in the year 1", () => {
Expand All @@ -27,8 +40,7 @@ describe(`${toEquivalentLocalTime.name}`, () => {
});

it("outputs the same YYYY-MM-DDTHH:mm:ss", () => {
timezoneMock.register("Etc/GMT+10");
const TEST_TIMEZONE_UTC_OFFSET_IN_MINUTES = 600; // corresponds to GMT+10
const TEST_TIMEZONE_UTC_OFFSET_IN_MINUTES = dayjs().utcOffset() * -1; // corresponds to our global test timezone of US/Pacific, and accounts for dayjs using a negative offset
const TEST_UTC_TIMESTAMP = "2000-01-01T12:00:00Z";

const result = toEquivalentLocalTime(TEST_UTC_TIMESTAMP);
Expand All @@ -37,23 +49,17 @@ describe(`${toEquivalentLocalTime.name}`, () => {
expect(dayjs(result).format().substring(0, 19)).toEqual(TEST_UTC_TIMESTAMP.substring(0, 19));
expect(result?.getTimezoneOffset()).toEqual(TEST_TIMEZONE_UTC_OFFSET_IN_MINUTES);
});
});

it("keeps a utc timestamp exactly the same", () => {
timezoneMock.register("UTC");
const TEST_UTC_TIMESTAMP = "2022-01-01T00:00:00Z";

const expectedDateObject = dayjs.utc(TEST_UTC_TIMESTAMP).toDate();

expect(toEquivalentLocalTime(TEST_UTC_TIMESTAMP)).toEqual(expectedDateObject);
describe(`${DatePicker.name}`, () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date("2010-09-05T00:00:00"));
});

afterEach(() => {
// Return global Date() object to system behavior
timezoneMock.unregister();
afterAll(() => {
jest.useRealTimers();
});
});

describe(`${DatePicker.name}`, () => {
it("allows typing a date manually", async () => {
const MOCK_DESIRED_DATETIME = "2010-09-12T00:00:00Z";
let mockValue = "";
Expand All @@ -70,13 +76,13 @@ describe(`${DatePicker.name}`, () => {
);

const input = screen.getByTestId("input");
await userEvent.type(input, MOCK_DESIRED_DATETIME, { delay: 1 });
// Important: input delay was removed here because it conflicts with jest.useFakeTimers() in user-event < 14.0.0
userEvent.type(input, MOCK_DESIRED_DATETIME);

expect(mockValue).toEqual(MOCK_DESIRED_DATETIME);
});

it("allows selecting a date from the datepicker", async () => {
jest.useFakeTimers().setSystemTime(new Date("2010-09-05"));
const MOCK_DESIRED_DATE = "2010-09-12";
let mockValue = "";
render(
Expand All @@ -97,66 +103,58 @@ describe(`${DatePicker.name}`, () => {
userEvent.click(date);

expect(mockValue).toEqual(MOCK_DESIRED_DATE);
jest.useRealTimers();
});

// eslint-disable-next-line jest/no-commented-out-tests
// it("allows selecting a datetime from the datepicker", async () => {
// jest.useFakeTimers().setSystemTime(new Date("2010-09-05"));
// const MOCK_DESIRED_DATETIME = "2010-09-05T12:00:00Z";
// let mockValue = "";
// render(
// <TestWrapper>
// <DatePicker
// onChange={(value) => {
// // necessary for controlled inputs https://github.com/testing-library/user-event/issues/387#issuecomment-819868799
// mockValue = mockValue + value;
// }}
// value={mockValue}
// withTime
// />
// </TestWrapper>
// );

// const datepicker = screen.getByLabelText("Open datepicker");
// userEvent.click(datepicker);
// const date = screen.getByText("12:00 PM");
// userEvent.click(date);

// expect(mockValue).toEqual(MOCK_DESIRED_DATETIME);
// jest.useRealTimers();
// });

// eslint-disable-next-line jest/no-commented-out-tests
// it("allows selecting a datetime with milliseconds from the datepicker", async () => {
// jest.useFakeTimers().setSystemTime(new Date("2010-09-05"));
// const MOCK_DESIRED_DATETIME = "2010-09-05T12:00:00.000Z";
// let mockValue = "";
// render(
// <TestWrapper>
// <DatePicker
// onChange={(value) => {
// // necessary for controlled inputs https://github.com/testing-library/user-event/issues/387#issuecomment-819868799
// mockValue = mockValue + value;
// }}
// value={mockValue}
// withTime
// withMilliseconds
// />
// </TestWrapper>
// );

// const datepicker = screen.getByLabelText("Open datepicker");
// userEvent.click(datepicker);
// const date = screen.getByText("12:00 PM");
// userEvent.click(date);

// expect(mockValue).toEqual(MOCK_DESIRED_DATETIME);
// jest.useRealTimers();
// });
it("allows selecting a datetime from the datepicker", async () => {
const MOCK_DESIRED_DATETIME = "2010-09-05T12:00:00Z";
let mockValue = "";
render(
<TestWrapper>
<DatePicker
onChange={(value) => {
// necessary for controlled inputs https://github.com/testing-library/user-event/issues/387#issuecomment-819868799
mockValue = mockValue + value;
}}
value={mockValue}
withTime
/>
</TestWrapper>
);

const datepicker = screen.getByLabelText("Open datepicker");
userEvent.click(datepicker);
const date = screen.getByText("12:00 PM");
userEvent.click(date);

expect(mockValue).toEqual(MOCK_DESIRED_DATETIME);
});

it("allows selecting a datetime with milliseconds from the datepicker", async () => {
const MOCK_DESIRED_DATETIME = "2010-09-05T12:00:00.000Z";
let mockValue = "";
render(
<TestWrapper>
<DatePicker
onChange={(value) => {
// necessary for controlled inputs https://github.com/testing-library/user-event/issues/387#issuecomment-819868799
mockValue = mockValue + value;
}}
value={mockValue}
withTime
withMilliseconds
/>
</TestWrapper>
);

const datepicker = screen.getByLabelText("Open datepicker");
userEvent.click(datepicker);
const date = screen.getByText("12:00 PM");
userEvent.click(date);

expect(mockValue).toEqual(MOCK_DESIRED_DATETIME);
});

it("focuses the input after selecting a date from the datepicker", async () => {
jest.useFakeTimers().setSystemTime(new Date("2010-09-05"));
let mockValue = "";
render(
<TestWrapper>
Expand All @@ -172,6 +170,5 @@ describe(`${DatePicker.name}`, () => {
const input = screen.getByTestId("input");

expect(input).toHaveFocus();
jest.useRealTimers();
});
});
14 changes: 7 additions & 7 deletions airbyte-webapp/src/components/ui/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,25 @@ import { Input } from "../Input";
* 2022-01-01T10:00:00+01:00 - what react-datepicker might convert this date into and display (e.g. 10:00am - bad!)
* 2022-01-01T09:00:00+01:00 - what we give react-datepicker instead, to trick it (User sees 9:00am - good!)
*/
export const toEquivalentLocalTime = (input: string): Date | undefined => {
if (!input) {
export const toEquivalentLocalTime = (utcString: string): Date | undefined => {
if (!utcString) {
return undefined;
}

const date = dayjs.utc(input);
const date = dayjs.utc(utcString);

if (!date?.isValid()) {
return undefined;
}

// Get the user's UTC offset based on the local time
const browserUtcOffset = dayjs().utcOffset();
// Get the user's UTC offset based on the local timezone and the given date
const browserUtcOffset = dayjs(utcString).utcOffset();

// Convert the selected date into a string which we can use to initialize a new date object.
// The second parameter to utcOffset() keeps the same local time, only changing the timezone.
const dateInUtcAsString = date.utcOffset(browserUtcOffset, true).format();
const localDateAsString = date.utcOffset(browserUtcOffset, true).format();

const equivalentDate = dayjs(dateInUtcAsString);
const equivalentDate = dayjs(localDateAsString);

// dayjs does not 0-pad years when formatting, so it's possible to have an invalid date here
// https://github.com/iamkun/dayjs/issues/1745
Expand Down
4 changes: 4 additions & 0 deletions airbyte-webapp/src/test-utils/global-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Timezone setup as described here https://stackoverflow.com/a/56482581
module.exports = async () => {
process.env.TZ = "US/Pacific";
};

0 comments on commit c56c2cb

Please sign in to comment.