Skip to content

Commit a075ae0

Browse files
committed
Add more unit tests
1 parent fd08d21 commit a075ae0

File tree

9 files changed

+316
-42
lines changed

9 files changed

+316
-42
lines changed

packages/circuit-ui/components/DateInput/DateInput.spec.tsx

Lines changed: 127 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { beforeAll, describe, expect, it, vi } from 'vitest';
16+
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
1717
import { createRef } from 'react';
1818
import MockDate from 'mockdate';
1919

2020
import { render, screen, axe, userEvent } from '../../util/test-utils.js';
21+
import { useMedia } from '../../hooks/useMedia/useMedia.js';
2122

2223
import { DateInput } from './DateInput.js';
2324

25+
vi.mock('../../hooks/useMedia/useMedia.js');
26+
2427
describe('DateInput', () => {
2528
const props = {
2629
onChange: vi.fn(),
@@ -36,8 +39,9 @@ describe('DateInput', () => {
3639
clearDateButtonLabel: 'Clear date',
3740
};
3841

39-
beforeAll(() => {
42+
beforeEach(() => {
4043
MockDate.set('2000-01-01');
44+
(useMedia as Mock).mockReturnValue(false);
4145
});
4246

4347
it('should forward a ref', () => {
@@ -182,32 +186,38 @@ describe('DateInput', () => {
182186
);
183187
});
184188

185-
it.skip('should mark the year input as readonly when the minimum and maximum dates have the same year', () => {
186-
render(<DateInput {...props} min="2000-04-29" max="2000-06-15" />);
187-
expect(screen.getByLabelText(/year/i)).toHaveAttribute('readonly');
188-
expect(screen.getByLabelText(/month/i)).toHaveAttribute(
189-
'aria-valuemin',
190-
'4',
191-
);
192-
expect(screen.getByLabelText(/month/i)).toHaveAttribute(
193-
'aria-valuemax',
194-
'6',
195-
);
196-
});
189+
it.todo(
190+
'should mark the year input as readonly when the minimum and maximum dates have the same year',
191+
() => {
192+
render(<DateInput {...props} min="2000-04-29" max="2000-06-15" />);
193+
expect(screen.getByLabelText(/year/i)).toHaveAttribute('readonly');
194+
expect(screen.getByLabelText(/month/i)).toHaveAttribute(
195+
'aria-valuemin',
196+
'4',
197+
);
198+
expect(screen.getByLabelText(/month/i)).toHaveAttribute(
199+
'aria-valuemax',
200+
'6',
201+
);
202+
},
203+
);
197204

198-
it.skip('should mark the year and month inputs as readonly when the minimum and maximum dates have the same year and month', () => {
199-
render(<DateInput {...props} min="2000-04-09" max="2000-04-27" />);
200-
expect(screen.getByLabelText(/year/i)).toHaveAttribute('readonly');
201-
expect(screen.getByLabelText(/month/i)).toHaveAttribute('readonly');
202-
expect(screen.getByLabelText(/day/i)).toHaveAttribute(
203-
'aria-valuemin',
204-
'9',
205-
);
206-
expect(screen.getByLabelText(/day/i)).toHaveAttribute(
207-
'aria-valuemax',
208-
'27',
209-
);
210-
});
205+
it.todo(
206+
'should mark the year and month inputs as readonly when the minimum and maximum dates have the same year and month',
207+
() => {
208+
render(<DateInput {...props} min="2000-04-09" max="2000-04-27" />);
209+
expect(screen.getByLabelText(/year/i)).toHaveAttribute('readonly');
210+
expect(screen.getByLabelText(/month/i)).toHaveAttribute('readonly');
211+
expect(screen.getByLabelText(/day/i)).toHaveAttribute(
212+
'aria-valuemin',
213+
'9',
214+
);
215+
expect(screen.getByLabelText(/day/i)).toHaveAttribute(
216+
'aria-valuemax',
217+
'27',
218+
);
219+
},
220+
);
211221
});
212222

213223
describe('state', () => {
@@ -227,6 +237,14 @@ describe('DateInput', () => {
227237
expect(screen.getByLabelText(/year/i)).toHaveValue('2000');
228238
});
229239

240+
it('should ignore an invalid value', () => {
241+
render(<DateInput {...props} value="2000-13-54" />);
242+
243+
expect(screen.getByLabelText(/day/i)).toHaveValue('');
244+
expect(screen.getByLabelText(/month/i)).toHaveValue('');
245+
expect(screen.getByLabelText(/year/i)).toHaveValue('');
246+
});
247+
230248
it('should update the displayed value', () => {
231249
const { rerender } = render(<DateInput {...props} value="2000-01-12" />);
232250

@@ -341,6 +359,88 @@ describe('DateInput', () => {
341359

342360
expect(onChange).toHaveBeenCalledWith('');
343361
});
362+
363+
describe('on narrow viewports', () => {
364+
beforeEach(() => {
365+
(useMedia as Mock).mockReturnValue(true);
366+
});
367+
368+
it('should allow users to select a date on a calendar', async () => {
369+
(useMedia as Mock).mockReturnValue(true);
370+
const onChange = vi.fn();
371+
372+
render(<DateInput {...props} onChange={onChange} />);
373+
374+
const openCalendarButton = screen.getByRole('button', {
375+
name: /change date/i,
376+
});
377+
await userEvent.click(openCalendarButton);
378+
379+
const calendarDialog = screen.getByRole('dialog');
380+
expect(calendarDialog).toBeVisible();
381+
382+
const dateButton = screen.getByRole('button', { name: /12/i });
383+
await userEvent.click(dateButton);
384+
385+
expect(onChange).not.toHaveBeenCalled();
386+
387+
const applyButton = screen.getByRole('button', { name: /apply/i });
388+
await userEvent.click(applyButton);
389+
390+
expect(onChange).toHaveBeenCalledWith('2000-01-12');
391+
});
392+
393+
it('should allow users to clear the date', async () => {
394+
const onChange = vi.fn();
395+
396+
render(
397+
<DateInput
398+
{...props}
399+
defaultValue="2000-01-12"
400+
onChange={onChange}
401+
/>,
402+
);
403+
404+
const openCalendarButton = screen.getByRole('button', {
405+
name: /change date/i,
406+
});
407+
await userEvent.click(openCalendarButton);
408+
409+
const calendarDialog = screen.getByRole('dialog');
410+
expect(calendarDialog).toBeVisible();
411+
412+
const clearButton = screen.getByRole('button', { name: /clear date/i });
413+
await userEvent.click(clearButton);
414+
415+
expect(onChange).toHaveBeenCalledWith('');
416+
});
417+
418+
it('should allow users to close the calendar dialog without selecting a date', async () => {
419+
const onChange = vi.fn();
420+
421+
render(
422+
<DateInput
423+
{...props}
424+
defaultValue="2000-01-12"
425+
onChange={onChange}
426+
/>,
427+
);
428+
429+
const openCalendarButton = screen.getByRole('button', {
430+
name: /change date/i,
431+
});
432+
await userEvent.click(openCalendarButton);
433+
434+
const calendarDialog = screen.getByRole('dialog');
435+
expect(calendarDialog).toBeVisible();
436+
437+
const closeButton = screen.getByRole('button', { name: /close/i });
438+
await userEvent.click(closeButton);
439+
440+
expect(calendarDialog).not.toBeVisible();
441+
expect(onChange).not.toHaveBeenCalled();
442+
});
443+
});
344444
});
345445

346446
describe('status messages', () => {

packages/circuit-ui/components/DateInput/DateInput.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,7 @@ export const DateInput = forwardRef<HTMLDivElement, DateInputProps>(
183183
const isMobile = useMedia('(max-width: 479px)');
184184

185185
const fieldRef = useRef<HTMLDivElement>(null);
186-
const floatingRef = useRef<HTMLDialogElement>(null);
187-
const calendarRef = useRef<HTMLDivElement>(null);
186+
const dialogRef = useRef<HTMLDialogElement>(null);
188187

189188
const dialogId = useId();
190189
const headlineId = useId();
@@ -211,7 +210,7 @@ export const DateInput = forwardRef<HTMLDivElement, DateInputProps>(
211210
middleware: [offset(4), flip(), shift()],
212211
elements: {
213212
reference: fieldRef.current,
214-
floating: floatingRef.current,
213+
floating: dialogRef.current,
215214
},
216215
});
217216

@@ -281,9 +280,10 @@ export const DateInput = forwardRef<HTMLDivElement, DateInputProps>(
281280

282281
const mobileStyles = {
283282
position: 'fixed',
283+
top: 'auto',
284+
right: '0px',
284285
bottom: '0px',
285286
left: '0px',
286-
right: '0px',
287287
} as const;
288288

289289
const dialogStyles = isMobile ? mobileStyles : floatingStyles;
@@ -454,9 +454,10 @@ export const DateInput = forwardRef<HTMLDivElement, DateInputProps>(
454454
/>
455455
</FieldSet>
456456
<Dialog
457-
ref={floatingRef}
457+
ref={dialogRef}
458458
id={dialogId}
459459
open={open}
460+
isModal={isMobile}
460461
onClose={closeCalendar}
461462
aria-labelledby={headlineId}
462463
style={dialogStyles}
@@ -478,7 +479,6 @@ export const DateInput = forwardRef<HTMLDivElement, DateInputProps>(
478479
</header>
479480

480481
<Calendar
481-
ref={calendarRef}
482482
className={classes.calendar}
483483
onSelect={handleSelect}
484484
selection={selection}

packages/circuit-ui/components/DateInput/DateInputService.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
*/
1515

1616
import { describe, expect, it } from 'vitest';
17+
import { Temporal } from 'temporal-polyfill';
1718

18-
import { getDateSegments } from './DateInputService.js';
19+
import { getCalendarButtonLabel, getDateSegments } from './DateInputService.js';
1920

2021
describe('DateInputService', () => {
2122
describe('getDateSegments', () => {
@@ -43,4 +44,29 @@ describe('DateInputService', () => {
4344
expect(literalSegment?.value).toBe(literal);
4445
});
4546
});
47+
48+
describe('getCalendarButtonLabel', () => {
49+
const label = 'Change date';
50+
51+
it('should return the plain label if the date is undefined', () => {
52+
const date = undefined;
53+
const locale = undefined;
54+
const actual = getCalendarButtonLabel(label, date, locale);
55+
expect(actual).toBe(label);
56+
});
57+
58+
it('should postfix the formatted date to the label', () => {
59+
const date = new Temporal.PlainDate(2017, 8, 28);
60+
const locale = undefined;
61+
const actual = getCalendarButtonLabel(label, date, locale);
62+
expect(actual).toBe(`${label}, August 28, 2017`);
63+
});
64+
65+
it('should format the date for the locale', () => {
66+
const date = new Temporal.PlainDate(2017, 8, 28);
67+
const locale = 'fr-FR';
68+
const actual = getCalendarButtonLabel(label, date, locale);
69+
expect(actual).toBe(`${label}, 28 août 2017`);
70+
});
71+
});
4672
});

0 commit comments

Comments
 (0)