@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.
Files changed (227) hide show
  1. package/.babelrc +12 -0
  2. package/.storybook/main.ts +35 -0
  3. package/.storybook/preview.ts +4 -0
  4. package/BAKpostcss.config.jsBAK +15 -0
  5. package/BAKtailwind.config.mjsBAK +99 -0
  6. package/README.md +464 -16
  7. package/coverage/storybook/coverage-storybook.json +32411 -0
  8. package/coverage/storybook/lcov-report/Accordion.tsx.html +805 -0
  9. package/coverage/storybook/lcov-report/Badge.tsx.html +346 -0
  10. package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +742 -0
  11. package/coverage/storybook/lcov-report/Button.tsx.html +448 -0
  12. package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +403 -0
  13. package/coverage/storybook/lcov-report/Card.tsx.html +292 -0
  14. package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +253 -0
  15. package/coverage/storybook/lcov-report/CheckBox.tsx.html +1555 -0
  16. package/coverage/storybook/lcov-report/DatePicker.tsx.html +826 -0
  17. package/coverage/storybook/lcov-report/Input.tsx.html +1012 -0
  18. package/coverage/storybook/lcov-report/List.tsx.html +364 -0
  19. package/coverage/storybook/lcov-report/Modal.tsx.html +745 -0
  20. package/coverage/storybook/lcov-report/Pill.tsx.html +358 -0
  21. package/coverage/storybook/lcov-report/Search.tsx.html +997 -0
  22. package/coverage/storybook/lcov-report/SearchContent.tsx.html +235 -0
  23. package/coverage/storybook/lcov-report/SectionHeader.tsx.html +358 -0
  24. package/coverage/storybook/lcov-report/Select.tsx.html +1012 -0
  25. package/coverage/storybook/lcov-report/Shield.tsx.html +802 -0
  26. package/coverage/storybook/lcov-report/SideBarNav.tsx.html +490 -0
  27. package/coverage/storybook/lcov-report/Skeleton.tsx.html +394 -0
  28. package/coverage/storybook/lcov-report/Slider.tsx.html +385 -0
  29. package/coverage/storybook/lcov-report/Status.tsx.html +322 -0
  30. package/coverage/storybook/lcov-report/Tabs.tsx.html +610 -0
  31. package/coverage/storybook/lcov-report/Toggle.tsx.html +373 -0
  32. package/coverage/storybook/lcov-report/Tooltip.tsx.html +496 -0
  33. package/coverage/storybook/lcov-report/base.css +224 -0
  34. package/coverage/storybook/lcov-report/block-navigation.js +87 -0
  35. package/coverage/storybook/lcov-report/favicon.png +0 -0
  36. package/coverage/storybook/lcov-report/index.html +476 -0
  37. package/coverage/storybook/lcov-report/prettify.css +1 -0
  38. package/coverage/storybook/lcov-report/prettify.js +2 -0
  39. package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
  40. package/coverage/storybook/lcov-report/sorter.js +196 -0
  41. package/coverage/storybook/lcov.info +2312 -0
  42. package/dist/README.md +1815 -0
  43. package/eslint.config.mjs +13 -0
  44. package/package.json +6 -7
  45. package/project.json +11 -0
  46. package/src/assets/img/Frame.svg +5 -0
  47. package/src/assets/img/backArrowRight.svg +10 -0
  48. package/src/assets/img/bc-separator.png +0 -0
  49. package/src/assets/img/calendar.png +0 -0
  50. package/src/assets/img/calendar.svg +4 -0
  51. package/src/assets/img/check.svg +5 -0
  52. package/src/assets/img/check_box.svg +10 -0
  53. package/src/assets/img/check_box_empty.svg +10 -0
  54. package/src/assets/img/check_box_fill.svg +10 -0
  55. package/src/assets/img/check_box_fill_empty.svg +10 -0
  56. package/src/assets/img/chevron-down-white.svg +2 -0
  57. package/src/assets/img/chevron-down.svg +2 -0
  58. package/src/assets/img/chevron-left.svg +1 -0
  59. package/src/assets/img/chevron-right-light.svg +4 -0
  60. package/src/assets/img/chevron-right.svg +3 -0
  61. package/src/assets/img/chevron-up-white.svg +1 -0
  62. package/src/assets/img/chevron-up.svg +1 -0
  63. package/src/assets/img/clock.svg +6 -0
  64. package/src/assets/img/close.svg +1 -0
  65. package/src/assets/img/close2.svg +6 -0
  66. package/src/assets/img/closeModal.svg +10 -0
  67. package/src/assets/img/close_icon_dark.svg +10 -0
  68. package/src/assets/img/close_small.svg +3 -0
  69. package/src/assets/img/emergency_home.svg +10 -0
  70. package/src/assets/img/first-aid-kit.svg +7 -0
  71. package/src/assets/img/heartbeat.svg +4 -0
  72. package/src/assets/img/home-gray.svg +3 -0
  73. package/src/assets/img/home.svg +3 -0
  74. package/src/assets/img/hospital.jpg +0 -0
  75. package/src/assets/img/indeterminate_check_box.svg +10 -0
  76. package/src/assets/img/indeterminate_check_box_fill.svg +10 -0
  77. package/src/assets/img/info_24_ 1d4ed8.svg +3 -0
  78. package/src/assets/img/info_24_ 2c6441.svg +3 -0
  79. package/src/assets/img/marker_check_by_default.svg +10 -0
  80. package/src/assets/img/marker_check_by_default_fill.svg +10 -0
  81. package/src/assets/img/minus-accordion.svg +5 -0
  82. package/src/assets/img/minus.svg +3 -0
  83. package/src/assets/img/open.svg +1 -0
  84. package/src/assets/img/pill-white.svg +7 -0
  85. package/src/assets/img/pill.svg +5 -0
  86. package/src/assets/img/plus-accordion.svg +5 -0
  87. package/src/assets/img/plus.svg +4 -0
  88. package/src/assets/img/prescription.svg +6 -0
  89. package/src/assets/img/search.svg +10 -0
  90. package/src/assets/img/search_icon_light.svg +10 -0
  91. package/src/assets/img/separator.svg +3 -0
  92. package/src/assets/img/stethoscope-white.svg +8 -0
  93. package/src/assets/img/stethoscope.svg +8 -0
  94. package/src/assets/img/thumb_up.svg +10 -0
  95. package/src/assets/img/vector.svg +3 -0
  96. package/src/assets/img/warning-badge-disabled.svg +11 -0
  97. package/src/assets/img/warning-badge-green.svg +11 -0
  98. package/src/assets/img/warning-badge-red.svg +11 -0
  99. package/src/assets/img/warning-badge-yellow.svg +11 -0
  100. package/src/assets/img/warning.svg +10 -0
  101. package/src/global.d.ts +13 -0
  102. package/{index.d.ts → src/index.ts} +13 -5
  103. package/src/lib/Accordian--Accordian.stories.tsx +312 -0
  104. package/src/lib/Accordion.spec.tsx +384 -0
  105. package/src/lib/Accordion.tsx +240 -0
  106. package/src/lib/AppointmentPicker.spec.tsx +138 -0
  107. package/src/lib/AppointmentPicker.tsx +97 -0
  108. package/src/lib/Badge--Badge.stories.tsx +60 -0
  109. package/src/lib/Badge.spec.tsx +70 -0
  110. package/src/lib/Badge.tsx +87 -0
  111. package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +114 -0
  112. package/src/lib/Breadcrumbs.spec.tsx +218 -0
  113. package/src/lib/Breadcrumbs.tsx +219 -0
  114. package/src/lib/Button--Button.stories.tsx +220 -0
  115. package/src/lib/Button.spec.tsx +241 -0
  116. package/src/lib/Button.tsx +121 -0
  117. package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +129 -0
  118. package/src/lib/ButtonGroup.spec.tsx +89 -0
  119. package/src/lib/ButtonGroup.tsx +107 -0
  120. package/src/lib/Card--Card.stories.tsx +113 -0
  121. package/src/lib/Card.spec.tsx +112 -0
  122. package/src/lib/Card.tsx +69 -0
  123. package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +169 -0
  124. package/src/lib/CharacterCounter.spec.tsx +123 -0
  125. package/src/lib/CharacterCounter.tsx +56 -0
  126. package/src/lib/CheckBox--CheckBox.stories.tsx +107 -0
  127. package/src/lib/CheckBox.spec.tsx +412 -0
  128. package/src/lib/CheckBox.tsx +491 -0
  129. package/src/lib/DatePicker--DatePicker.stories.tsx +228 -0
  130. package/src/lib/DatePicker.spec.tsx +424 -0
  131. package/src/lib/DatePicker.tsx +247 -0
  132. package/src/lib/Input--Input.stories.tsx +449 -0
  133. package/src/lib/Input.spec.tsx +281 -0
  134. package/src/lib/Input.tsx +309 -0
  135. package/src/lib/List--List.stories.tsx +157 -0
  136. package/src/lib/List.spec.tsx +211 -0
  137. package/src/lib/List.tsx +93 -0
  138. package/src/lib/Modal--Modal.stories.tsx +454 -0
  139. package/src/lib/Modal.spec.tsx +202 -0
  140. package/src/lib/Modal.tsx +220 -0
  141. package/src/lib/Pill--Pill.stories.tsx +98 -0
  142. package/src/lib/Pill.spec.tsx +103 -0
  143. package/src/lib/Pill.tsx +91 -0
  144. package/src/lib/ProgressBar.spec.tsx +106 -0
  145. package/src/lib/ProgressBar.tsx +112 -0
  146. package/src/lib/RadioGroup.spec.tsx +84 -0
  147. package/src/lib/RadioGroup.tsx +74 -0
  148. package/src/lib/RadioIcon.tsx +13 -0
  149. package/src/lib/Search--Search.stories.tsx +67 -0
  150. package/src/lib/Search.spec.tsx +182 -0
  151. package/src/lib/Search.tsx +304 -0
  152. package/src/lib/SearchContent.tsx +51 -0
  153. package/src/lib/SectionHeader--SectionHeader.stories.tsx +98 -0
  154. package/src/lib/SectionHeader.spec.tsx +60 -0
  155. package/src/lib/SectionHeader.tsx +91 -0
  156. package/src/lib/Select--Select.stories.tsx +387 -0
  157. package/src/lib/Select.spec.tsx +493 -0
  158. package/src/lib/Select.tsx +311 -0
  159. package/src/lib/Shield--Shield.stories.tsx +196 -0
  160. package/src/lib/Shield.spec.tsx +275 -0
  161. package/src/lib/Shield.tsx +239 -0
  162. package/src/lib/SideBarNav--SideBarNav.stories.tsx +136 -0
  163. package/src/lib/SideBarNav.spec.tsx +178 -0
  164. package/src/lib/SideBarNav.tsx +135 -0
  165. package/src/lib/Skeleton--Skeleton.stories.tsx +77 -0
  166. package/src/lib/Skeleton.module.css +16 -0
  167. package/src/lib/Skeleton.spec.tsx +83 -0
  168. package/src/lib/Skeleton.tsx +103 -0
  169. package/src/lib/SkipLink.spec.tsx +76 -0
  170. package/src/lib/SkipLink.tsx +48 -0
  171. package/src/lib/Slider--Slider.stories.tsx +108 -0
  172. package/src/lib/Slider.module.css +109 -0
  173. package/src/lib/Slider.spec.tsx +67 -0
  174. package/src/lib/Slider.tsx +101 -0
  175. package/src/lib/Status--Status.stories.tsx +93 -0
  176. package/src/lib/Status.spec.tsx +118 -0
  177. package/src/lib/Status.tsx +79 -0
  178. package/src/lib/Tabs--Tabs.stories.tsx +294 -0
  179. package/src/lib/Tabs.spec.tsx +249 -0
  180. package/src/lib/Tabs.tsx +188 -0
  181. package/src/lib/Tester.spec.tsx +17 -0
  182. package/src/lib/Toggle--Toggle.stories.tsx +162 -0
  183. package/src/lib/Toggle.spec.tsx +122 -0
  184. package/src/lib/Toggle.tsx +96 -0
  185. package/src/lib/Tooltip--Tooltip.stories.tsx +315 -0
  186. package/src/lib/Tooltip.spec.tsx +307 -0
  187. package/src/lib/Tooltip.tsx +137 -0
  188. package/src/lib/bak-simple-ui.stories.tsx-bak +24 -0
  189. package/src/styles.css +190 -0
  190. package/tsconfig.json +25 -0
  191. package/tsconfig.lib.json +42 -0
  192. package/tsconfig.spec.json +29 -0
  193. package/tsconfig.storybook.json +36 -0
  194. package/vite.config.mts +87 -0
  195. package/vitest.setup.ts +12 -0
  196. package/index.css +0 -1
  197. package/index.js +0 -35
  198. package/index.mjs +0 -4981
  199. package/lib/Accordion.d.ts +0 -36
  200. package/lib/AppointmentPicker.d.ts +0 -21
  201. package/lib/Badge.d.ts +0 -11
  202. package/lib/Breadcrumbs.d.ts +0 -13
  203. package/lib/Button.d.ts +0 -15
  204. package/lib/ButtonGroup.d.ts +0 -8
  205. package/lib/Card.d.ts +0 -11
  206. package/lib/CharacterCounter.d.ts +0 -11
  207. package/lib/CheckBox.d.ts +0 -30
  208. package/lib/DatePicker.d.ts +0 -7
  209. package/lib/Input.d.ts +0 -16
  210. package/lib/List.d.ts +0 -22
  211. package/lib/Modal.d.ts +0 -18
  212. package/lib/Pill.d.ts +0 -13
  213. package/lib/ProgressBar.d.ts +0 -19
  214. package/lib/RadioGroup.d.ts +0 -15
  215. package/lib/Search.d.ts +0 -26
  216. package/lib/SearchContent.d.ts +0 -6
  217. package/lib/SectionHeader.d.ts +0 -18
  218. package/lib/Select.d.ts +0 -19
  219. package/lib/Shield.d.ts +0 -12
  220. package/lib/SideBarNav.d.ts +0 -21
  221. package/lib/Skeleton.d.ts +0 -15
  222. package/lib/SkipLink.d.ts +0 -22
  223. package/lib/Slider.d.ts +0 -14
  224. package/lib/Status.d.ts +0 -10
  225. package/lib/Tabs.d.ts +0 -23
  226. package/lib/Toggle.d.ts +0 -11
  227. 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
+ */