@openmrs/esm-styleguide 10.0.1-pre.4966 → 10.0.1-pre.4968

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.
@@ -1,3 +1,3 @@
1
- [0] Successfully compiled: 123 files, copied 164 files with swc (211.09ms)
1
+ [0] Successfully compiled: 123 files, copied 164 files with swc (251.02ms)
2
2
  [0] swc --strip-leading-paths --copy-files src -d dist && svgo -rf dist --quiet exited with code 0
3
3
  [1] tsc --project tsconfig.build.json exited with code 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-styleguide",
3
- "version": "10.0.1-pre.4966",
3
+ "version": "10.0.1-pre.4968",
4
4
  "license": "MPL-2.0",
5
5
  "description": "The styleguide for OpenMRS SPA",
6
6
  "module": "dist/internal.js",
@@ -77,18 +77,18 @@
77
77
  "react-aria-components": "^1.7.1"
78
78
  },
79
79
  "peerDependencies": {
80
- "@openmrs/esm-api": "^10.0.1-pre.4966",
81
- "@openmrs/esm-config": "^10.0.1-pre.4966",
82
- "@openmrs/esm-emr-api": "^10.0.1-pre.4966",
83
- "@openmrs/esm-error-handling": "^10.0.1-pre.4966",
84
- "@openmrs/esm-extensions": "^10.0.1-pre.4966",
85
- "@openmrs/esm-globals": "^10.0.1-pre.4966",
86
- "@openmrs/esm-navigation": "^10.0.1-pre.4966",
87
- "@openmrs/esm-react-utils": "^10.0.1-pre.4966",
88
- "@openmrs/esm-routes": "^10.0.1-pre.4966",
89
- "@openmrs/esm-state": "^10.0.1-pre.4966",
90
- "@openmrs/esm-translations": "^10.0.1-pre.4966",
91
- "@openmrs/esm-utils": "^10.0.1-pre.4966",
80
+ "@openmrs/esm-api": "^10.0.1-pre.4968",
81
+ "@openmrs/esm-config": "^10.0.1-pre.4968",
82
+ "@openmrs/esm-emr-api": "^10.0.1-pre.4968",
83
+ "@openmrs/esm-error-handling": "^10.0.1-pre.4968",
84
+ "@openmrs/esm-extensions": "^10.0.1-pre.4968",
85
+ "@openmrs/esm-globals": "^10.0.1-pre.4968",
86
+ "@openmrs/esm-navigation": "^10.0.1-pre.4968",
87
+ "@openmrs/esm-react-utils": "^10.0.1-pre.4968",
88
+ "@openmrs/esm-routes": "^10.0.1-pre.4968",
89
+ "@openmrs/esm-state": "^10.0.1-pre.4968",
90
+ "@openmrs/esm-translations": "^10.0.1-pre.4968",
91
+ "@openmrs/esm-utils": "^10.0.1-pre.4968",
92
92
  "dayjs": "1.x",
93
93
  "i18next": "25.x",
94
94
  "react": "18.x",
@@ -98,18 +98,18 @@
98
98
  "swr": "2.x"
99
99
  },
