Skip to content

Commit af5d7d8

Browse files
authored
Merge pull request #744 from gadget-inc/feature/shadcn-datetimepicker
Shadcn-AutoDateTimePickers
2 parents a082313 + 0ab5bdb commit af5d7d8

22 files changed

+998
-320
lines changed
Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
import { format } from "date-fns";
2+
import { utcToZonedTime } from "date-fns-tz";
3+
import React, { useState } from "react";
4+
import { elements } from "../../../../spec/auto/shadcn-defaults/index.js";
5+
import { apiTriggerOnly } from "../../../../spec/auto/support/Triggers.js";
6+
import { PolarisAutoForm } from "../../../../src/auto/polaris/PolarisAutoForm.js";
7+
import { PolarisAutoDateTimePicker } from "../../../../src/auto/polaris/inputs/PolarisAutoDateTimePicker.js";
8+
import { makeAutoForm } from "../../../../src/auto/shadcn/ShadcnAutoForm.js";
9+
import { makeShadcnAutoDateTimePicker } from "../../../../src/auto/shadcn/inputs/ShadcnAutoDateTimePicker.js";
10+
import { api } from "../../../support/api.js";
11+
import { describeForEachAutoAdapter } from "../../../support/auto.js";
12+
import { SUITE_NAMES } from "../../../support/constants.js";
13+
14+
const baseDate = new Date("2021-03-05T11:23:00.000Z");
15+
const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
16+
const dateInLocalTZ = utcToZonedTime(baseDate, localTz);
17+
18+
const ShadcnAutoDateTimePicker = makeShadcnAutoDateTimePicker(elements);
19+
const ShadcnAutoForm = makeAutoForm(elements);
20+
21+
const TestComponentWithCustomOnChange = ({ suiteName }: { suiteName: string }) => {
22+
const [date, setDate] = useState(baseDate);
23+
24+
if (suiteName === SUITE_NAMES.POLARIS) {
25+
return (
26+
<PolarisAutoForm action={api.widget.create}>
27+
<PolarisAutoDateTimePicker id="test" value={date} onChange={setDate} field="startsAt" />
28+
</PolarisAutoForm>
29+
);
30+
}
31+
32+
if (suiteName === SUITE_NAMES.SHADCN) {
33+
return (
34+
<ShadcnAutoForm action={api.widget.create}>
35+
<ShadcnAutoDateTimePicker id="test" value={date} onChange={setDate} field="startsAt" />
36+
</ShadcnAutoForm>
37+
);
38+
}
39+
40+
throw new Error("Invalid suite name");
41+
};
42+
43+
const AutoDateTimePicker = (props: {
44+
suiteName?: string;
45+
field: string;
46+
label?: string;
47+
id?: string;
48+
onChange?: (date: Date) => void;
49+
value?: Date;
50+
includeTime?: boolean;
51+
}) => {
52+
if (props.suiteName === SUITE_NAMES.POLARIS) {
53+
return <PolarisAutoDateTimePicker {...props} />;
54+
}
55+
56+
if (props.suiteName === SUITE_NAMES.SHADCN) {
57+
return <ShadcnAutoDateTimePicker {...props} />;
58+
}
59+
60+
throw new Error("Invalid suite name");
61+
};
62+
63+
describeForEachAutoAdapter("AutoFormDateTimePicker", ({ name, adapter: { AutoForm }, wrapper }) => {
64+
beforeEach(() => {
65+
cy.viewport("macbook-13");
66+
});
67+
68+
const mockMetadataResponse = (defaultRecord?: Record<string, any> | undefined) => {
69+
cy.mockModelActionMetadata(api, {
70+
modelName: "Widget",
71+
modelApiIdentifier: "widget",
72+
action: { apiIdentifier: "create", operatesWithRecordIdentity: false },
73+
triggers: apiTriggerOnly,
74+
inputFields: [
75+
{
76+
name: "Widget",
77+
apiIdentifier: "widget",
78+
fieldType: "Object",
79+
requiredArgumentForInput: false,
80+
configuration: {
81+
__typename: "GadgetObjectFieldConfig",
82+
fieldType: "Object",
83+
validations: [],
84+
name: null,
85+
fields: [
86+
{
87+
name: "Starts at",
88+
apiIdentifier: "startsAt",
89+
fieldType: "DateTime",
90+
requiredArgumentForInput: false,
91+
sortable: true,
92+
filterable: true,
93+
configuration: {
94+
__typename: "GadgetDateTimeConfig",
95+
fieldType: "DateTime",
96+
validations: [],
97+
},
98+
},
99+
],
100+
},
101+
__typename: "GadgetObjectField",
102+
},
103+
],
104+
defaultRecord: defaultRecord ?? {},
105+
});
106+
};
107+
108+
describe("date only", () => {
109+
it("can show with a blank current value", () => {
110+
mockMetadataResponse();
111+
const onChangeSpy = cy.spy().as("onChangeSpy");
112+
cy.mountWithWrapper(
113+
<AutoForm action={api.widget.create}>
114+
<AutoDateTimePicker suiteName={name} field="startsAt" id="test" onChange={onChangeSpy} />
115+
</AutoForm>,
116+
wrapper
117+
);
118+
cy.wait("@ModelCreateActionMetadata");
119+
120+
if (name === SUITE_NAMES.POLARIS) {
121+
cy.get("#test-date").should("have.value", "");
122+
cy.get("#test-date").click();
123+
cy.get(".Polaris-DatePicker__Title").contains(`${new Date().getFullYear()}`);
124+
}
125+
126+
if (name === SUITE_NAMES.SHADCN) {
127+
cy.get("#test-date").contains("YYYY-MM-DD");
128+
cy.get("#test-date").click();
129+
cy.get(".rdp-month_caption").contains(`${new Date().getFullYear()}`);
130+
}
131+
});
132+
133+
it("can show the current value", () => {
134+
mockMetadataResponse();
135+
const onChangeSpy = cy.spy().as("onChangeSpy");
136+
cy.mountWithWrapper(
137+
<AutoForm action={api.widget.create}>
138+
<AutoDateTimePicker suiteName={name} id="test" includeTime value={baseDate} onChange={onChangeSpy} field="startsAt" />
139+
</AutoForm>,
140+
wrapper
141+
);
142+
cy.wait("@ModelCreateActionMetadata");
143+
144+
if (name === SUITE_NAMES.POLARIS) {
145+
cy.get("#test-date").should("have.value", format(dateInLocalTZ, "yyyy-MM-dd"));
146+
}
147+
148+
if (name === SUITE_NAMES.SHADCN) {
149+
cy.get("#test-date").contains(format(dateInLocalTZ, "yyyy-MM-dd"));
150+
}
151+
});
152+
153+
it("can change the date", async () => {
154+
mockMetadataResponse();
155+
const onChangeSpy = cy.spy().as("onChangeSpy");
156+
cy.mountWithWrapper(
157+
<AutoForm action={api.widget.create}>
158+
<AutoDateTimePicker suiteName={name} id="test" value={baseDate} onChange={onChangeSpy} field="startsAt" />
159+
</AutoForm>,
160+
wrapper
161+
);
162+
cy.wait("@ModelCreateActionMetadata");
163+
cy.get("#test-date").click();
164+
cy.get(`[aria-label='Thursday March 4 2021']`).click();
165+
// eslint-disable-next-line jest/valid-expect-in-promise
166+
cy.get("@onChangeSpy")
167+
.should("have.been.called")
168+
.then(() => {
169+
expect(onChangeSpy.getCalls()[0].args[0].toISOString()).equal(new Date("2021-03-04T11:23:10.000Z").toISOString());
170+
});
171+
});
172+
173+
it("can change the date across a DST boundary", async () => {
174+
mockMetadataResponse();
175+
const onChangeSpy = cy.spy().as("onChangeSpy");
176+
cy.mountWithWrapper(
177+
<AutoForm action={api.widget.create}>
178+
<AutoDateTimePicker suiteName={name} id="test" value={baseDate} onChange={onChangeSpy} field="startsAt" />
179+
</AutoForm>,
180+
wrapper
181+
);
182+
cy.wait("@ModelCreateActionMetadata");
183+
cy.get("#test-date").click();
184+
cy.get(`[aria-label='Wednesday March 17 2021']`).click();
185+
// eslint-disable-next-line jest/valid-expect-in-promise
186+
cy.get("@onChangeSpy")
187+
.should("have.been.called")
188+
.then(() => {
189+
expect(onChangeSpy.getCalls()[0].args[0].toISOString()).equal(new Date("2021-03-17T10:23:10.000Z").toISOString());
190+
});
191+
});
192+
193+
describe("date and time", () => {
194+
it("can show with a blank current value", () => {
195+
mockMetadataResponse();
196+
const onChangeSpy = cy.spy().as("onChangeSpy");
197+
cy.mountWithWrapper(
198+
<AutoForm action={api.widget.create}>
199+
<AutoDateTimePicker suiteName={name} id="test" includeTime onChange={onChangeSpy} field="startsAt" />
200+
</AutoForm>,
201+
wrapper
202+
);
203+
cy.wait("@ModelCreateActionMetadata");
204+
// Test is flaky without waiting for the DOM to load
205+
cy.wait(100);
206+
207+
if (name === SUITE_NAMES.POLARIS) {
208+
cy.get("#test-date").should("have.value", "");
209+
cy.get("#test-time").should("have.value", "");
210+
}
211+
212+
if (name === SUITE_NAMES.SHADCN) {
213+
cy.get("#test-date").contains("YYYY-MM-DD hh:mm aa");
214+
cy.get("#test-date").click();
215+
cy.get("#test-time").should("have.value", "");
216+
}
217+
});
218+
219+
it(
220+
"can show the default field value from the metadata",
221+
// eslint-disable-next-line
222+
// @ts-ignore - This test passes in isolation but is flakey when run with the rest of the file
223+
{ retries: 2 },
224+
() => {
225+
mockMetadataResponse({ startsAt: "2024-07-10T04:00:00.000Z" });
226+
cy.mountWithWrapper(
227+
<AutoForm action={api.widget.create}>
228+
<AutoDateTimePicker suiteName={name} id="test" includeTime field="startsAt" />
229+
</AutoForm>,
230+
wrapper
231+
);
232+
cy.wait("@ModelCreateActionMetadata");
233+
234+
if (name === SUITE_NAMES.POLARIS) {
235+
cy.get("#test-date").should("have.value", "2024-07-10");
236+
cy.get("#test-time").should("have.value", "4:00 AM");
237+
}
238+
239+
if (name === SUITE_NAMES.SHADCN) {
240+
cy.get("#test-date").contains("2024-07-10");
241+
cy.get("#test-date").click();
242+
cy.get("#test-time").should("have.value", "4:00 AM");
243+
}
244+
}
245+
);
246+
247+
it("can show the default field value from the defaultValues prop", () => {
248+
mockMetadataResponse();
249+
cy.mountWithWrapper(
250+
<AutoForm action={api.widget.create} defaultValues={{ widget: { startsAt: "2024-07-10T04:00:00.000Z" } }}>
251+
<AutoDateTimePicker suiteName={name} id="test" includeTime field="startsAt" />
252+
</AutoForm>,
253+
wrapper
254+
);
255+
cy.wait("@ModelCreateActionMetadata");
256+
257+
if (name === SUITE_NAMES.POLARIS) {
258+
cy.get("#test-date").should("have.value", "2024-07-10");
259+
cy.get("#test-time").should("have.value", "4:00 AM");
260+
}
261+
262+
if (name === SUITE_NAMES.SHADCN) {
263+
cy.get("#test-date").contains("2024-07-10");
264+
cy.get("#test-date").click();
265+
cy.get("#test-time").should("have.value", "4:00 AM");
266+
}
267+
});
268+
269+
it("can show the current value", () => {
270+
mockMetadataResponse();
271+
const onChangeSpy = cy.spy().as("onChangeSpy");
272+
cy.mountWithWrapper(
273+
<AutoForm action={api.widget.create}>
274+
<AutoDateTimePicker suiteName={name} id="test" includeTime value={baseDate} onChange={onChangeSpy} field="startsAt" />
275+
</AutoForm>,
276+
wrapper
277+
);
278+
cy.wait("@ModelCreateActionMetadata");
279+
280+
if (name === SUITE_NAMES.POLARIS) {
281+
cy.get("#test-date").should("have.value", format(dateInLocalTZ, "yyyy-MM-dd"));
282+
cy.get("#test-time").should("have.value", format(dateInLocalTZ, "K:m aa"));
283+
}
284+
285+
if (name === SUITE_NAMES.SHADCN) {
286+
cy.get("#test-date").contains(format(dateInLocalTZ, "yyyy-MM-dd"));
287+
cy.get("#test-date").click();
288+
cy.get("#test-time").should("have.value", format(dateInLocalTZ, "K:m aa"));
289+
}
290+
});
291+
292+
it("can change the date", () => {
293+
mockMetadataResponse();
294+
const onChangeSpy = cy.spy().as("onChangeSpy");
295+
cy.mountWithWrapper(
296+
<AutoForm action={api.widget.create}>
297+
<AutoDateTimePicker suiteName={name} id="test" includeTime value={baseDate} onChange={onChangeSpy} field="startsAt" />
298+
</AutoForm>,
299+
wrapper
300+
);
301+
cy.wait("@ModelCreateActionMetadata");
302+
cy.get("#test-date").click();
303+
304+
if (name === SUITE_NAMES.POLARIS) {
305+
cy.get(`[aria-label='Thursday March 4 2021']`).click();
306+
}
307+
308+
if (name === SUITE_NAMES.SHADCN) {
309+
cy.get(`[aria-label='Thursday, March 4th, 2021']`).click();
310+
}
311+
// eslint-disable-next-line jest/valid-expect-in-promise
312+
cy.get("@onChangeSpy")
313+
.should("have.been.called")
314+
.then(() => {
315+
expect(onChangeSpy.getCalls()[0].args[0].toISOString()).equal(new Date("2021-03-04T11:23:00.000Z").toISOString());
316+
});
317+
});
318+
319+
it("can enter an invalid time and show an error", () => {
320+
mockMetadataResponse();
321+
const onChangeSpy = cy.spy().as("onChangeSpy");
322+
cy.mountWithWrapper(
323+
<AutoForm action={api.widget.create}>
324+
<AutoDateTimePicker suiteName={name} id="test" includeTime value={baseDate} onChange={onChangeSpy} field="startsAt" />
325+
</AutoForm>,
326+
wrapper
327+
);
328+
cy.wait("@ModelCreateActionMetadata");
329+
330+
if (name === SUITE_NAMES.POLARIS) {
331+
cy.get("#test-time").click().clear().type("foo");
332+
cy.get("body").click();
333+
334+
cy.contains("Invalid time format");
335+
cy.get("#test-time").click().clear().type("12:21 AM");
336+
cy.contains("Invalid time format").should("not.exist");
337+
}
338+
339+
if (name === SUITE_NAMES.SHADCN) {
340+
cy.get("#test-date").click();
341+
cy.get("#test-time").click().clear().type("foo");
342+
cy.contains("Please use format: 12:00 PM");
343+
cy.get("#test-time").click().clear().type("12:21 AM");
344+
cy.contains("Please use format: 12:00 PM").should("not.exist");
345+
}
346+
});
347+
348+
it("can show the selected date", () => {
349+
mockMetadataResponse();
350+
cy.mountWithWrapper(<TestComponentWithCustomOnChange suiteName={name} />, wrapper);
351+
cy.wait("@ModelCreateActionMetadata");
352+
cy.get("#test-date").click();
353+
354+
if (name === SUITE_NAMES.POLARIS) {
355+
cy.get(`[aria-label='Thursday March 4 2021']`).click();
356+
cy.get("#test-date").click();
357+
cy.get(`[aria-label='Thursday March 4 2021']`).should("have.attr", "aria-pressed", "true");
358+
}
359+
360+
cy.get("#test-date").click();
361+
362+
if (name === SUITE_NAMES.SHADCN) {
363+
cy.get(`[aria-label='Thursday, March 4th, 2021']`).click();
364+
cy.get(`[aria-label='Thursday, March 4th, 2021, selected']`).should("exist");
365+
}
366+
});
367+
});
368+
});
369+
});

0 commit comments

Comments
 (0)