@terreno/ui 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ActionSheet.js +15 -27
- package/dist/ActionSheet.js.map +1 -1
- package/dist/MarkdownView.js +20 -7
- package/dist/MarkdownView.js.map +1 -1
- package/dist/useConsentHistory.d.ts +6 -1
- package/dist/useConsentHistory.js +2 -1
- package/dist/useConsentHistory.js.map +1 -1
- package/package.json +1 -1
- package/src/ActionSheet.test.tsx +554 -0
- package/src/ActionSheet.tsx +24 -37
- package/src/Banner.test.tsx +36 -1
- package/src/DataTable.test.tsx +176 -1
- package/src/DateTimeField.test.tsx +716 -2
- package/src/HeightActionSheet.test.tsx +1 -1
- package/src/HeightField.test.tsx +35 -0
- package/src/HeightFieldDesktop.test.tsx +19 -0
- package/src/MarkdownView.test.tsx +28 -0
- package/src/MarkdownView.tsx +69 -7
- package/src/MobileAddressAutoComplete.test.tsx +6 -2
- package/src/PickerSelect.test.tsx +243 -0
- package/src/SplitPage.test.tsx +299 -43
- package/src/TapToEdit.test.tsx +13 -0
- package/src/ToastNotifications.test.tsx +674 -0
- package/src/Tooltip.test.tsx +707 -1
- package/src/WebAddressAutocomplete.test.tsx +99 -0
- package/src/WebDropdownMenu.test.tsx +28 -2
- package/src/__snapshots__/Banner.test.tsx.snap +125 -0
- package/src/__snapshots__/DataTable.test.tsx.snap +366 -0
- package/src/__snapshots__/MarkdownView.test.tsx.snap +284 -74
- package/src/__snapshots__/SplitPage.test.tsx.snap +698 -46
- package/src/bunSetup.ts +0 -4
- package/src/login/LoginScreen.test.tsx +12 -0
- package/src/useConsentHistory.test.ts +20 -13
- package/src/useConsentHistory.ts +7 -2
|
@@ -1,20 +1,62 @@
|
|
|
1
1
|
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
afterAll,
|
|
4
|
+
afterEach,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
type mock as MockType,
|
|
10
|
+
mock,
|
|
11
|
+
} from "bun:test";
|
|
3
12
|
import {act, userEvent} from "@testing-library/react-native";
|
|
4
13
|
import {DateTime} from "luxon";
|
|
5
14
|
|
|
6
15
|
import {DateTimeField} from "./DateTimeField";
|
|
7
16
|
import {renderWithTheme, setupComponentTest, teardownComponentTest} from "./test-utils";
|
|
8
17
|
|
|
18
|
+
const setDesktop = () => {
|
|
19
|
+
mock.module("./MediaQuery", () => ({
|
|
20
|
+
isMobileDevice: () => false,
|
|
21
|
+
mediaQuery: () => "lg" as const,
|
|
22
|
+
mediaQueryLargerThan: () => true,
|
|
23
|
+
mediaQuerySmallerThan: () => false,
|
|
24
|
+
}));
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const setMobile = () => {
|
|
28
|
+
mock.module("./MediaQuery", () => ({
|
|
29
|
+
isMobileDevice: () => true,
|
|
30
|
+
mediaQuery: () => "xs" as const,
|
|
31
|
+
mediaQueryLargerThan: () => false,
|
|
32
|
+
mediaQuerySmallerThan: () => true,
|
|
33
|
+
}));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Restore MediaQuery to bunSetup defaults after all tests to prevent cross-file pollution.
|
|
37
|
+
// bunSetup mocks: isMobileDevice → false, mediaQueryLargerThan → false.
|
|
38
|
+
const restoreDefault = () => {
|
|
39
|
+
mock.module("./MediaQuery", () => ({
|
|
40
|
+
isMobileDevice: mock(() => false),
|
|
41
|
+
mediaQueryLargerThan: mock(() => false),
|
|
42
|
+
}));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
afterAll(() => {
|
|
46
|
+
restoreDefault();
|
|
47
|
+
});
|
|
48
|
+
|
|
9
49
|
describe("DateTimeField", () => {
|
|
10
|
-
let mockOnChange: ReturnType<
|
|
50
|
+
let mockOnChange: ReturnType<MockType>;
|
|
11
51
|
|
|
12
52
|
beforeEach(() => {
|
|
53
|
+
setDesktop();
|
|
13
54
|
const mocks = setupComponentTest();
|
|
14
55
|
mockOnChange = mocks.onChange;
|
|
15
56
|
});
|
|
16
57
|
|
|
17
58
|
afterEach(() => {
|
|
59
|
+
setDesktop();
|
|
18
60
|
teardownComponentTest();
|
|
19
61
|
});
|
|
20
62
|
|
|
@@ -391,4 +433,676 @@ describe("DateTimeField", () => {
|
|
|
391
433
|
expect(getByPlaceholderText("YYYY").props.value).toBe("2023");
|
|
392
434
|
});
|
|
393
435
|
});
|
|
436
|
+
|
|
437
|
+
describe("empty and invalid value handling", () => {
|
|
438
|
+
it("should clear all fields when value is empty", () => {
|
|
439
|
+
const {getByPlaceholderText, rerender} = renderWithTheme(
|
|
440
|
+
<DateTimeField onChange={mockOnChange} type="date" value="2023-05-15T00:00:00.000Z" />
|
|
441
|
+
);
|
|
442
|
+
expect(getByPlaceholderText("MM").props.value).toBe("05");
|
|
443
|
+
|
|
444
|
+
rerender(<DateTimeField onChange={mockOnChange} type="date" value="" />);
|
|
445
|
+
expect(getByPlaceholderText("MM").props.value).toBe("");
|
|
446
|
+
expect(getByPlaceholderText("DD").props.value).toBe("");
|
|
447
|
+
expect(getByPlaceholderText("YYYY").props.value).toBe("");
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("should clear all fields when value is undefined", () => {
|
|
451
|
+
const {getByPlaceholderText, rerender} = renderWithTheme(
|
|
452
|
+
<DateTimeField onChange={mockOnChange} type="datetime" value="2023-05-15T15:30:00.000Z" />
|
|
453
|
+
);
|
|
454
|
+
rerender(<DateTimeField onChange={mockOnChange} type="datetime" value={undefined} />);
|
|
455
|
+
expect(getByPlaceholderText("MM").props.value).toBe("");
|
|
456
|
+
expect(getByPlaceholderText("hh").props.value).toBe("");
|
|
457
|
+
expect(getByPlaceholderText("mm").props.value).toBe("");
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
describe("time type field validation", () => {
|
|
462
|
+
it("should validate hour fields for time type", async () => {
|
|
463
|
+
const user = userEvent.setup();
|
|
464
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
465
|
+
<DateTimeField
|
|
466
|
+
onChange={mockOnChange}
|
|
467
|
+
timezone="America/New_York"
|
|
468
|
+
type="time"
|
|
469
|
+
value="2023-05-15T15:30:00.000Z"
|
|
470
|
+
/>
|
|
471
|
+
);
|
|
472
|
+
const hourInput = getByPlaceholderText("hh");
|
|
473
|
+
await user.clear(hourInput);
|
|
474
|
+
await user.type(hourInput, "13");
|
|
475
|
+
await act(async () => {
|
|
476
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
477
|
+
});
|
|
478
|
+
expect(hourInput).toBeTruthy();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("should validate year field correctly", async () => {
|
|
482
|
+
const user = userEvent.setup();
|
|
483
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
484
|
+
<DateTimeField onChange={mockOnChange} type="date" value="2023-05-15T00:00:00.000Z" />
|
|
485
|
+
);
|
|
486
|
+
const yearInput = getByPlaceholderText("YYYY");
|
|
487
|
+
await user.clear(yearInput);
|
|
488
|
+
await user.type(yearInput, "1800");
|
|
489
|
+
await act(async () => {
|
|
490
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
491
|
+
});
|
|
492
|
+
expect(yearInput).toBeTruthy();
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("should validate day out of range", async () => {
|
|
496
|
+
const user = userEvent.setup();
|
|
497
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
498
|
+
<DateTimeField onChange={mockOnChange} type="date" value="2023-05-15T00:00:00.000Z" />
|
|
499
|
+
);
|
|
500
|
+
const dayInput = getByPlaceholderText("DD");
|
|
501
|
+
await user.clear(dayInput);
|
|
502
|
+
await user.type(dayInput, "32");
|
|
503
|
+
await act(async () => {
|
|
504
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
505
|
+
});
|
|
506
|
+
expect(dayInput).toBeTruthy();
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe("datetime type interactions", () => {
|
|
511
|
+
it("should render datetime with all segments on desktop", () => {
|
|
512
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
513
|
+
<DateTimeField
|
|
514
|
+
onChange={mockOnChange}
|
|
515
|
+
timezone="America/New_York"
|
|
516
|
+
type="datetime"
|
|
517
|
+
value="2023-05-15T15:30:00.000Z"
|
|
518
|
+
/>
|
|
519
|
+
);
|
|
520
|
+
expect(getByPlaceholderText("MM").props.value).toBe("05");
|
|
521
|
+
expect(getByPlaceholderText("DD").props.value).toBe("15");
|
|
522
|
+
expect(getByPlaceholderText("YYYY").props.value).toBe("2023");
|
|
523
|
+
expect(getByPlaceholderText("hh").props.value).toBe("11");
|
|
524
|
+
expect(getByPlaceholderText("mm").props.value).toBe("30");
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it("should handle hour change in datetime mode", async () => {
|
|
528
|
+
const user = userEvent.setup();
|
|
529
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
530
|
+
<DateTimeField
|
|
531
|
+
onChange={mockOnChange}
|
|
532
|
+
timezone="America/New_York"
|
|
533
|
+
type="datetime"
|
|
534
|
+
value="2023-05-15T15:30:00.000Z"
|
|
535
|
+
/>
|
|
536
|
+
);
|
|
537
|
+
const hourInput = getByPlaceholderText("hh");
|
|
538
|
+
await user.clear(hourInput);
|
|
539
|
+
await user.type(hourInput, "3");
|
|
540
|
+
await act(async () => {
|
|
541
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
542
|
+
});
|
|
543
|
+
expect(hourInput).toBeTruthy();
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it("should handle minute change in datetime mode", async () => {
|
|
547
|
+
const user = userEvent.setup();
|
|
548
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
549
|
+
<DateTimeField
|
|
550
|
+
onChange={mockOnChange}
|
|
551
|
+
timezone="America/New_York"
|
|
552
|
+
type="datetime"
|
|
553
|
+
value="2023-05-15T15:30:00.000Z"
|
|
554
|
+
/>
|
|
555
|
+
);
|
|
556
|
+
const minuteInput = getByPlaceholderText("mm");
|
|
557
|
+
await user.clear(minuteInput);
|
|
558
|
+
await user.type(minuteInput, "45");
|
|
559
|
+
await act(async () => {
|
|
560
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
561
|
+
});
|
|
562
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
describe("disabled state", () => {
|
|
567
|
+
it("should render disabled date field", () => {
|
|
568
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
569
|
+
<DateTimeField
|
|
570
|
+
disabled
|
|
571
|
+
onChange={mockOnChange}
|
|
572
|
+
type="date"
|
|
573
|
+
value="2023-05-15T00:00:00.000Z"
|
|
574
|
+
/>
|
|
575
|
+
);
|
|
576
|
+
expect(getByPlaceholderText("MM").props.readOnly).toBe(true);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("should not render action sheet when disabled", () => {
|
|
580
|
+
const {queryByAccessibilityHint} = renderWithTheme(
|
|
581
|
+
<DateTimeField
|
|
582
|
+
disabled
|
|
583
|
+
onChange={mockOnChange}
|
|
584
|
+
type="datetime"
|
|
585
|
+
value="2023-05-15T15:30:00.000Z"
|
|
586
|
+
/>
|
|
587
|
+
);
|
|
588
|
+
expect(queryByAccessibilityHint("Opens the calendar to select a date and time")).toBeNull();
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
describe("title, error, and helper text", () => {
|
|
593
|
+
it("should render title", () => {
|
|
594
|
+
const {getByText} = renderWithTheme(
|
|
595
|
+
<DateTimeField
|
|
596
|
+
onChange={mockOnChange}
|
|
597
|
+
title="Pick a date"
|
|
598
|
+
type="date"
|
|
599
|
+
value="2023-05-15T00:00:00.000Z"
|
|
600
|
+
/>
|
|
601
|
+
);
|
|
602
|
+
expect(getByText("Pick a date")).toBeTruthy();
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it("should render error text", () => {
|
|
606
|
+
const {getByText} = renderWithTheme(
|
|
607
|
+
<DateTimeField
|
|
608
|
+
errorText="Required field"
|
|
609
|
+
onChange={mockOnChange}
|
|
610
|
+
type="date"
|
|
611
|
+
value="2023-05-15T00:00:00.000Z"
|
|
612
|
+
/>
|
|
613
|
+
);
|
|
614
|
+
expect(getByText("Required field")).toBeTruthy();
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("should render helper text", () => {
|
|
618
|
+
const {getByText} = renderWithTheme(
|
|
619
|
+
<DateTimeField
|
|
620
|
+
helperText="Select a date"
|
|
621
|
+
onChange={mockOnChange}
|
|
622
|
+
type="date"
|
|
623
|
+
value="2023-05-15T00:00:00.000Z"
|
|
624
|
+
/>
|
|
625
|
+
);
|
|
626
|
+
expect(getByText("Select a date")).toBeTruthy();
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
describe("getFieldValue edge cases", () => {
|
|
631
|
+
it("should return empty string for unrecognized index in time mode", () => {
|
|
632
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
633
|
+
<DateTimeField
|
|
634
|
+
onChange={mockOnChange}
|
|
635
|
+
timezone="America/New_York"
|
|
636
|
+
type="time"
|
|
637
|
+
value="2023-05-15T15:30:00.000Z"
|
|
638
|
+
/>
|
|
639
|
+
);
|
|
640
|
+
expect(getByPlaceholderText("hh").props.value).toBe("11");
|
|
641
|
+
expect(getByPlaceholderText("mm").props.value).toBe("30");
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
describe("getISOFromFields edge cases", () => {
|
|
646
|
+
it("should return undefined when time fields are incomplete for time type", () => {
|
|
647
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
648
|
+
<DateTimeField onChange={mockOnChange} timezone="America/New_York" type="time" value="" />
|
|
649
|
+
);
|
|
650
|
+
expect(getByPlaceholderText("hh").props.value).toBe("");
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("should handle 12pm correctly in time type", async () => {
|
|
654
|
+
const user = userEvent.setup();
|
|
655
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
656
|
+
<DateTimeField
|
|
657
|
+
onChange={mockOnChange}
|
|
658
|
+
timezone="America/New_York"
|
|
659
|
+
type="time"
|
|
660
|
+
value="2023-05-15T12:00:00.000Z"
|
|
661
|
+
/>
|
|
662
|
+
);
|
|
663
|
+
const minuteInput = getByPlaceholderText("mm");
|
|
664
|
+
await user.clear(minuteInput);
|
|
665
|
+
await user.type(minuteInput, "15");
|
|
666
|
+
await act(async () => {
|
|
667
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
668
|
+
});
|
|
669
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("should handle 12am correctly in time type", () => {
|
|
673
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
674
|
+
<DateTimeField
|
|
675
|
+
onChange={mockOnChange}
|
|
676
|
+
timezone="America/New_York"
|
|
677
|
+
type="time"
|
|
678
|
+
value="2023-05-15T04:00:00.000Z"
|
|
679
|
+
/>
|
|
680
|
+
);
|
|
681
|
+
expect(getByPlaceholderText("hh").props.value).toBe("12");
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
describe("onTimezoneChange callback", () => {
|
|
686
|
+
it("should call onTimezoneChange when provided", () => {
|
|
687
|
+
const mockTzChange = mock(() => {});
|
|
688
|
+
renderWithTheme(
|
|
689
|
+
<DateTimeField
|
|
690
|
+
onChange={mockOnChange}
|
|
691
|
+
onTimezoneChange={mockTzChange}
|
|
692
|
+
timezone="America/New_York"
|
|
693
|
+
type="time"
|
|
694
|
+
value="2023-05-15T15:30:00.000Z"
|
|
695
|
+
/>
|
|
696
|
+
);
|
|
697
|
+
expect(mockTzChange).toBeDefined();
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
describe("mobile time display", () => {
|
|
702
|
+
it("should render MobileTimeDisplay on mobile with time type", () => {
|
|
703
|
+
setMobile();
|
|
704
|
+
const {getByAccessibilityHint} = renderWithTheme(
|
|
705
|
+
<DateTimeField
|
|
706
|
+
onChange={mockOnChange}
|
|
707
|
+
timezone="America/New_York"
|
|
708
|
+
type="time"
|
|
709
|
+
value="2023-05-15T15:30:00.000Z"
|
|
710
|
+
/>
|
|
711
|
+
);
|
|
712
|
+
expect(getByAccessibilityHint("Tap to select a time")).toBeTruthy();
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it("should render disabled MobileTimeDisplay", () => {
|
|
716
|
+
setMobile();
|
|
717
|
+
const {getByAccessibilityHint} = renderWithTheme(
|
|
718
|
+
<DateTimeField
|
|
719
|
+
disabled
|
|
720
|
+
onChange={mockOnChange}
|
|
721
|
+
timezone="America/New_York"
|
|
722
|
+
type="time"
|
|
723
|
+
value="2023-05-15T15:30:00.000Z"
|
|
724
|
+
/>
|
|
725
|
+
);
|
|
726
|
+
expect(getByAccessibilityHint("Tap to select a time")).toBeTruthy();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it("should render MobileTimeDisplay placeholder when no value", () => {
|
|
730
|
+
setMobile();
|
|
731
|
+
const {getByAccessibilityHint} = renderWithTheme(
|
|
732
|
+
<DateTimeField onChange={mockOnChange} timezone="America/New_York" type="time" value="" />
|
|
733
|
+
);
|
|
734
|
+
expect(getByAccessibilityHint("Tap to select a time")).toBeTruthy();
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("should render mobile datetime with time display", () => {
|
|
738
|
+
setMobile();
|
|
739
|
+
const {getByAccessibilityHint} = renderWithTheme(
|
|
740
|
+
<DateTimeField
|
|
741
|
+
onChange={mockOnChange}
|
|
742
|
+
timezone="America/New_York"
|
|
743
|
+
type="datetime"
|
|
744
|
+
value="2023-05-15T15:30:00.000Z"
|
|
745
|
+
/>
|
|
746
|
+
);
|
|
747
|
+
expect(getByAccessibilityHint("Opens date and time picker")).toBeTruthy();
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
describe("onActionSheetChange", () => {
|
|
752
|
+
it("should handle action sheet date selection", async () => {
|
|
753
|
+
setDesktop();
|
|
754
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
755
|
+
<DateTimeField
|
|
756
|
+
onChange={mockOnChange}
|
|
757
|
+
timezone="America/New_York"
|
|
758
|
+
type="date"
|
|
759
|
+
value="2023-05-15T00:00:00.000Z"
|
|
760
|
+
/>
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
const actionSheet = UNSAFE_root.findAll(
|
|
764
|
+
(n: any) => n.props?.onChange && n.props?.visible !== undefined
|
|
765
|
+
);
|
|
766
|
+
expect(actionSheet.length).toBeGreaterThan(0);
|
|
767
|
+
await act(async () => {
|
|
768
|
+
actionSheet[0].props.onChange("2023-06-20T00:00:00.000Z");
|
|
769
|
+
});
|
|
770
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("should handle action sheet clear (empty string)", async () => {
|
|
774
|
+
setDesktop();
|
|
775
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
776
|
+
<DateTimeField
|
|
777
|
+
onChange={mockOnChange}
|
|
778
|
+
timezone="America/New_York"
|
|
779
|
+
type="date"
|
|
780
|
+
value="2023-05-15T00:00:00.000Z"
|
|
781
|
+
/>
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
const actionSheet = UNSAFE_root.findAll(
|
|
785
|
+
(n: any) => n.props?.onChange && n.props?.visible !== undefined
|
|
786
|
+
);
|
|
787
|
+
expect(actionSheet.length).toBeGreaterThan(0);
|
|
788
|
+
await act(async () => {
|
|
789
|
+
actionSheet[0].props.onChange("");
|
|
790
|
+
});
|
|
791
|
+
expect(mockOnChange).toHaveBeenCalledWith("");
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it("should handle action sheet time selection", async () => {
|
|
795
|
+
setDesktop();
|
|
796
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
797
|
+
<DateTimeField
|
|
798
|
+
onChange={mockOnChange}
|
|
799
|
+
timezone="America/New_York"
|
|
800
|
+
type="time"
|
|
801
|
+
value="2023-05-15T15:30:00.000Z"
|
|
802
|
+
/>
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const actionSheet = UNSAFE_root.findAll(
|
|
806
|
+
(n: any) => n.props?.onChange && n.props?.visible !== undefined
|
|
807
|
+
);
|
|
808
|
+
expect(actionSheet.length).toBeGreaterThan(0);
|
|
809
|
+
await act(async () => {
|
|
810
|
+
actionSheet[0].props.onChange("2023-05-15T18:45:00.000Z");
|
|
811
|
+
});
|
|
812
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it("should handle action sheet datetime selection", async () => {
|
|
816
|
+
setDesktop();
|
|
817
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
818
|
+
<DateTimeField
|
|
819
|
+
onChange={mockOnChange}
|
|
820
|
+
timezone="America/New_York"
|
|
821
|
+
type="datetime"
|
|
822
|
+
value="2023-05-15T15:30:00.000Z"
|
|
823
|
+
/>
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
const actionSheet = UNSAFE_root.findAll(
|
|
827
|
+
(n: any) => n.props?.onChange && n.props?.visible !== undefined
|
|
828
|
+
);
|
|
829
|
+
expect(actionSheet.length).toBeGreaterThan(0);
|
|
830
|
+
await act(async () => {
|
|
831
|
+
actionSheet[0].props.onChange("2023-06-20T10:00:00.000Z");
|
|
832
|
+
});
|
|
833
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
describe("handleAmPmChange", () => {
|
|
838
|
+
it("should toggle AM to PM and emit new value", async () => {
|
|
839
|
+
setDesktop();
|
|
840
|
+
// 15:30 UTC = 11:30 AM in New York => toggling to PM should change the time
|
|
841
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
842
|
+
<DateTimeField
|
|
843
|
+
onChange={mockOnChange}
|
|
844
|
+
timezone="America/New_York"
|
|
845
|
+
type="time"
|
|
846
|
+
value="2023-05-15T15:30:00.000Z"
|
|
847
|
+
/>
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
const amPmSelects = UNSAFE_root.findAll((n: any) => n.props?.onAmPmChange);
|
|
851
|
+
expect(amPmSelects.length).toBeGreaterThan(0);
|
|
852
|
+
await act(async () => {
|
|
853
|
+
amPmSelects[0].props.onAmPmChange("pm");
|
|
854
|
+
});
|
|
855
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
describe("handleTimezoneChange", () => {
|
|
860
|
+
it("should call onTimezoneChange callback and emit new value", async () => {
|
|
861
|
+
setDesktop();
|
|
862
|
+
const mockTzChange = mock(() => {});
|
|
863
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
864
|
+
<DateTimeField
|
|
865
|
+
onChange={mockOnChange}
|
|
866
|
+
onTimezoneChange={mockTzChange}
|
|
867
|
+
timezone="America/New_York"
|
|
868
|
+
type="time"
|
|
869
|
+
value="2023-05-15T15:30:00.000Z"
|
|
870
|
+
/>
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
const tzPickers = UNSAFE_root.findAll((n: any) => n.props?.onTimezoneChange);
|
|
874
|
+
expect(tzPickers.length).toBeGreaterThan(0);
|
|
875
|
+
await act(async () => {
|
|
876
|
+
tzPickers[0].props.onTimezoneChange("America/Chicago");
|
|
877
|
+
});
|
|
878
|
+
expect(mockTzChange).toHaveBeenCalledWith("America/Chicago");
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it("should use local timezone state when onTimezoneChange not provided", async () => {
|
|
882
|
+
setDesktop();
|
|
883
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
884
|
+
<DateTimeField
|
|
885
|
+
onChange={mockOnChange}
|
|
886
|
+
timezone="America/New_York"
|
|
887
|
+
type="time"
|
|
888
|
+
value="2023-05-15T15:30:00.000Z"
|
|
889
|
+
/>
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
const tzPickers = UNSAFE_root.findAll((n: any) => n.props?.onTimezoneChange);
|
|
893
|
+
expect(tzPickers.length).toBeGreaterThan(0);
|
|
894
|
+
await act(async () => {
|
|
895
|
+
tzPickers[0].props.onTimezoneChange("America/Chicago");
|
|
896
|
+
});
|
|
897
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
898
|
+
});
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
describe("minute validation", () => {
|
|
902
|
+
it("should validate invalid minute in time mode", async () => {
|
|
903
|
+
const user = userEvent.setup();
|
|
904
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
905
|
+
<DateTimeField
|
|
906
|
+
onChange={mockOnChange}
|
|
907
|
+
timezone="America/New_York"
|
|
908
|
+
type="time"
|
|
909
|
+
value="2023-05-15T15:30:00.000Z"
|
|
910
|
+
/>
|
|
911
|
+
);
|
|
912
|
+
const minuteInput = getByPlaceholderText("mm");
|
|
913
|
+
await user.clear(minuteInput);
|
|
914
|
+
await user.type(minuteInput, "99");
|
|
915
|
+
await act(async () => {
|
|
916
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
917
|
+
});
|
|
918
|
+
expect(minuteInput).toBeTruthy();
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
describe("getISOFromFields datetime am/pm", () => {
|
|
923
|
+
it("should handle pm in datetime mode", async () => {
|
|
924
|
+
const user = userEvent.setup();
|
|
925
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
926
|
+
<DateTimeField
|
|
927
|
+
onChange={mockOnChange}
|
|
928
|
+
timezone="America/New_York"
|
|
929
|
+
type="datetime"
|
|
930
|
+
value="2023-05-15T20:30:00.000Z"
|
|
931
|
+
/>
|
|
932
|
+
);
|
|
933
|
+
const minuteInput = getByPlaceholderText("mm");
|
|
934
|
+
await user.clear(minuteInput);
|
|
935
|
+
await user.type(minuteInput, "45");
|
|
936
|
+
await act(async () => {
|
|
937
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
938
|
+
});
|
|
939
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
it("should handle 12am in datetime mode", async () => {
|
|
943
|
+
const user = userEvent.setup();
|
|
944
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
945
|
+
<DateTimeField
|
|
946
|
+
onChange={mockOnChange}
|
|
947
|
+
timezone="America/New_York"
|
|
948
|
+
type="datetime"
|
|
949
|
+
value="2023-05-15T04:30:00.000Z"
|
|
950
|
+
/>
|
|
951
|
+
);
|
|
952
|
+
const minuteInput = getByPlaceholderText("mm");
|
|
953
|
+
await user.clear(minuteInput);
|
|
954
|
+
await user.type(minuteInput, "15");
|
|
955
|
+
await act(async () => {
|
|
956
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
957
|
+
});
|
|
958
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
describe("onBlur edge cases", () => {
|
|
963
|
+
it("should handle onBlur with AM/PM override in time mode", async () => {
|
|
964
|
+
setDesktop();
|
|
965
|
+
const user = userEvent.setup();
|
|
966
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
967
|
+
<DateTimeField
|
|
968
|
+
onChange={mockOnChange}
|
|
969
|
+
timezone="America/New_York"
|
|
970
|
+
type="time"
|
|
971
|
+
value="2023-05-15T15:30:00.000Z"
|
|
972
|
+
/>
|
|
973
|
+
);
|
|
974
|
+
const hourInput = getByPlaceholderText("hh");
|
|
975
|
+
await user.clear(hourInput);
|
|
976
|
+
await user.type(hourInput, "5");
|
|
977
|
+
await act(async () => {
|
|
978
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
979
|
+
});
|
|
980
|
+
expect(hourInput).toBeTruthy();
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
describe("onDismiss and onLayout", () => {
|
|
985
|
+
it("should handle DateTimeActionSheet onDismiss", async () => {
|
|
986
|
+
setDesktop();
|
|
987
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
988
|
+
<DateTimeField
|
|
989
|
+
onChange={mockOnChange}
|
|
990
|
+
timezone="America/New_York"
|
|
991
|
+
type="date"
|
|
992
|
+
value="2023-05-15T00:00:00.000Z"
|
|
993
|
+
/>
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
const actionSheet = UNSAFE_root.findAll(
|
|
997
|
+
(n: any) => n.props?.onDismiss && n.props?.visible !== undefined
|
|
998
|
+
);
|
|
999
|
+
expect(actionSheet.length).toBeGreaterThan(0);
|
|
1000
|
+
await act(async () => {
|
|
1001
|
+
actionSheet[0].props.onDismiss();
|
|
1002
|
+
});
|
|
1003
|
+
expect(UNSAFE_root).toBeTruthy();
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it("should handle Pressable onLayout", async () => {
|
|
1007
|
+
setDesktop();
|
|
1008
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
1009
|
+
<DateTimeField
|
|
1010
|
+
onChange={mockOnChange}
|
|
1011
|
+
timezone="America/New_York"
|
|
1012
|
+
type="date"
|
|
1013
|
+
value="2023-05-15T00:00:00.000Z"
|
|
1014
|
+
/>
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
const pressables = UNSAFE_root.findAll((n: any) => n.props?.onLayout);
|
|
1018
|
+
expect(pressables.length).toBeGreaterThan(0);
|
|
1019
|
+
await act(async () => {
|
|
1020
|
+
pressables[0].props.onLayout({nativeEvent: {layout: {width: 500}}});
|
|
1021
|
+
});
|
|
1022
|
+
expect(UNSAFE_root).toBeTruthy();
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
describe("SelectField AM/PM onChange inline callback", () => {
|
|
1027
|
+
it("should trigger SelectField onChange to call onAmPmChange", async () => {
|
|
1028
|
+
setDesktop();
|
|
1029
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
1030
|
+
<DateTimeField
|
|
1031
|
+
onChange={mockOnChange}
|
|
1032
|
+
timezone="America/New_York"
|
|
1033
|
+
type="time"
|
|
1034
|
+
value="2023-05-15T15:30:00.000Z"
|
|
1035
|
+
/>
|
|
1036
|
+
);
|
|
1037
|
+
const selects = UNSAFE_root.findAll((n: any) => {
|
|
1038
|
+
const opts = n.props?.options;
|
|
1039
|
+
return (
|
|
1040
|
+
Array.isArray(opts) &&
|
|
1041
|
+
opts.some((o: {value?: string}) => o?.value === "am" || o?.value === "pm")
|
|
1042
|
+
);
|
|
1043
|
+
});
|
|
1044
|
+
expect(selects.length).toBeGreaterThan(0);
|
|
1045
|
+
expect(selects[0].props.onChange).toBeDefined();
|
|
1046
|
+
await act(async () => {
|
|
1047
|
+
selects[0].props.onChange("pm");
|
|
1048
|
+
});
|
|
1049
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
describe("inputRef onRef callback", () => {
|
|
1054
|
+
it("should set ref when segment renders", () => {
|
|
1055
|
+
setDesktop();
|
|
1056
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1057
|
+
<DateTimeField
|
|
1058
|
+
onChange={mockOnChange}
|
|
1059
|
+
timezone="America/New_York"
|
|
1060
|
+
type="time"
|
|
1061
|
+
value="2023-05-15T15:30:00.000Z"
|
|
1062
|
+
/>
|
|
1063
|
+
);
|
|
1064
|
+
expect(getByPlaceholderText("hh")).toBeTruthy();
|
|
1065
|
+
expect(getByPlaceholderText("mm")).toBeTruthy();
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
describe("datetime type date-only change", () => {
|
|
1070
|
+
it("should handle changing date in datetime mode without changing time", async () => {
|
|
1071
|
+
const user = userEvent.setup();
|
|
1072
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1073
|
+
<DateTimeField
|
|
1074
|
+
onChange={mockOnChange}
|
|
1075
|
+
timezone="America/New_York"
|
|
1076
|
+
type="datetime"
|
|
1077
|
+
value="2023-05-15T15:30:00.000Z"
|
|
1078
|
+
/>
|
|
1079
|
+
);
|
|
1080
|
+
const dayInput = getByPlaceholderText("DD");
|
|
1081
|
+
await user.clear(dayInput);
|
|
1082
|
+
await user.type(dayInput, "20");
|
|
1083
|
+
await act(async () => {
|
|
1084
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1085
|
+
});
|
|
1086
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
it("should handle changing year in datetime mode", async () => {
|
|
1090
|
+
const user = userEvent.setup();
|
|
1091
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1092
|
+
<DateTimeField
|
|
1093
|
+
onChange={mockOnChange}
|
|
1094
|
+
timezone="America/New_York"
|
|
1095
|
+
type="datetime"
|
|
1096
|
+
value="2023-05-15T15:30:00.000Z"
|
|
1097
|
+
/>
|
|
1098
|
+
);
|
|
1099
|
+
const yearInput = getByPlaceholderText("YYYY");
|
|
1100
|
+
await user.clear(yearInput);
|
|
1101
|
+
await user.type(yearInput, "2024");
|
|
1102
|
+
await act(async () => {
|
|
1103
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1104
|
+
});
|
|
1105
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
394
1108
|
});
|