100
100
  "devDependencies": {
101
- "@openmrs/esm-api": "10.0.1-pre.4966",
102
- "@openmrs/esm-config": "10.0.1-pre.4966",
103
- "@openmrs/esm-emr-api": "10.0.1-pre.4966",
104
- "@openmrs/esm-error-handling": "10.0.1-pre.4966",
105
- "@openmrs/esm-extensions": "10.0.1-pre.4966",
106
- "@openmrs/esm-globals": "10.0.1-pre.4966",
107
- "@openmrs/esm-navigation": "10.0.1-pre.4966",
108
- "@openmrs/esm-react-utils": "10.0.1-pre.4966",
109
- "@openmrs/esm-routes": "10.0.1-pre.4966",
110
- "@openmrs/esm-state": "10.0.1-pre.4966",
111
- "@openmrs/esm-translations": "10.0.1-pre.4966",
112
- "@openmrs/esm-utils": "10.0.1-pre.4966",
101
+ "@openmrs/esm-api": "10.0.1-pre.4968",
102
+ "@openmrs/esm-config": "10.0.1-pre.4968",
103
+ "@openmrs/esm-emr-api": "10.0.1-pre.4968",
104
+ "@openmrs/esm-error-handling": "10.0.1-pre.4968",
105
+ "@openmrs/esm-extensions": "10.0.1-pre.4968",
106
+ "@openmrs/esm-globals": "10.0.1-pre.4968",
107
+ "@openmrs/esm-navigation": "10.0.1-pre.4968",
108
+ "@openmrs/esm-react-utils": "10.0.1-pre.4968",
109
+ "@openmrs/esm-routes": "10.0.1-pre.4968",
110
+ "@openmrs/esm-state": "10.0.1-pre.4968",
111
+ "@openmrs/esm-translations": "10.0.1-pre.4968",
112
+ "@openmrs/esm-utils": "10.0.1-pre.4968",
113
113
  "@swc/cli": "0.8.1",
114
114
  "@swc/core": "1.15.21",
115
115
  "@types/geopattern": "^1.2.9",
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import type { i18n } from 'i18next';
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import '@testing-library/jest-dom/vitest';
5
- import { render, screen, within } from '@testing-library/react';
5
+ import { render, screen, waitFor, within } from '@testing-library/react';
6
6
  import userEvent from '@testing-library/user-event';
7
7
  import { useConfig } from '@openmrs/esm-react-utils/mock';
8
8
  import { OpenmrsDatePicker } from './index';
@@ -41,26 +41,20 @@ describe('OpenmrsDatePicker', () => {
41
41
  it('should render RTL layout for Arabic locale', () => {
42
42
  window.i18next = { language: 'ar' } as i18n;
43
43
  useConfig.mockReturnValue({ preferredDateLocale: {} });
44
-
45
44
  render(<OpenmrsDatePicker aria-label="datepicker" />);
46
45
  const input = screen.getByLabelText('datepicker');
47
46
  const text = input.textContent?.replace(/\u200F/g, '');
48
-
49
47
  expect(text).toBe('يوم/شهر/سنة');
50
-
51
48
  window.i18next = { language: 'en' } as i18n;
52
49
  });
53
50
 
54
51
  it('should render RTL layout for Amharic locale', () => {
55
52
  window.i18next = { language: 'am' } as i18n;
56
53
  useConfig.mockReturnValue({ preferredDateLocale: {} });
57
-
58
54
  render(<OpenmrsDatePicker aria-label="datepicker" />);
59
55
  const input = screen.getByLabelText('datepicker');
60
56
  const text = input.textContent?.replace(/\u200F/g, '');
61
-
62
57
  expect(text).toBe('ቀቀ/ሚሜ/ዓዓዓዓ');
63
-
64
58
  window.i18next = { language: 'en' } as i18n;
65
59
  });
66
60
  });
@@ -90,6 +84,24 @@ describe('OpenmrsDatePicker', () => {
90
84
  );
91
85
  consoleWarnSpy.mockRestore();
92
86
  });
87
+
88
+ it('should support the deprecated label prop', () => {
89
+ render(<OpenmrsDatePicker label="Legacy label" />);
90
+ expect(screen.getByText('Legacy label')).toBeInTheDocument();
91
+ expect(screen.getByText('Legacy label')).toHaveClass('cds--label');
92
+ });
93
+
94
+ it('should not warn when aria-labelledby is provided', () => {
95
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
96
+ render(
97
+ <>
98
+ <span id="my-label">External label</span>
99
+ <OpenmrsDatePicker aria-labelledby="my-label" labelText="" />
100
+ </>,
101
+ );
102
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
103
+ consoleWarnSpy.mockRestore();
104
+ });
93
105
  });
94
106
 
95
107
  describe('value display', () => {
@@ -104,6 +116,18 @@ describe('OpenmrsDatePicker', () => {
104
116
  const input = screen.getByLabelText('datepicker');
105
117
  expect(input).toHaveTextContent('18/06/2025');
106
118
  });
119
+
120
+ it('should display placeholder when value is undefined', () => {
121
+ render(<OpenmrsDatePicker aria-label="datepicker" value={undefined} />);
122
+ const input = screen.getByLabelText('datepicker');
123
+ expect(input).toHaveTextContent('dd/mm/yyyy');
124
+ });
125
+
126
+ it('should display placeholder when value is null', () => {
127
+ render(<OpenmrsDatePicker aria-label="datepicker" value={null} />);
128
+ const input = screen.getByLabelText('datepicker');
129
+ expect(input).toHaveTextContent('dd/mm/yyyy');
130
+ });
107
131
  });
108
132
 
109
133
  describe('invalid state', () => {
@@ -121,6 +145,11 @@ describe('OpenmrsDatePicker', () => {
121
145
  render(<OpenmrsDatePicker aria-label="datepicker" isInvalid={true} invalidText="Bad date" />);
122
146
  expect(screen.getByText('Bad date')).toBeInTheDocument();
123
147
  });
148
+
149
+ it('should be invalid when either invalid or isInvalid is true', () => {
150
+ render(<OpenmrsDatePicker aria-label="datepicker" invalid={true} isInvalid={false} invalidText="Error" />);
151
+ expect(screen.getByText('Error')).toBeInTheDocument();
152
+ });
124
153
  });
