diff --git a/package.json b/package.json index d1791eb..bb6c893 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", "antd": "^5.14.0", + "date-fns": "^3.3.1", "eslint": "^8.55.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", @@ -87,6 +88,7 @@ "eslint-plugin-testing-library": "^6.2.0", "jsdom": "^24.0.0", "json-schema-to-ts": "^3.0.0", + "rc-picker": "^4.2.0", "prettier": "3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -100,6 +102,8 @@ "lodash.isempty": "^4.4.0", "lodash.merge": "^4.6.2", "lodash.range": "^3.2.0", - "lodash.startcase": "^4.4.0" + "lodash.startcase": "^4.4.0", + "date-fns": "^3.3.1", + "rc-picker": "^4.2.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54b82ca..bf26825 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + date-fns: + specifier: ^3.3.1 + version: 3.6.0 lodash.isempty: specifier: ^4.4.0 version: 4.4.0 @@ -17,6 +20,9 @@ dependencies: lodash.startcase: specifier: ^4.4.0 version: 4.4.0 + rc-picker: + specifier: ^4.2.0 + version: 4.3.0(date-fns@3.6.0)(react-dom@18.2.0)(react@18.2.0) devDependencies: '@ant-design/icons': @@ -90,7 +96,7 @@ devDependencies: version: 4.2.1(vite@5.0.12) antd: specifier: ^5.14.0 - version: 5.14.0(react-dom@18.2.0)(react@18.2.0) + version: 5.14.0(date-fns@3.6.0)(react-dom@18.2.0)(react@18.2.0) eslint: specifier: ^8.55.0 version: 8.56.0 @@ -1478,7 +1484,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - dev: true /@babel/template@7.24.0: resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} @@ -2982,7 +2987,6 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@rc-component/tour@1.12.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-U4mf1FiUxGCwrX4ed8op77Y8VKur+8Y/61ylxtqGbcSoh1EBC7bWd/DkLu0ClTUrKZInqEi1FL7YgFtnT90vHA==} @@ -3017,6 +3021,23 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@rc-component/trigger@2.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-niwKADPdY5dhdIblV6uwSayVivwo2uUISfJqri+/ovYQcH/omxDYBJKo755QKeoIIsWptxnRpgr7reEnNEZGFg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.24.0 + '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) + classnames: 2.5.1 + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@rollup/pluginutils@5.1.0: resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} @@ -4950,7 +4971,7 @@ packages: engines: {node: '>=12'} dev: true - /antd@5.14.0(react-dom@18.2.0)(react@18.2.0): + /antd@5.14.0(date-fns@3.6.0)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-LdRJnYd8dTykR2xr483zNE0mBKmWHMLqmjkfcX4otQRD0kaZjOwSmN74vMC70jnMM8oqhWILFjWy3dEy/E1W6w==} peerDependencies: react: '>=16.9.0' @@ -4984,7 +5005,7 @@ packages: rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) rc-notification: 5.3.0(react-dom@18.2.0)(react@18.2.0) rc-pagination: 4.0.4(react-dom@18.2.0)(react@18.2.0) - rc-picker: 4.0.0-alpha.44(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) + rc-picker: 4.0.0-alpha.44(date-fns@3.6.0)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) rc-progress: 3.5.1(react-dom@18.2.0)(react@18.2.0) rc-rate: 2.12.0(react-dom@18.2.0)(react@18.2.0) rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) @@ -5506,7 +5527,6 @@ packages: /classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - dev: true /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} @@ -5818,6 +5838,9 @@ packages: whatwg-url: 14.0.0 dev: true + /date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + /dayjs@1.11.10: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} dev: true @@ -7805,7 +7828,6 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} @@ -8129,7 +8151,6 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 - dev: true /loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -9445,7 +9466,6 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /rc-notification@5.3.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-WCf0uCOkZ3HGfF0p1H4Sgt7aWfipxORWTPp7o6prA3vxwtWhtug3GfpYls1pnBp4WA+j8vGIi5c2/hQRpGzPcQ==} @@ -9474,7 +9494,6 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /rc-pagination@4.0.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GGrLT4NgG6wgJpT/hHIpL9nELv27A1XbSZzECIuQBQTVSf4xGKxWr6I/jhpRPauYEWEbWVw22ObG6tJQqwJqWQ==} @@ -9489,7 +9508,7 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /rc-picker@4.0.0-alpha.44(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0): + /rc-picker@4.0.0-alpha.44(date-fns@3.6.0)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-OvzzTS4UZDT1qRfv4PRK/+LDpXWJ6sD0zv5LPC7fvprihT/YVvjrOQPicWLlw5GqrrqP4hqbQkWB4KXDNlb5ag==} engines: {node: '>=8.x'} peerDependencies: @@ -9512,6 +9531,7 @@ packages: '@babel/runtime': 7.24.0 '@rc-component/trigger': 1.18.3(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 + date-fns: 3.6.0 dayjs: 1.11.10 rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) @@ -9520,6 +9540,37 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /rc-picker@4.3.0(date-fns@3.6.0)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bQNB/+NdW55jlQ5lPnNqF5J90Tq4SihLbAF7tzPBvGDJyoYmDgwLm4FN0ZB3Ot9i1v6vJY/1mgqZZTT9jbYc5w==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@rc-component/trigger': 2.0.0(react-dom@18.2.0)(react@18.2.0) + classnames: 2.5.1 + date-fns: 3.6.0 + rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /rc-progress@3.5.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw==} peerDependencies: @@ -9559,7 +9610,6 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) resize-observer-polyfill: 1.5.1 - dev: true /rc-segmented@2.3.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-I3FtM5Smua/ESXutFfb8gJ8ZPcvFR+qUgeeGFQHBOvRiRKyAk4aBE5nfqrxXx+h8/vn60DQjOt6i4RNtrbOobg==} @@ -9751,7 +9801,6 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-is: 18.2.0 - dev: true /rc-virtual-list@3.11.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-NbBi0fvyIu26gP69nQBiWgUMTPX3mr4FcuBQiVqagU0BnuX8WQkiivnMs105JROeuUIFczLrlgUhLQwTWV1XDA==} @@ -9832,7 +9881,6 @@ packages: loose-envify: 1.4.0 react: 18.2.0 scheduler: 0.23.0 - dev: true /react-element-to-jsx-string@15.0.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==} @@ -9861,7 +9909,6 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} @@ -9925,7 +9972,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - dev: true /read-pkg-up@11.0.0: resolution: {integrity: sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==} @@ -10040,7 +10086,6 @@ packages: /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: true /regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} @@ -10122,7 +10167,6 @@ packages: /resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - dev: true /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -10269,7 +10313,6 @@ packages: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: loose-envify: 1.4.0 - dev: true /scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} diff --git a/src/common/schema-derived-types.ts b/src/common/schema-derived-types.ts index 0916bc9..da6a234 100644 --- a/src/common/schema-derived-types.ts +++ b/src/common/schema-derived-types.ts @@ -5,6 +5,7 @@ import { NumericControlOptions, OneOfControlOptions, TextControlOptions, + DateControlOptions, UISchema, } from ".." @@ -22,7 +23,7 @@ type JsonSchemaTypeToControlOptions< ? U extends "object" // ObjectControlOptions goes here ? unknown : U extends "string" - ? TextControlOptions + ? TextControlOptions | DateControlOptions : U extends "number" | "integer" ? NumericControlOptions : U extends "array" diff --git a/src/controls/DateControl.test.tsx b/src/controls/DateControl.test.tsx new file mode 100644 index 0000000..476c68f --- /dev/null +++ b/src/controls/DateControl.test.tsx @@ -0,0 +1,69 @@ +import { describe, expect, test, it } from "vitest" +import { render } from "../common/test-render" + +import { screen } from "@testing-library/react" +import { + dateSchema, + dateUISchema, + dateUISchemaWithRule, + dateUISchemaWithFormatOption, +} from "../testSchemas/dateSchema" +import { isDateControl, rankWith } from "@jsonforms/core" + +describe("DateControlTester", () => { + test("tester works", () => { + const uiSchema = { + type: "Control", + scope: "#/properties/date", + } + const schema = { + type: "string", + title: "The Future is Now", + format: "date", + } + const context = { rootSchema: dateSchema, config: {} } + + expect(rankWith(1, isDateControl)(uiSchema, schema, context)).toBe(1) + }) +}) +describe("DateControl", () => { + // FYI this date is treated as UTC + const timestamp: Date = new Date("2023-07-18") + const title = "The Future is Now" + test("renders a datetime input with no UISchema provided", async () => { + render({ + schema: dateSchema, + }) + await screen.findByText(title) + }) + + it("Follows the hide rule", () => { + render({ + data: { date: timestamp }, + schema: dateSchema, + uischema: dateUISchemaWithRule, + }) + expect(screen.queryByText(title)).toBeNull() + }) + + it("renders when data is included", async () => { + render({ + data: { date: timestamp }, + schema: dateSchema, + uischema: dateUISchema, + }) + await screen.findByText(title) + // Should use the default date format + await screen.findByDisplayValue("07/18/2023") + }) + + it("respects dateFormat", async () => { + render({ + data: { date: timestamp }, + schema: dateSchema, + uischema: dateUISchemaWithFormatOption, + }) + + await screen.findByDisplayValue("2023 07 18") + }) +}) diff --git a/src/controls/DateControl.tsx b/src/controls/DateControl.tsx new file mode 100644 index 0000000..e0c63e2 --- /dev/null +++ b/src/controls/DateControl.tsx @@ -0,0 +1,39 @@ +import { ControlProps } from "@jsonforms/core" +import { withJsonFormsControlProps } from "@jsonforms/react" +import { DatePicker, Form } from "antd" +import dateFnsGenerateConfig from "rc-picker/lib/generate/dateFns" +import { DateControlOptions } from ".." + +interface DateControlProps extends ControlProps { + data: string + handleChange(path: string, value: Date | null): void + path: string +} + +const MyDatePicker = DatePicker.generatePicker(dateFnsGenerateConfig) + +export function DateControl({ + data, + handleChange, + path, + label, + visible, + id, + required, + uischema, +}: DateControlProps) { + const options: DateControlOptions = + (uischema.options as DateControlOptions) ?? {} + const format = options.dateFormat ?? "MM/DD/YYYY" + return !visible ? null : ( + + handleChange(path, e)} + /> + + ) +} + +export const DateRenderer = withJsonFormsControlProps(DateControl) diff --git a/src/renderer-registry-entries.ts b/src/renderer-registry-entries.ts index c0b6aaf..0cdbe39 100644 --- a/src/renderer-registry-entries.ts +++ b/src/renderer-registry-entries.ts @@ -19,6 +19,7 @@ import { or, isPrimitiveArrayControl, isOneOfControl, + isDateControl, isAnyOfControl, } from "@jsonforms/core" import { withJsonFormsCellProps } from "@jsonforms/react" @@ -37,6 +38,7 @@ import { ObjectArrayRenderer } from "./controls/ObjectArrayControl" import { PrimitiveArrayRenderer } from "./controls/PrimitiveArrayControl" import { OneOfRenderer } from "./controls/combinators/OneOfControl" import { AnyOfRenderer } from "./controls/combinators/AnyOfControl" +import { DateRenderer } from "./controls/DateControl" // Ordered from lowest rank to highest rank. Higher rank renderers will be preferred over lower rank renderers. export const rendererRegistryEntries: JsonFormsRendererRegistryEntry[] = [ @@ -72,6 +74,10 @@ export const rendererRegistryEntries: JsonFormsRendererRegistryEntry[] = [ tester: rankWith(2, or(isNumberControl, isIntegerControl)), renderer: NumericRenderer, }, + { + tester: rankWith(3, isDateControl), + renderer: DateRenderer, + }, { tester: rankWith( 3, diff --git a/src/stories/controls/DateControl.stories.tsx b/src/stories/controls/DateControl.stories.tsx new file mode 100644 index 0000000..5d08a4f --- /dev/null +++ b/src/stories/controls/DateControl.stories.tsx @@ -0,0 +1,62 @@ +import { Meta, StoryObj } from "@storybook/react" +import { rendererRegistryEntries } from "../../renderer-registry-entries" +import { StorybookAntDJsonForm } from "../../common/StorybookAntDJsonForm" +import { + dateSchema, + dateUISchema, + dateUISchemaWithFormatOption, +} from "../../testSchemas/dateSchema" + +const meta: Meta = { + title: "Control/Date", + component: StorybookAntDJsonForm, + tags: ["autodocs"], + args: { + jsonSchema: dateSchema, + rendererRegistryEntries: [...rendererRegistryEntries], + }, + argTypes: { + rendererRegistryEntries: { table: { disable: true } }, + uiSchemaRegistryEntries: { table: { disable: true } }, + jsonSchema: { + control: "object", + }, + data: { table: { disable: true } }, + config: { control: "object" }, + onChange: { table: { disable: true, action: "on-change" } }, + }, +} + +export default meta +type Story = StoryObj + +export const RegularDate: Story = { + tags: ["autodocs"], + args: { + jsonSchema: dateSchema, + uiSchema: dateUISchema, + }, +} + +export const RequiredDate: Story = { + tags: ["autodocs"], + args: { + jsonSchema: { + ...dateSchema, + required: ["date"], + }, + uiSchema: dateUISchema, + }, +} + +export const DateWithOptionFormat: Story = { + tags: ["autodocs"], + args: { + data: { date: new Date("2021-01-01") }, + jsonSchema: { + ...dateSchema, + required: ["date"], + }, + uiSchema: dateUISchemaWithFormatOption, + }, +} diff --git a/src/testSchemas/dateSchema.ts b/src/testSchemas/dateSchema.ts new file mode 100644 index 0000000..9fc7a1d --- /dev/null +++ b/src/testSchemas/dateSchema.ts @@ -0,0 +1,51 @@ +import { JSONSchema } from "json-schema-to-ts" +import { UISchema } from "../ui-schema" +import { RuleEffect } from "@jsonforms/core" + +export const dateSchema = { + type: "object", + properties: { + date: { + type: "string", + title: "The Future is Now", + format: "date", + }, + }, +} satisfies JSONSchema + +export const dateUISchema = { + type: "VerticalLayout", + elements: [ + { + type: "Control", + scope: "#/properties/date", + }, + ], +} satisfies UISchema + +export const dateUISchemaWithFormatOption = { + type: "VerticalLayout", + elements: [ + { + type: "Control", + scope: "#/properties/date", + options: { + dateFormat: "YYYY MM DD", + }, + }, + ], +} satisfies UISchema + +export const dateUISchemaWithRule = { + type: "VerticalLayout", + elements: [ + { + type: "Control", + scope: "#/properties/date", + rule: { + effect: RuleEffect.HIDE, + condition: {}, + }, + }, + ], +} satisfies UISchema diff --git a/src/ui-schema.ts b/src/ui-schema.ts index 77f4976..87491f9 100644 --- a/src/ui-schema.ts +++ b/src/ui-schema.ts @@ -287,3 +287,7 @@ export type NumericControlOptions = { addonBefore?: InputNumberProps["addonBefore"] addonAfter?: InputNumberProps["addonAfter"] } + +export type DateControlOptions = { + dateFormat?: string +} diff --git a/test-global.ts b/test-global.ts new file mode 100644 index 0000000..a10760b --- /dev/null +++ b/test-global.ts @@ -0,0 +1,3 @@ +export const setup = () => { + process.env.TZ = "UTC" +} diff --git a/tsconfig.json b/tsconfig.json index feea382..5edacc8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,5 +23,5 @@ // TODO: move this to a tsconfig.test.json file so that we only load these types for compiling tests "types": ["@testing-library/jest-dom"] }, - "include": ["src", "test-setup.ts"] + "include": ["src", "test-setup.ts", "test-global.ts"] } diff --git a/vite.config.ts b/vite.config.ts index 9f10272..a1d52d2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ test: { environment: "jsdom", setupFiles: ["test-setup.ts"], + globalSetup: "test-global.ts", // globals: true // very happy about being able to turn globals off here! }, })