Skip to content

Commit da6e199

Browse files
dpitcockpan-kot
andauthored
chore: Updating tests to the masking to ensure date formats are covered (#3530)
Co-authored-by: Andrei Zhaleznichenka <[email protected]>
1 parent f3bdaa2 commit da6e199

File tree

4 files changed

+428
-136
lines changed

4 files changed

+428
-136
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { RefObject } from 'react';
5+
6+
import { warnOnce } from '@cloudscape-design/component-toolkit/internal';
7+
8+
import { renderHook } from '../../../../__tests__/render-hook';
9+
import { InputProps } from '../../../../input/interfaces';
10+
import { BaseKeyDetail } from '../../../events';
11+
import { KeyCode } from '../../../keycode';
12+
import useMask, { UseMaskProps } from '../use-mask';
13+
import MaskFormat from '../utils/mask-format';
14+
15+
jest.mock('@cloudscape-design/component-toolkit/internal', () => ({
16+
...jest.requireActual('@cloudscape-design/component-toolkit/internal'),
17+
warnOnce: jest.fn(),
18+
}));
19+
20+
afterEach(() => {
21+
(warnOnce as jest.Mock).mockReset();
22+
});
23+
24+
function createChangeEvent(value: string) {
25+
return { detail: { value } } as unknown as CustomEvent<InputProps.ChangeDetail>;
26+
}
27+
28+
function createKeyDownEvent(detail: Partial<BaseKeyDetail>) {
29+
return { detail, preventDefault: jest.fn() } as unknown as CustomEvent<InputProps.KeyDetail>;
30+
}
31+
32+
function createBlurEvent() {
33+
return null as unknown as CustomEvent<null>;
34+
}
35+
36+
function createClipboardEvent(value: string) {
37+
const clipboardData = {
38+
getData: jest.fn().mockReturnValue(value),
39+
};
40+
return { clipboardData } as unknown as React.ClipboardEvent;
41+
}
42+
43+
describe('useMask', () => {
44+
const setup = (props: Partial<UseMaskProps> = {}) => {
45+
// Create a simple time format (HH:MM) for testing
46+
const createTimeFormat = jest.fn().mockReturnValue(
47+
new MaskFormat({
48+
separator: ':',
49+
segments: [
50+
{ min: 0, max: 23, length: 2 },
51+
{ min: 0, max: 59, length: 2 },
52+
],
53+
})
54+
);
55+
const format = createTimeFormat();
56+
const onChange = jest.fn();
57+
const onKeyDown = jest.fn();
58+
const onBlur = jest.fn();
59+
const inputRef = { current: { selectionStart: 0, selectionEnd: 0 } } as RefObject<HTMLInputElement>;
60+
const setPosition = jest.fn();
61+
const { result } = renderHook(() =>
62+
useMask({ value: '', onChange, onKeyDown, onBlur, format, inputRef, setPosition, ...props })
63+
);
64+
return {
65+
result,
66+
format,
67+
inputRef,
68+
mockOnBlur: onBlur,
69+
mockOnChange: onChange,
70+
mockOnKeyDown: onKeyDown,
71+
mockSetPosition: setPosition,
72+
mockCreateTimeFormat: createTimeFormat,
73+
};
74+
};
75+
76+
describe('initialization', () => {
77+
it('should return the initial value', () => {
78+
const { result } = setup({ value: '12:34' });
79+
expect(result.current.value).toBe('12:34');
80+
});
81+
82+
it('should correct the initial value if autofix is true', () => {
83+
const { result, mockCreateTimeFormat } = setup({ value: '25:70', autofix: true });
84+
expect(result.current.value).toBe('23:59');
85+
expect(mockCreateTimeFormat).toHaveBeenCalled();
86+
});
87+
88+
it('should not correct the initial value if autofix is false', () => {
89+
const { result, mockCreateTimeFormat } = setup({ value: '25:70', autofix: false });
90+
expect(result.current.value).toBe('2');
91+
expect(mockCreateTimeFormat).toHaveBeenCalled();
92+
});
93+
94+
it('should warn if the initial value is invalid', () => {
95+
setup({ value: 'invalid' });
96+
expect(warnOnce).toHaveBeenCalledWith('useMask', 'Invalid string "invalid" provided');
97+
});
98+
99+
it('should return a valid value even if the initial value is invalid', () => {
100+
const { result } = setup({ value: 'invalid' });
101+
expect(result.current.value).toBe('');
102+
});
103+
});
104+
105+
describe('onChange handler', () => {
106+
it('should call onChange with the updated value', () => {
107+
const { result, mockOnChange } = setup();
108+
result.current.onChange(createChangeEvent('12:34'));
109+
expect(mockOnChange).toHaveBeenCalledWith('12:34');
110+
});
111+
112+
it('should not call onChange if the value is invalid', () => {
113+
const { result, mockOnChange } = setup();
114+
result.current.onChange(createChangeEvent('invalid'));
115+
expect(mockOnChange).not.toHaveBeenCalled();
116+
});
117+
118+
it('should not call onChange if the value is the same', () => {
119+
const { result, mockOnChange } = setup({ value: '12:34' });
120+
result.current.onChange(createChangeEvent('12:34'));
121+
expect(mockOnChange).not.toHaveBeenCalled();
122+
});
123+
124+
it('should autofix the value if autofix is true', () => {
125+
const { result, mockOnChange } = setup({ autofix: true });
126+
result.current.onChange(createChangeEvent('25:70'));
127+
expect(mockOnChange).toHaveBeenCalledWith('23:59');
128+
});
129+
130+
it('should not call onChange if the value is out of bounds and autofix is false', () => {
131+
const { result, mockOnChange } = setup({ autofix: false });
132+
result.current.onChange(createChangeEvent('25:70'));
133+
expect(mockOnChange).not.toHaveBeenCalled();
134+
});
135+
});
136+
137+
describe('onKeyDown handler', () => {
138+
it('should handle digit keys', () => {
139+
const { result, inputRef, mockOnChange, mockSetPosition } = setup();
140+
inputRef.current!.selectionStart = 0;
141+
inputRef.current!.selectionEnd = 0;
142+
result.current.onKeyDown(createKeyDownEvent({ key: '1', keyCode: 49, ctrlKey: false, metaKey: false }));
143+
expect(mockOnChange).toHaveBeenCalledWith('1');
144+
expect(mockSetPosition).toHaveBeenCalledWith(1);
145+
});
146+
147+
it('should handle backspace key', () => {
148+
const { result, mockOnChange, inputRef, mockSetPosition } = setup({ value: '12:34' });
149+
inputRef.current!.selectionStart = 3;
150+
inputRef.current!.selectionEnd = 3;
151+
result.current.onKeyDown(createKeyDownEvent({ keyCode: KeyCode.backspace, ctrlKey: false, metaKey: false }));
152+
expect(mockOnChange).toHaveBeenCalled();
153+
expect(mockSetPosition).toHaveBeenCalled();
154+
});
155+
156+
it('should handle enter key for auto-completion', () => {
157+
const { result, mockOnChange, inputRef, mockSetPosition } = setup({ value: '12:3' });
158+
inputRef.current!.selectionStart = 4;
159+
inputRef.current!.selectionEnd = 4;
160+
result.current.onKeyDown(createKeyDownEvent({ keyCode: KeyCode.enter, ctrlKey: false, metaKey: false }));
161+
expect(mockOnChange).toHaveBeenCalledWith('12:03');
162+
expect(mockSetPosition).toHaveBeenCalledWith(5);
163+
});
164+
165+
it('should prevent default for non-command keys', () => {
166+
const { result } = setup();
167+
const event = createKeyDownEvent({ key: 'a', keyCode: 65, ctrlKey: false, metaKey: false });
168+
result.current.onKeyDown(event);
169+
expect(event.preventDefault).toHaveBeenCalled();
170+
});
171+
172+
it('should not prevent default for command keys', () => {
173+
const { result } = setup();
174+
const event = createKeyDownEvent({ keyCode: KeyCode.tab, ctrlKey: false, metaKey: false });
175+
result.current.onKeyDown(event);
176+
expect(event.preventDefault).not.toHaveBeenCalled();
177+
});
178+
179+
it('should call the original onKeyDown handler', () => {
180+
const { result, mockOnKeyDown } = setup();
181+
const event = createKeyDownEvent({ key: '1', keyCode: 49, ctrlKey: false, metaKey: false });
182+
result.current.onKeyDown(event);
183+
expect(mockOnKeyDown).toHaveBeenCalledWith(event);
184+
});
185+
});
186+
187+
describe('onBlur handler', () => {
188+
it('should auto-complete the value on blur', () => {
189+
const { result, mockOnChange, mockOnBlur } = setup({ value: '12:3' });
190+
result.current.onBlur(createBlurEvent());
191+
expect(mockOnChange).toHaveBeenCalledWith('12:03');
192+
expect(mockOnBlur).toHaveBeenCalled();
193+
});
194+
195+
it('should not auto-complete if disableAutocompleteOnBlur is true', () => {
196+
const { result, mockOnChange, mockOnBlur } = setup({ value: '12:3', disableAutocompleteOnBlur: true });
197+
result.current.onBlur(createBlurEvent());
198+
expect(mockOnChange).not.toHaveBeenCalled();
199+
expect(mockOnBlur).toHaveBeenCalled();
200+
});
201+
202+
it('should not auto-complete if the value is empty', () => {
203+
const { result, mockOnChange } = setup({ value: '' });
204+
result.current.onBlur(createBlurEvent());
205+
expect(mockOnChange).not.toHaveBeenCalled();
206+
});
207+
});
208+
209+
describe('onPaste handler', () => {
210+
it('should handle pasted text', () => {
211+
const { result, mockOnChange } = setup();
212+
result.current.onPaste(createClipboardEvent('1234'));
213+
expect(mockOnChange).toHaveBeenCalledWith('12:34');
214+
});
215+
216+
it('should handle pasted text with selection range', () => {
217+
const { result, mockOnChange, inputRef } = setup({ value: '12:34' });
218+
inputRef.current!.selectionStart = 0;
219+
inputRef.current!.selectionEnd = 5;
220+
result.current.onPaste(createClipboardEvent('0959'));
221+
expect(mockOnChange).toHaveBeenCalledWith('09:59');
222+
});
223+
224+
it('should handle pasted text with separators', () => {
225+
const { result, mockOnChange } = setup();
226+
result.current.onPaste(createClipboardEvent('12:34'));
227+
expect(mockOnChange).toHaveBeenCalledWith('12:34');
228+
});
229+
});
230+
});

0 commit comments

Comments
 (0)