125
154
 
126
155
  describe('disabled state', () => {
@@ -129,6 +158,12 @@ describe('OpenmrsDatePicker', () => {
129
158
  const label = screen.getByText('Date');
130
159
  expect(label).toHaveClass('cds--label--disabled');
131
160
  });
161
+
162
+ it('should not show disabled styling when isDisabled is false', () => {
163
+ render(<OpenmrsDatePicker labelText="Date" isDisabled={false} />);
164
+ const label = screen.getByText('Date');
165
+ expect(label).not.toHaveClass('cds--label--disabled');
166
+ });
132
167
  });
133
168
 
134
169
  describe('onChange callbacks', () => {
@@ -136,57 +171,105 @@ describe('OpenmrsDatePicker', () => {
136
171
  const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
137
172
  const onChange = vi.fn();
138
173
  const onChangeRaw = vi.fn();
139
-
140
174
  render(<OpenmrsDatePicker aria-label="datepicker" onChange={onChange} onChangeRaw={onChangeRaw} />);
141
-
142
175
  expect(consoleErrorSpy).toHaveBeenCalledWith(
143
176
  'An OpenmrsDatePicker component was created with both onChange and onChangeRaw handlers defined. Only onChangeRaw will be used.',
144
177
  );
145
178
  consoleErrorSpy.mockRestore();
146
179
  });
180
+
181
+ it('should not log an error when only onChange is provided', () => {
182
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
183
+ render(<OpenmrsDatePicker aria-label="datepicker" onChange={vi.fn()} />);
184
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
185
+ consoleErrorSpy.mockRestore();
186
+ });
187
+
188
+ it('should not log an error when only onChangeRaw is provided', () => {
189
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
190
+ render(<OpenmrsDatePicker aria-label="datepicker" onChangeRaw={vi.fn()} />);
191
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
192
+ consoleErrorSpy.mockRestore();
193
+ });
147
194
  });
148
195
 
149
196
  describe('size prop', () => {
150
197
  /* eslint-disable testing-library/no-container, testing-library/no-node-access */
151
198
  it('should apply md size classes by default', () => {
152
199
  const { container } = render(<OpenmrsDatePicker aria-label="datepicker" />);
153
- const wrapper = container.querySelector('.cds--date-picker-input__wrapper')!;
200
+ const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
201
+ expect(wrapper).not.toBeNull();
154
202
  expect(wrapper).toHaveClass(styles.inputWrapperMd);
155
203
  expect(screen.getByRole('button')).toHaveClass(styles.flatButtonMd);
156
204
  });
157
205
 
158
206
  it('should apply sm size classes when size="sm"', () => {
159
207
  const { container } = render(<OpenmrsDatePicker aria-label="datepicker" size="sm" />);
160
- const wrapper = container.querySelector('.cds--date-picker-input__wrapper')!;
208
+ const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
209
+ expect(wrapper).not.toBeNull();
161
210
  expect(wrapper).toHaveClass(styles.inputWrapperSm);
162
211
  expect(screen.getByRole('button')).toHaveClass(styles.flatButtonSm);
163
212
  });
164
213
 
165
214
  it('should apply md size classes when size="md"', () => {
166
215
  const { container } = render(<OpenmrsDatePicker aria-label="datepicker" size="md" />);
167
- const wrapper = container.querySelector('.cds--date-picker-input__wrapper')!;
216
+ const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
217
+ expect(wrapper).not.toBeNull();
168
218
  expect(wrapper).toHaveClass(styles.inputWrapperMd);
169
219
  expect(screen.getByRole('button')).toHaveClass(styles.flatButtonMd);
170
220
  });
171
221
 
172
222
  it('should apply lg size classes when size="lg"', () => {
173
223
  const { container } = render(<OpenmrsDatePicker aria-label="datepicker" size="lg" />);
174
- const wrapper = container.querySelector('.cds--date-picker-input__wrapper')!;
224
+ const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
225
+ expect(wrapper).not.toBeNull();
175
226
  expect(wrapper).toHaveClass(styles.inputWrapperLg);
176
227
  expect(screen.getByRole('button')).toHaveClass(styles.flatButtonLg);
177
228
  });
178
229
  /* eslint-enable testing-library/no-container, testing-library/no-node-access */
179
230
  });
180
231
 
