@dhasdk/simple-ui 1.0.7 → 1.0.8
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/.babelrc +12 -0
- package/.storybook/main.ts +35 -0
- package/.storybook/preview.ts +4 -0
- package/BAKpostcss.config.jsBAK +15 -0
- package/BAKtailwind.config.mjsBAK +99 -0
- package/README.md +464 -16
- package/coverage/storybook/coverage-storybook.json +32411 -0
- package/coverage/storybook/lcov-report/Accordion.tsx.html +805 -0
- package/coverage/storybook/lcov-report/Badge.tsx.html +346 -0
- package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +742 -0
- package/coverage/storybook/lcov-report/Button.tsx.html +448 -0
- package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +403 -0
- package/coverage/storybook/lcov-report/Card.tsx.html +292 -0
- package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +253 -0
- package/coverage/storybook/lcov-report/CheckBox.tsx.html +1555 -0
- package/coverage/storybook/lcov-report/DatePicker.tsx.html +826 -0
- package/coverage/storybook/lcov-report/Input.tsx.html +1012 -0
- package/coverage/storybook/lcov-report/List.tsx.html +364 -0
- package/coverage/storybook/lcov-report/Modal.tsx.html +745 -0
- package/coverage/storybook/lcov-report/Pill.tsx.html +358 -0
- package/coverage/storybook/lcov-report/Search.tsx.html +997 -0
- package/coverage/storybook/lcov-report/SearchContent.tsx.html +235 -0
- package/coverage/storybook/lcov-report/SectionHeader.tsx.html +358 -0
- package/coverage/storybook/lcov-report/Select.tsx.html +1012 -0
- package/coverage/storybook/lcov-report/Shield.tsx.html +802 -0
- package/coverage/storybook/lcov-report/SideBarNav.tsx.html +490 -0
- package/coverage/storybook/lcov-report/Skeleton.tsx.html +394 -0
- package/coverage/storybook/lcov-report/Slider.tsx.html +385 -0
- package/coverage/storybook/lcov-report/Status.tsx.html +322 -0
- package/coverage/storybook/lcov-report/Tabs.tsx.html +610 -0
- package/coverage/storybook/lcov-report/Toggle.tsx.html +373 -0
- package/coverage/storybook/lcov-report/Tooltip.tsx.html +496 -0
- package/coverage/storybook/lcov-report/base.css +224 -0
- package/coverage/storybook/lcov-report/block-navigation.js +87 -0
- package/coverage/storybook/lcov-report/favicon.png +0 -0
- package/coverage/storybook/lcov-report/index.html +476 -0
- package/coverage/storybook/lcov-report/prettify.css +1 -0
- package/coverage/storybook/lcov-report/prettify.js +2 -0
- package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/storybook/lcov-report/sorter.js +196 -0
- package/coverage/storybook/lcov.info +2312 -0
- package/dist/README.md +1815 -0
- package/eslint.config.mjs +13 -0
- package/package.json +6 -7
- package/project.json +11 -0
- package/src/assets/img/Frame.svg +5 -0
- package/src/assets/img/backArrowRight.svg +10 -0
- package/src/assets/img/bc-separator.png +0 -0
- package/src/assets/img/calendar.png +0 -0
- package/src/assets/img/calendar.svg +4 -0
- package/src/assets/img/check.svg +5 -0
- package/src/assets/img/check_box.svg +10 -0
- package/src/assets/img/check_box_empty.svg +10 -0
- package/src/assets/img/check_box_fill.svg +10 -0
- package/src/assets/img/check_box_fill_empty.svg +10 -0
- package/src/assets/img/chevron-down-white.svg +2 -0
- package/src/assets/img/chevron-down.svg +2 -0
- package/src/assets/img/chevron-left.svg +1 -0
- package/src/assets/img/chevron-right-light.svg +4 -0
- package/src/assets/img/chevron-right.svg +3 -0
- package/src/assets/img/chevron-up-white.svg +1 -0
- package/src/assets/img/chevron-up.svg +1 -0
- package/src/assets/img/clock.svg +6 -0
- package/src/assets/img/close.svg +1 -0
- package/src/assets/img/close2.svg +6 -0
- package/src/assets/img/closeModal.svg +10 -0
- package/src/assets/img/close_icon_dark.svg +10 -0
- package/src/assets/img/close_small.svg +3 -0
- package/src/assets/img/emergency_home.svg +10 -0
- package/src/assets/img/first-aid-kit.svg +7 -0
- package/src/assets/img/heartbeat.svg +4 -0
- package/src/assets/img/home-gray.svg +3 -0
- package/src/assets/img/home.svg +3 -0
- package/src/assets/img/hospital.jpg +0 -0
- package/src/assets/img/indeterminate_check_box.svg +10 -0
- package/src/assets/img/indeterminate_check_box_fill.svg +10 -0
- package/src/assets/img/info_24_ 1d4ed8.svg +3 -0
- package/src/assets/img/info_24_ 2c6441.svg +3 -0
- package/src/assets/img/marker_check_by_default.svg +10 -0
- package/src/assets/img/marker_check_by_default_fill.svg +10 -0
- package/src/assets/img/minus-accordion.svg +5 -0
- package/src/assets/img/minus.svg +3 -0
- package/src/assets/img/open.svg +1 -0
- package/src/assets/img/pill-white.svg +7 -0
- package/src/assets/img/pill.svg +5 -0
- package/src/assets/img/plus-accordion.svg +5 -0
- package/src/assets/img/plus.svg +4 -0
- package/src/assets/img/prescription.svg +6 -0
- package/src/assets/img/search.svg +10 -0
- package/src/assets/img/search_icon_light.svg +10 -0
- package/src/assets/img/separator.svg +3 -0
- package/src/assets/img/stethoscope-white.svg +8 -0
- package/src/assets/img/stethoscope.svg +8 -0
- package/src/assets/img/thumb_up.svg +10 -0
- package/src/assets/img/vector.svg +3 -0
- package/src/assets/img/warning-badge-disabled.svg +11 -0
- package/src/assets/img/warning-badge-green.svg +11 -0
- package/src/assets/img/warning-badge-red.svg +11 -0
- package/src/assets/img/warning-badge-yellow.svg +11 -0
- package/src/assets/img/warning.svg +10 -0
- package/src/global.d.ts +13 -0
- package/{index.d.ts → src/index.ts} +13 -5
- package/src/lib/Accordian--Accordian.stories.tsx +312 -0
- package/src/lib/Accordion.spec.tsx +384 -0
- package/src/lib/Accordion.tsx +240 -0
- package/src/lib/AppointmentPicker.spec.tsx +138 -0
- package/src/lib/AppointmentPicker.tsx +97 -0
- package/src/lib/Badge--Badge.stories.tsx +60 -0
- package/src/lib/Badge.spec.tsx +70 -0
- package/src/lib/Badge.tsx +87 -0
- package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +114 -0
- package/src/lib/Breadcrumbs.spec.tsx +218 -0
- package/src/lib/Breadcrumbs.tsx +219 -0
- package/src/lib/Button--Button.stories.tsx +220 -0
- package/src/lib/Button.spec.tsx +241 -0
- package/src/lib/Button.tsx +121 -0
- package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +129 -0
- package/src/lib/ButtonGroup.spec.tsx +89 -0
- package/src/lib/ButtonGroup.tsx +107 -0
- package/src/lib/Card--Card.stories.tsx +113 -0
- package/src/lib/Card.spec.tsx +112 -0
- package/src/lib/Card.tsx +69 -0
- package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +169 -0
- package/src/lib/CharacterCounter.spec.tsx +123 -0
- package/src/lib/CharacterCounter.tsx +56 -0
- package/src/lib/CheckBox--CheckBox.stories.tsx +107 -0
- package/src/lib/CheckBox.spec.tsx +412 -0
- package/src/lib/CheckBox.tsx +491 -0
- package/src/lib/DatePicker--DatePicker.stories.tsx +228 -0
- package/src/lib/DatePicker.spec.tsx +424 -0
- package/src/lib/DatePicker.tsx +247 -0
- package/src/lib/Input--Input.stories.tsx +449 -0
- package/src/lib/Input.spec.tsx +281 -0
- package/src/lib/Input.tsx +309 -0
- package/src/lib/List--List.stories.tsx +157 -0
- package/src/lib/List.spec.tsx +211 -0
- package/src/lib/List.tsx +93 -0
- package/src/lib/Modal--Modal.stories.tsx +454 -0
- package/src/lib/Modal.spec.tsx +202 -0
- package/src/lib/Modal.tsx +220 -0
- package/src/lib/Pill--Pill.stories.tsx +98 -0
- package/src/lib/Pill.spec.tsx +103 -0
- package/src/lib/Pill.tsx +91 -0
- package/src/lib/ProgressBar.spec.tsx +106 -0
- package/src/lib/ProgressBar.tsx +112 -0
- package/src/lib/RadioGroup.spec.tsx +84 -0
- package/src/lib/RadioGroup.tsx +74 -0
- package/src/lib/RadioIcon.tsx +13 -0
- package/src/lib/Search--Search.stories.tsx +67 -0
- package/src/lib/Search.spec.tsx +182 -0
- package/src/lib/Search.tsx +304 -0
- package/src/lib/SearchContent.tsx +51 -0
- package/src/lib/SectionHeader--SectionHeader.stories.tsx +98 -0
- package/src/lib/SectionHeader.spec.tsx +60 -0
- package/src/lib/SectionHeader.tsx +91 -0
- package/src/lib/Select--Select.stories.tsx +387 -0
- package/src/lib/Select.spec.tsx +493 -0
- package/src/lib/Select.tsx +311 -0
- package/src/lib/Shield--Shield.stories.tsx +196 -0
- package/src/lib/Shield.spec.tsx +275 -0
- package/src/lib/Shield.tsx +239 -0
- package/src/lib/SideBarNav--SideBarNav.stories.tsx +136 -0
- package/src/lib/SideBarNav.spec.tsx +178 -0
- package/src/lib/SideBarNav.tsx +135 -0
- package/src/lib/Skeleton--Skeleton.stories.tsx +77 -0
- package/src/lib/Skeleton.module.css +16 -0
- package/src/lib/Skeleton.spec.tsx +83 -0
- package/src/lib/Skeleton.tsx +103 -0
- package/src/lib/SkipLink.spec.tsx +76 -0
- package/src/lib/SkipLink.tsx +48 -0
- package/src/lib/Slider--Slider.stories.tsx +108 -0
- package/src/lib/Slider.module.css +109 -0
- package/src/lib/Slider.spec.tsx +67 -0
- package/src/lib/Slider.tsx +101 -0
- package/src/lib/Status--Status.stories.tsx +93 -0
- package/src/lib/Status.spec.tsx +118 -0
- package/src/lib/Status.tsx +79 -0
- package/src/lib/Tabs--Tabs.stories.tsx +294 -0
- package/src/lib/Tabs.spec.tsx +249 -0
- package/src/lib/Tabs.tsx +188 -0
- package/src/lib/Tester.spec.tsx +17 -0
- package/src/lib/Toggle--Toggle.stories.tsx +162 -0
- package/src/lib/Toggle.spec.tsx +122 -0
- package/src/lib/Toggle.tsx +96 -0
- package/src/lib/Tooltip--Tooltip.stories.tsx +315 -0
- package/src/lib/Tooltip.spec.tsx +307 -0
- package/src/lib/Tooltip.tsx +137 -0
- package/src/lib/bak-simple-ui.stories.tsx-bak +24 -0
- package/src/styles.css +190 -0
- package/tsconfig.json +25 -0
- package/tsconfig.lib.json +42 -0
- package/tsconfig.spec.json +29 -0
- package/tsconfig.storybook.json +36 -0
- package/vite.config.mts +87 -0
- package/vitest.setup.ts +12 -0
- package/index.css +0 -1
- package/index.js +0 -35
- package/index.mjs +0 -4981
- package/lib/Accordion.d.ts +0 -36
- package/lib/AppointmentPicker.d.ts +0 -21
- package/lib/Badge.d.ts +0 -11
- package/lib/Breadcrumbs.d.ts +0 -13
- package/lib/Button.d.ts +0 -15
- package/lib/ButtonGroup.d.ts +0 -8
- package/lib/Card.d.ts +0 -11
- package/lib/CharacterCounter.d.ts +0 -11
- package/lib/CheckBox.d.ts +0 -30
- package/lib/DatePicker.d.ts +0 -7
- package/lib/Input.d.ts +0 -16
- package/lib/List.d.ts +0 -22
- package/lib/Modal.d.ts +0 -18
- package/lib/Pill.d.ts +0 -13
- package/lib/ProgressBar.d.ts +0 -19
- package/lib/RadioGroup.d.ts +0 -15
- package/lib/Search.d.ts +0 -26
- package/lib/SearchContent.d.ts +0 -6
- package/lib/SectionHeader.d.ts +0 -18
- package/lib/Select.d.ts +0 -19
- package/lib/Shield.d.ts +0 -12
- package/lib/SideBarNav.d.ts +0 -21
- package/lib/Skeleton.d.ts +0 -15
- package/lib/SkipLink.d.ts +0 -22
- package/lib/Slider.d.ts +0 -14
- package/lib/Status.d.ts +0 -10
- package/lib/Tabs.d.ts +0 -23
- package/lib/Toggle.d.ts +0 -11
- package/lib/Tooltip.d.ts +0 -14
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// DatePicker.stories.tsx
|
|
2
|
+
import { Meta, StoryObj, StoryContext } from '@storybook/react';
|
|
3
|
+
import { DatePicker, DatePickerProps } from './DatePicker';
|
|
4
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
5
|
+
import { fireEvent } from 'storybook/test';
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
|
|
8
|
+
// Meta object
|
|
9
|
+
export default {
|
|
10
|
+
title: 'Components/DatePicker',
|
|
11
|
+
component: DatePicker,
|
|
12
|
+
args: {
|
|
13
|
+
id: '42',
|
|
14
|
+
label: 'Select Date',
|
|
15
|
+
value: '',
|
|
16
|
+
},
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'centered',
|
|
19
|
+
backgrounds: { default: 'light' },
|
|
20
|
+
},
|
|
21
|
+
} as Meta<DatePickerProps>;
|
|
22
|
+
|
|
23
|
+
// Default story
|
|
24
|
+
export const Default: StoryObj<DatePickerProps> = {
|
|
25
|
+
args: {
|
|
26
|
+
// You can override the default args here
|
|
27
|
+
label: 'Select Birthday',
|
|
28
|
+
// value: '', // no default date
|
|
29
|
+
onChange: (date) => {
|
|
30
|
+
console.log('Date changed:', date);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Default story
|
|
36
|
+
export const NoColor: StoryObj<DatePickerProps> = {
|
|
37
|
+
args: {
|
|
38
|
+
// You can override the default args here
|
|
39
|
+
label: 'Select Birthday',
|
|
40
|
+
// color: '',
|
|
41
|
+
// value: '', // no default date
|
|
42
|
+
onChange: (date) => {
|
|
43
|
+
console.log('Date changed:', date);
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Story with a pre-filled date
|
|
49
|
+
export const PreFilledDate: StoryObj<DatePickerProps> = {
|
|
50
|
+
args: {
|
|
51
|
+
label: 'Pre-filled Date',
|
|
52
|
+
value: '01-15-2025', // for example
|
|
53
|
+
onChange: (date) => {
|
|
54
|
+
console.log('Date changed:', date);
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
// Story with no default date and forced invalid state
|
|
61
|
+
export const InvalidDate: StoryObj<DatePickerProps> = {
|
|
62
|
+
args: {
|
|
63
|
+
label: 'Invalid Date Handling',
|
|
64
|
+
value: '13-40-9999', // obviously invalid
|
|
65
|
+
onChange: (date) => {
|
|
66
|
+
console.log('Date changed:', date);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Story demonstrating using today's date
|
|
72
|
+
export const TodayDate = {
|
|
73
|
+
args: {
|
|
74
|
+
label: 'Today\'s Date',
|
|
75
|
+
value: new Date().toLocaleDateString('en-US', {
|
|
76
|
+
month: '2-digit', day: '2-digit', year: 'numeric'
|
|
77
|
+
}).replace(/\//g, '-'),
|
|
78
|
+
// This converts something like 2/5/2025 -> "02-05-2025"
|
|
79
|
+
onChange: (date: string) => {
|
|
80
|
+
console.log('Date changed:', date);
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
play: async ({ canvasElement }: StoryContext ) => {
|
|
84
|
+
|
|
85
|
+
const currentMonthName = dayjs().format('MMMM'); // full month name
|
|
86
|
+
const currentYear = dayjs().format('YYYY'); // 4-digit year
|
|
87
|
+
// const testMonthYear = currentMonthName + ' ' + currentYear;
|
|
88
|
+
const testMonthYear = 'February 2025';
|
|
89
|
+
console.log('current month + year: \'' + testMonthYear + '\'');
|
|
90
|
+
|
|
91
|
+
const canvas = within(canvasElement);
|
|
92
|
+
|
|
93
|
+
// select input box
|
|
94
|
+
const inputBox = canvas.getByLabelText('Today\'s Date'); // Select via text of input label
|
|
95
|
+
expect (inputBox).toBeVisible();
|
|
96
|
+
|
|
97
|
+
// clear box
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 250)); // required to clear text effectively
|
|
99
|
+
await userEvent.clear(inputBox);
|
|
100
|
+
|
|
101
|
+
// enter '01-2' into box
|
|
102
|
+
await userEvent.type(inputBox, '01-');
|
|
103
|
+
await new Promise((resolve) => setTimeout(resolve, 250)); // required to clear text effectively
|
|
104
|
+
|
|
105
|
+
// confirm this partial input creates an error
|
|
106
|
+
const errorParagraph = canvas.getByText("Please enter a valid date 'MM-DD-YYYY'");
|
|
107
|
+
expect(errorParagraph).toBeInTheDocument();
|
|
108
|
+
expect(errorParagraph).toBeVisible();
|
|
109
|
+
|
|
110
|
+
// enter remainder of an incorrect date
|
|
111
|
+
await userEvent.type(inputBox, '33-2025');
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 250)); // required to clear text effectively
|
|
113
|
+
expect(errorParagraph).toBeVisible();
|
|
114
|
+
|
|
115
|
+
// clear input, type VALID date
|
|
116
|
+
await userEvent.clear(inputBox);
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
118
|
+
await userEvent.type(inputBox, '02-01-2025');
|
|
119
|
+
|
|
120
|
+
// Confirm no error box visible
|
|
121
|
+
expect(errorParagraph).not.toBeVisible();
|
|
122
|
+
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
124
|
+
|
|
125
|
+
// Open Calendar via Button
|
|
126
|
+
const button = canvas.getByLabelText('Open calendar');
|
|
127
|
+
await button.click();
|
|
128
|
+
|
|
129
|
+
// Test that the correct Month + Year are displayed in the Calendar
|
|
130
|
+
const monthYearDiv = canvas.getByText('February 2025');
|
|
131
|
+
expect(monthYearDiv).toHaveTextContent('February 2025');
|
|
132
|
+
|
|
133
|
+
// Click the next button two times, see that it advances
|
|
134
|
+
const nextMonthButton = canvas.getByLabelText('Next month');
|
|
135
|
+
await nextMonthButton.click();
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
137
|
+
await nextMonthButton.click();
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
139
|
+
expect (monthYearDiv).not.toHaveTextContent(testMonthYear);
|
|
140
|
+
|
|
141
|
+
// Click the previous button two times, see that it retreats
|
|
142
|
+
const prevMonthButton = canvas.getByLabelText('Previous month');
|
|
143
|
+
await prevMonthButton.click();
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
145
|
+
await prevMonthButton.click();
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
147
|
+
|
|
148
|
+
// Test again that we are at the original location
|
|
149
|
+
expect (monthYearDiv).toHaveTextContent(testMonthYear);
|
|
150
|
+
|
|
151
|
+
// Click outside of Calendar
|
|
152
|
+
await userEvent.click(canvasElement);
|
|
153
|
+
|
|
154
|
+
// verify Calendar is no longer visible
|
|
155
|
+
expect (monthYearDiv).not.toBeVisible();
|
|
156
|
+
|
|
157
|
+
// re-open calendar
|
|
158
|
+
await button.click();
|
|
159
|
+
|
|
160
|
+
// find the 5th of the month, and click on that
|
|
161
|
+
const fifthOfMonth = canvas.getByText('5');
|
|
162
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
163
|
+
|
|
164
|
+
fireEvent.keyDown(fifthOfMonth, {key: 'Enter', code: 'Enter', charCode: 13});
|
|
165
|
+
|
|
166
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
167
|
+
|
|
168
|
+
// verify we DO NOT have an input error
|
|
169
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
170
|
+
expect(errorParagraph).not.toBeVisible();
|
|
171
|
+
|
|
172
|
+
// select input box, clear it, then type out 01-24-2025
|
|
173
|
+
// const inputBox = canvas.getByLabelText('Today\'s Date'); // Select via text of input label
|
|
174
|
+
expect (inputBox).toBeVisible();
|
|
175
|
+
|
|
176
|
+
// clear box
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, 250)); // required to clear text effectively
|
|
178
|
+
await userEvent.clear(inputBox);
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
// Story demonstrating using today's date
|
|
185
|
+
export const ClickDate = {
|
|
186
|
+
args: {
|
|
187
|
+
label: 'Today\'s Date',
|
|
188
|
+
value: new Date().toLocaleDateString('en-US', {
|
|
189
|
+
month: '2-digit', day: '2-digit', year: 'numeric'
|
|
190
|
+
}).replace(/\//g, '-'),
|
|
191
|
+
// This converts something like 2/5/2025 -> "02-05-2025"
|
|
192
|
+
onChange: (date: string) => {
|
|
193
|
+
console.log('Date changed:', date);
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
play: async ({ canvasElement }: StoryContext ) => {
|
|
197
|
+
|
|
198
|
+
const currentMonthName = dayjs().format('MMMM'); // full month name
|
|
199
|
+
const currentYear = dayjs().format('YYYY'); // 4-digit year
|
|
200
|
+
const testMonthYear = currentMonthName + ' ' + currentYear;
|
|
201
|
+
console.log('current month + year: \'' + testMonthYear + '\'');
|
|
202
|
+
|
|
203
|
+
const canvas = within(canvasElement);
|
|
204
|
+
|
|
205
|
+
// select input box
|
|
206
|
+
const inputBox = canvas.getByLabelText('Today\'s Date') as HTMLInputElement; // Select via text of input label
|
|
207
|
+
expect (inputBox).toBeVisible();
|
|
208
|
+
|
|
209
|
+
// clear box
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, 250)); // required to clear text effectively
|
|
211
|
+
await userEvent.clear(inputBox);
|
|
212
|
+
|
|
213
|
+
// Open Calendar via Button
|
|
214
|
+
const button = canvas.getByLabelText('Open calendar');
|
|
215
|
+
await button.click();
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
// find the 5th of the month, and click on that
|
|
219
|
+
const fifthOfMonth = canvas.getByText('5');
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
221
|
+
|
|
222
|
+
// click on / select the 5th of the Month
|
|
223
|
+
fifthOfMonth.click()
|
|
224
|
+
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, 500)); // required to clear text effectively
|
|
226
|
+
await expect(inputBox.value).toContain('-05-');
|
|
227
|
+
}
|
|
228
|
+
};
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
// DatePicker.test.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
6
|
+
import { axe } from "vitest-axe";
|
|
7
|
+
import dayjs from "dayjs";
|
|
8
|
+
import { DatePicker } from "./DatePicker";
|
|
9
|
+
|
|
10
|
+
// Make sure to clean up any mocks between tests.
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/*describe("DatePicker - AddStylesSvg", () => {
|
|
16
|
+
it("returns the original SVG string when no fill attribute is present", async () => {
|
|
17
|
+
// Arrange: create an SVG string without a fill attribute.
|
|
18
|
+
const svgWithoutFill = '<svg width="100" height="100"></svg>';
|
|
19
|
+
const testColor = "purple";
|
|
20
|
+
|
|
21
|
+
// Mock the global fetch to return our SVG string.
|
|
22
|
+
vi.spyOn(global, "fetch").mockResolvedValue({
|
|
23
|
+
// The fetch API returns a Response-like object.
|
|
24
|
+
text: async () => svgWithoutFill,
|
|
25
|
+
} as Response);
|
|
26
|
+
|
|
27
|
+
// Render the DatePicker.
|
|
28
|
+
render(
|
|
29
|
+
<DatePicker
|
|
30
|
+
id="test-datepicker"
|
|
31
|
+
label="Test Date"
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
33
|
+
onChange={() => {}}
|
|
34
|
+
color={testColor}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Wait for the image to appear (the fetch & useEffect run asynchronously).
|
|
39
|
+
const img = await screen.findByAltText("calendar icon");
|
|
40
|
+
|
|
41
|
+
// The AddStylesSvg function encodes the returned SVG string.
|
|
42
|
+
const expectedDataUrl = `data:image/svg+xml,${encodeURIComponent(svgWithoutFill)}`;
|
|
43
|
+
|
|
44
|
+
// Assert: the <img> element's src should be built using the original (unmodified) SVG string.
|
|
45
|
+
expect(img.getAttribute("src")).toEqual(expectedDataUrl);
|
|
46
|
+
});
|
|
47
|
+
});*/
|
|
48
|
+
|
|
49
|
+
describe("DatePicker Component", () => {
|
|
50
|
+
let onChangeMock: (date: string) => void;
|
|
51
|
+
|
|
52
|
+
it("sets focusedDate to null and shows an error when an invalid date is passed", async () => {
|
|
53
|
+
// Pass an invalid date string. This will force:
|
|
54
|
+
// dayjs(value, "MM-DD-YYYY", true).isValid() === false
|
|
55
|
+
render(
|
|
56
|
+
<DatePicker
|
|
57
|
+
id="test-datepicker"
|
|
58
|
+
label="Test Date"
|
|
59
|
+
value="99-99-9999"
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
61
|
+
onChange={() => {}}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// The invalid date should cause an error message to be rendered
|
|
66
|
+
expect(
|
|
67
|
+
screen.getByText("Please enter a valid date 'MM-DD-YYYY'")
|
|
68
|
+
).toBeInTheDocument();
|
|
69
|
+
|
|
70
|
+
// Open the calendar so we can check the header (which displays the month/year)
|
|
71
|
+
const calendarButton = screen.getByRole("button", { name: /open calendar/i });
|
|
72
|
+
userEvent.click(calendarButton);
|
|
73
|
+
|
|
74
|
+
// Wait for the dialog to be rendered
|
|
75
|
+
const dialog = await screen.findByRole("dialog");
|
|
76
|
+
|
|
77
|
+
// The header is defined as:
|
|
78
|
+
// <div className="text-lg font-semibold">
|
|
79
|
+
// {focusedDate?.format("MMMM YYYY")}
|
|
80
|
+
// </div>
|
|
81
|
+
// Since focusedDate should be null for an invalid date, this header will render empty.
|
|
82
|
+
const header = dialog.querySelector("div.text-lg.font-semibold");
|
|
83
|
+
expect(header?.textContent).toBe(""); // Empty header indicates focusedDate is null.
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Before each test, create a mock onChange function and stub the fetch call for the calendar icon.
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
onChangeMock = vi.fn();
|
|
89
|
+
global.fetch = vi.fn(() =>
|
|
90
|
+
Promise.resolve({
|
|
91
|
+
text: () =>
|
|
92
|
+
Promise.resolve('<svg fill="#000" stroke="#000"></svg>'),
|
|
93
|
+
})
|
|
94
|
+
) as unknown as typeof fetch;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(() => {
|
|
98
|
+
vi.resetAllMocks();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("renders with the provided label and initial value", async () => {
|
|
102
|
+
render(
|
|
103
|
+
<DatePicker
|
|
104
|
+
id="date-picker"
|
|
105
|
+
label="Select Date"
|
|
106
|
+
value="10-27-2023"
|
|
107
|
+
onChange={onChangeMock}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// The label should be in the document.
|
|
112
|
+
expect(screen.getByLabelText("Select Date")).toBeInTheDocument();
|
|
113
|
+
|
|
114
|
+
// The input should show the initial value.
|
|
115
|
+
const input = screen.getByRole("textbox");
|
|
116
|
+
await waitFor(() => expect(input).toHaveValue("10-27-2023"));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("opens and closes the calendar dialog when clicking the calendar button and outside", async () => {
|
|
120
|
+
render(
|
|
121
|
+
<DatePicker
|
|
122
|
+
id="date-picker"
|
|
123
|
+
label="Select Date"
|
|
124
|
+
value="10-27-2023"
|
|
125
|
+
onChange={onChangeMock}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const calendarButton = screen.getByRole("button", {
|
|
130
|
+
name: /open calendar/i,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// The dialog should not be visible initially.
|
|
134
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
135
|
+
|
|
136
|
+
// Click the calendar button to open the dialog.
|
|
137
|
+
userEvent.click(calendarButton);
|
|
138
|
+
expect(await screen.findByRole("dialog")).toBeInTheDocument();
|
|
139
|
+
|
|
140
|
+
// Clicking outside (simulate a mousedown on the document body) should close the dialog.
|
|
141
|
+
fireEvent.mouseDown(document.body);
|
|
142
|
+
await waitFor(() =>
|
|
143
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument()
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("calls onChange with the selected date when a day is clicked", async () => {
|
|
148
|
+
// Use an initial valid date so that the calendar shows the correct month.
|
|
149
|
+
render(
|
|
150
|
+
<DatePicker
|
|
151
|
+
id="date-picker"
|
|
152
|
+
label="Select Date"
|
|
153
|
+
value="10-27-2023"
|
|
154
|
+
onChange={onChangeMock}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Open the calendar dialog.
|
|
159
|
+
const calendarButton = screen.getByRole("button", {
|
|
160
|
+
name: /open calendar/i,
|
|
161
|
+
});
|
|
162
|
+
userEvent.click(calendarButton);
|
|
163
|
+
expect(await screen.findByRole("dialog")).toBeInTheDocument();
|
|
164
|
+
|
|
165
|
+
// Locate a day button by its aria-label. For example, select "15".
|
|
166
|
+
// (The day buttons use aria-label set to the full date string.)
|
|
167
|
+
const dayButton = screen.getByText("15");
|
|
168
|
+
userEvent.click(dayButton);
|
|
169
|
+
|
|
170
|
+
// The dialog should now be closed and the input value updated.
|
|
171
|
+
await waitFor(() => {
|
|
172
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
173
|
+
expect(screen.getByRole("textbox")).toHaveValue("10-15-2023");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("updates the input when a valid date is typed", async () => {
|
|
178
|
+
render(
|
|
179
|
+
<DatePicker
|
|
180
|
+
id="date-picker"
|
|
181
|
+
label="Select Date"
|
|
182
|
+
value="10-27-2023"
|
|
183
|
+
onChange={onChangeMock}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const input = screen.getByRole("textbox");
|
|
188
|
+
|
|
189
|
+
// Clear the input and type a new valid date.
|
|
190
|
+
userEvent.clear(input);
|
|
191
|
+
userEvent.type(input, "12-25-2023");
|
|
192
|
+
|
|
193
|
+
// The input value should update.
|
|
194
|
+
await waitFor(() =>
|
|
195
|
+
expect(input).toHaveValue("12-25-2023")
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// onChange should eventually be called with the new valid date.
|
|
199
|
+
expect(onChangeMock).toHaveBeenLastCalledWith("12-25-2023");
|
|
200
|
+
|
|
201
|
+
// There should be no error message about invalid date format.
|
|
202
|
+
expect(
|
|
203
|
+
screen.queryByText(/please enter a valid date 'MM-DD-YYYY'/i)
|
|
204
|
+
).not.toBeInTheDocument();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("displays an error message for an invalid date input", async () => {
|
|
208
|
+
render(
|
|
209
|
+
<DatePicker
|
|
210
|
+
id="date-picker"
|
|
211
|
+
label="Select Date"
|
|
212
|
+
value="10-27-2023"
|
|
213
|
+
onChange={onChangeMock}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const input = screen.getByRole("textbox");
|
|
218
|
+
|
|
219
|
+
// Clear the input and type an invalid date.
|
|
220
|
+
// For example, "13-40-2023" matches the regex but is not a valid date.
|
|
221
|
+
userEvent.clear(input);
|
|
222
|
+
userEvent.type(input, "13-40-2023");
|
|
223
|
+
|
|
224
|
+
// An error message should appear.
|
|
225
|
+
expect(
|
|
226
|
+
await screen.findByText(/please enter a valid date 'MM-DD-YYYY'/i)
|
|
227
|
+
).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("allows keyboard selection of a date using the Enter key", async () => {
|
|
231
|
+
render(
|
|
232
|
+
<DatePicker
|
|
233
|
+
id="date-picker"
|
|
234
|
+
label="Select Date"
|
|
235
|
+
value="10-27-2023"
|
|
236
|
+
onChange={onChangeMock}
|
|
237
|
+
/>
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Open the calendar dialog.
|
|
241
|
+
const calendarButton = screen.getByRole("button", {
|
|
242
|
+
name: /open calendar/i,
|
|
243
|
+
});
|
|
244
|
+
userEvent.click(calendarButton);
|
|
245
|
+
expect(await screen.findByRole("dialog")).toBeInTheDocument();
|
|
246
|
+
|
|
247
|
+
// Focus on a day button (for example, "October 20, 2023").
|
|
248
|
+
const dayButton = screen.getByLabelText("October 20, 2023");
|
|
249
|
+
dayButton.focus();
|
|
250
|
+
expect(dayButton).toHaveFocus();
|
|
251
|
+
|
|
252
|
+
// Simulate pressing the Enter key.
|
|
253
|
+
fireEvent.keyDown(dayButton, { key: "Enter", code: "Enter" });
|
|
254
|
+
await waitFor(() =>
|
|
255
|
+
expect(onChangeMock).toHaveBeenCalledWith("10-20-2023")
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("navigates to previous and next months", async () => {
|
|
260
|
+
render(
|
|
261
|
+
<DatePicker
|
|
262
|
+
id="date-picker"
|
|
263
|
+
label="Select Date"
|
|
264
|
+
value="10-27-2023"
|
|
265
|
+
onChange={onChangeMock}
|
|
266
|
+
/>
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Open the calendar dialog.
|
|
270
|
+
const calendarButton = screen.getByRole("button", {
|
|
271
|
+
name: /open calendar/i,
|
|
272
|
+
});
|
|
273
|
+
userEvent.click(calendarButton);
|
|
274
|
+
expect(await screen.findByRole("dialog")).toBeInTheDocument();
|
|
275
|
+
|
|
276
|
+
// Initially, the month/year label should be "October 2023".
|
|
277
|
+
expect(screen.getByText("October 2023")).toBeInTheDocument();
|
|
278
|
+
|
|
279
|
+
// Click the "previous month" button.
|
|
280
|
+
const prevButton = screen.getByRole("button", {
|
|
281
|
+
name: /previous month/i,
|
|
282
|
+
});
|
|
283
|
+
userEvent.click(prevButton);
|
|
284
|
+
await waitFor(() =>
|
|
285
|
+
expect(screen.getByText("September 2023")).toBeInTheDocument()
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Click the "next month" button twice (to go from September back to November).
|
|
289
|
+
const nextButton = screen.getByRole("button", {
|
|
290
|
+
name: /next month/i,
|
|
291
|
+
});
|
|
292
|
+
userEvent.click(nextButton);
|
|
293
|
+
await waitFor(() =>
|
|
294
|
+
expect(screen.getByText("October 2023")).toBeInTheDocument()
|
|
295
|
+
);
|
|
296
|
+
userEvent.click(nextButton);
|
|
297
|
+
await waitFor(() =>
|
|
298
|
+
expect(screen.getByText("November 2023")).toBeInTheDocument()
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("has no detectable accessibility violations", async () => {
|
|
303
|
+
const { container } = render(
|
|
304
|
+
<DatePicker
|
|
305
|
+
id="date-picker"
|
|
306
|
+
label="Select Date"
|
|
307
|
+
value="10-27-2023"
|
|
308
|
+
onChange={onChangeMock}
|
|
309
|
+
/>
|
|
310
|
+
);
|
|
311
|
+
const results = await axe(container);
|
|
312
|
+
expect(results).toHaveNoViolations();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("handles empty initial value by setting today's date internally but displays an empty input", async () => {
|
|
316
|
+
// Render the DatePicker without providing a value prop.
|
|
317
|
+
render(
|
|
318
|
+
<DatePicker
|
|
319
|
+
id="date-picker-empty"
|
|
320
|
+
label="Select Date"
|
|
321
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
322
|
+
onChange={() => {}}
|
|
323
|
+
/>
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// The component's effect sets inputValue to today's date internally,
|
|
327
|
+
// but because valueEntered is set to false, the input shows an empty string.
|
|
328
|
+
const input = screen.getByRole("textbox");
|
|
329
|
+
|
|
330
|
+
// Wait for the effect to complete and then assert the input displays empty string.
|
|
331
|
+
await waitFor(() => {
|
|
332
|
+
expect(input).toHaveValue('');
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
it("handles input change with a regex-valid but invalid date, triggering the 'else' branch", async () => {
|
|
338
|
+
const onChangeMock = vi.fn();
|
|
339
|
+
|
|
340
|
+
render(
|
|
341
|
+
<DatePicker
|
|
342
|
+
id="date-picker-invalid"
|
|
343
|
+
label="Select Date"
|
|
344
|
+
value="10-27-2023"
|
|
345
|
+
onChange={onChangeMock}
|
|
346
|
+
/>
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const input = screen.getByRole("textbox");
|
|
350
|
+
|
|
351
|
+
// Clear the input and simulate typing an invalid date.
|
|
352
|
+
// "02-31-2023" matches the regex (02 for month, 31 for day, 2023 for year)
|
|
353
|
+
// but February 31st is not a valid date.
|
|
354
|
+
userEvent.clear(input);
|
|
355
|
+
userEvent.type(input, "02-34-2023");
|
|
356
|
+
|
|
357
|
+
// Wait until the component processes the input change.
|
|
358
|
+
await waitFor(() => {
|
|
359
|
+
// onChange should be called with the invalid date.
|
|
360
|
+
expect(onChangeMock).toHaveBeenLastCalledWith("02-34-2023");
|
|
361
|
+
|
|
362
|
+
// Because the date is invalid, an error message should be displayed.
|
|
363
|
+
expect(
|
|
364
|
+
screen.getByText(/please enter a valid date 'MM-DD-YYYY'/i)
|
|
365
|
+
).toBeInTheDocument();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
/*describe("DatePicker SVG styling branch", () => {
|
|
373
|
+
const originalSvg = '<svg fill="#000" stroke="#000"></svg>';
|
|
374
|
+
|
|
375
|
+
beforeEach(() => {
|
|
376
|
+
// Mock the fetch call so that it always returns our known SVG.
|
|
377
|
+
global.fetch = vi.fn(() =>
|
|
378
|
+
Promise.resolve({
|
|
379
|
+
text: () => Promise.resolve(originalSvg)
|
|
380
|
+
})
|
|
381
|
+
) as unknown as typeof fetch;
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
afterEach(() => {
|
|
385
|
+
vi.resetAllMocks();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("returns the original SVG when color prop is empty", async () => {
|
|
389
|
+
render(
|
|
390
|
+
<DatePicker
|
|
391
|
+
id="date-picker-empty-color"
|
|
392
|
+
label="Select Date"
|
|
393
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
394
|
+
onChange={() => {}}
|
|
395
|
+
color=""
|
|
396
|
+
/>
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Wait for the SVG image to load.
|
|
400
|
+
const img = await screen.findByAltText("calendar icon");
|
|
401
|
+
const expectedSrc = `data:image/svg+xml,${encodeURIComponent(originalSvg)}`;
|
|
402
|
+
|
|
403
|
+
expect(img).toHaveAttribute("src", expectedSrc);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("returns the original SVG when color prop is 'none'", async () => {
|
|
407
|
+
render(
|
|
408
|
+
<DatePicker
|
|
409
|
+
id="date-picker-none-color"
|
|
410
|
+
label="Select Date"
|
|
411
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
412
|
+
onChange={() => {}}
|
|
413
|
+
color="none"
|
|
414
|
+
/>
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// Wait for the SVG image to load.
|
|
418
|
+
const img = await screen.findByAltText("calendar icon");
|
|
419
|
+
const expectedSrc = `data:image/svg+xml,${encodeURIComponent(originalSvg)}`;
|
|
420
|
+
|
|
421
|
+
expect(img).toHaveAttribute("src", expectedSrc);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
*/
|