232
+ describe('CSS class variants', () => {
233
+ /* eslint-disable testing-library/no-container, testing-library/no-node-access */
234
+ it('should apply short class when short prop is true', () => {
235
+ const { container } = render(<OpenmrsDatePicker aria-label="datepicker" short={true} />);
236
+ const datePicker = container.querySelector('.cds--date-picker');
237
+ expect(datePicker).not.toBeNull();
238
+ expect(datePicker).toHaveClass('cds--date-picker--short');
239
+ });
240
+
241
+ it('should not apply short class when short prop is false', () => {
242
+ const { container } = render(<OpenmrsDatePicker aria-label="datepicker" short={false} />);
243
+ const datePicker = container.querySelector('.cds--date-picker');
244
+ expect(datePicker).not.toBeNull();
245
+ expect(datePicker).not.toHaveClass('cds--date-picker--short');
246
+ });
247
+
248
+ it('should apply light class when light prop is true', () => {
249
+ const { container } = render(<OpenmrsDatePicker aria-label="datepicker" light={true} />);
250
+ const datePicker = container.querySelector('.cds--date-picker');
251
+ expect(datePicker).not.toBeNull();
252
+ expect(datePicker).toHaveClass('cds--date-picker--light');
253
+ });
254
+
255
+ it('should apply custom className to the outer div', () => {
256
+ const { container } = render(<OpenmrsDatePicker aria-label="datepicker" className="my-custom-class" />);
257
+ const outerDiv = container.querySelector('.cds--form-item');
258
+ expect(outerDiv).not.toBeNull();
259
+ expect(outerDiv).toHaveClass('my-custom-class');
260
+ });
261
+ /* eslint-enable testing-library/no-container, testing-library/no-node-access */
262
+ });
263
+
181
264
  describe('calendar popover', () => {
182
265
  it('should open the calendar popover when the calendar button is clicked', async () => {
183
266
  const user = userEvent.setup();
184
267
  render(<OpenmrsDatePicker aria-label="datepicker" />);
185
-
186
268
  const button = screen.getByRole('button');
187
269
  await user.click(button);
188
-
189
- expect(screen.getByRole('dialog')).toBeInTheDocument();
270
+ await waitFor(() => {
271
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
272
+ });
190
273
  expect(screen.getByRole('grid')).toBeInTheDocument();
191
274
  });
192
275
 
@@ -198,14 +281,45 @@ describe('OpenmrsDatePicker', () => {
198
281
  value={new Date(DEFAULT_MIN_DATE_FLOOR.year, DEFAULT_MIN_DATE_FLOOR.month - 1, DEFAULT_MIN_DATE_FLOOR.day)}
199
282
  />,
200
283
  );
201
-
202
284
  await user.click(screen.getByRole('button'));
203
-
285
+ await waitFor(() => {
286
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
287
+ });
204
288
  const dialog = screen.getByRole('dialog');
205
289
  const previousButton = within(dialog).getByRole('button', { name: /previous/i });
206
-
207
290
  expect(previousButton).toBeInTheDocument();
208
291
  expect(previousButton).toBeDisabled();
209
292
  });
293
+
294
+ it('should respect maxDate and disable next-month navigation at the max boundary', async () => {
295
+ const user = userEvent.setup();
296
+ render(
297
+ <OpenmrsDatePicker aria-label="datepicker" value={new Date(2025, 5, 15)} maxDate={new Date(2025, 5, 30)} />,
298
+ );
299
+ await user.click(screen.getByRole('button'));
300
+ await waitFor(() => {
301
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
302
+ });
303
+ const dialog = screen.getByRole('dialog');
304
+ const nextButtons = within(dialog).getAllByRole('button', { name: /next/i });
305
+ expect(nextButtons[0]).toBeDisabled();
306
+ });
307
+
308
+ it('should select a date and call onChange', async () => {
309
+ const user = userEvent.setup();
310
+ const onChange = vi.fn();
311
+ render(<OpenmrsDatePicker aria-label="datepicker" defaultValue={new Date(2025, 5, 1)} onChange={onChange} />);
312
+ await user.click(screen.getByRole('button'));
313
+ await waitFor(() => {
314
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
315
+ });
316
+ const dialog = screen.getByRole('dialog');
317
+ const grid = within(dialog).getByRole('grid');
318
+ const dayCell = within(grid).getByRole('button', { name: /15/ });
319
+ await user.click(dayCell);
320
+ await waitFor(() => {
321
+ expect(onChange).toHaveBeenCalledWith(new Date(2025, 5, 15));
322
+ });
323
+ });
210
324
  });
211
325
  });