Skip to content

Commit b919521

Browse files
authored
test(experience): add unit tests for DateField component (#7690)
1 parent 4617fe2 commit b919521

File tree

2 files changed

+205
-1
lines changed

2 files changed

+205
-1
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { SupportedDateFormat } from '@logto/schemas';
2+
import { render, fireEvent, act } from '@testing-library/react';
3+
import { useState } from 'react';
4+
5+
import DateField from '.';
6+
7+
// Mock i18n hook (only label optional usage); return label directly
8+
jest.mock('react-i18next', () => ({
9+
useTranslation: () => ({
10+
t: (key: string, options?: Record<string, string>) => options?.label ?? key,
11+
i18n: { dir: () => 'ltr' },
12+
}),
13+
}));
14+
15+
// Helper controlled wrapper so DateField reflects user input value updates
16+
const Controlled = (props: React.ComponentProps<typeof DateField>) => {
17+
const [value, setValue] = useState(props.value ?? '');
18+
return (
19+
<DateField
20+
{...props}
21+
value={value}
22+
onChange={(newValue) => {
23+
setValue(newValue);
24+
props.onChange?.(newValue);
25+
}}
26+
/>
27+
);
28+
};
29+
30+
describe('DateField Component', () => {
31+
test('render US format placeholders and separator', () => {
32+
const { container } = render(<DateField dateFormat={SupportedDateFormat.US} label="dob" />);
33+
34+
// Focus wrapper to activate inputs
35+
const wrapper = container.querySelector('[role="button"]');
36+
expect(wrapper).not.toBeNull();
37+
if (wrapper) {
38+
act(() => {
39+
fireEvent.click(wrapper);
40+
});
41+
}
42+
43+
const inputs = Array.from(container.querySelectorAll('input'));
44+
expect(inputs).toHaveLength(3);
45+
expect(inputs[0]?.getAttribute('placeholder')).toBe('MM');
46+
expect(inputs[1]?.getAttribute('placeholder')).toBe('DD');
47+
expect(inputs[2]?.getAttribute('placeholder')).toBe('YYYY');
48+
49+
const separators = container.querySelectorAll('span');
50+
51+
expect(
52+
Array.from(separators).filter((spanElement) => spanElement.textContent === '/')
53+
).toHaveLength(2);
54+
});
55+
56+
test('typing fills each part and produces final date (controlled, 2023-08-20)', () => {
57+
const handleChange = jest.fn();
58+
const { container } = render(
59+
<Controlled dateFormat={SupportedDateFormat.US} onChange={handleChange} />
60+
);
61+
const wrapper = container.querySelector('[role="button"]');
62+
if (wrapper) {
63+
act(() => {
64+
fireEvent.click(wrapper);
65+
});
66+
}
67+
const inputs = Array.from(container.querySelectorAll('input'));
68+
69+
act(() => {
70+
fireEvent.input(inputs[0]!, { target: { value: '08' } });
71+
fireEvent.input(inputs[1]!, { target: { value: '20' } });
72+
fireEvent.input(inputs[2]!, { target: { value: '2023' } });
73+
});
74+
75+
expect(handleChange).toHaveBeenLastCalledWith('08/20/2023');
76+
// Also ensure DOM reflects the value
77+
expect(inputs[2]!.value).toBe('2023');
78+
});
79+
80+
test('backspace clears previous field when empty', () => {
81+
const handleChange = jest.fn();
82+
const { container } = render(
83+
<Controlled dateFormat={SupportedDateFormat.US} value="08/20/2023" onChange={handleChange} />
84+
);
85+
const wrapper = container.querySelector('[role="button"]');
86+
if (wrapper) {
87+
act(() => {
88+
fireEvent.click(wrapper);
89+
});
90+
}
91+
const inputs = Array.from(container.querySelectorAll('input'));
92+
93+
// Focus last input and clear it with backspace until it moves to previous
94+
act(() => {
95+
inputs[2]!.focus();
96+
fireEvent.keyDown(inputs[2]!, { key: 'Backspace' }); // Clears year
97+
});
98+
expect(handleChange).toHaveBeenLastCalledWith('08/20/');
99+
100+
// Now last input is empty, backspace again should move to previous and clear day
101+
act(() => {
102+
fireEvent.keyDown(inputs[2]!, { key: 'Backspace' });
103+
});
104+
expect(handleChange).toHaveBeenLastCalledWith('08//');
105+
106+
// Finally clear the month field so that all parts empty => '' (not '//')
107+
act(() => {
108+
inputs[0]!.focus();
109+
fireEvent.keyDown(inputs[0]!, { key: 'Backspace' });
110+
});
111+
expect(handleChange).toHaveBeenLastCalledWith('');
112+
const emittedValuesAfterFullClear = handleChange.mock.calls.map((call) => call[0]);
113+
expect(emittedValuesAfterFullClear.includes('//')).toBe(false);
114+
});
115+
116+
test('paste distributes digits across inputs', () => {
117+
const handleChange = jest.fn();
118+
const { container } = render(
119+
<DateField dateFormat={SupportedDateFormat.US} onChange={handleChange} />
120+
);
121+
const wrapper = container.querySelector('[role="button"]');
122+
if (wrapper) {
123+
act(() => {
124+
fireEvent.click(wrapper);
125+
});
126+
}
127+
const inputs = Array.from(container.querySelectorAll('input'));
128+
129+
act(() => {
130+
fireEvent.paste(inputs[0]!, {
131+
clipboardData: {
132+
getData: () => '08202023',
133+
},
134+
} as unknown as ClipboardEvent);
135+
});
136+
137+
// Should fill: 08 / 20 / 2023
138+
expect(handleChange).toHaveBeenLastCalledWith('08/20/2023');
139+
});
140+
141+
test('fallback to simple input when unsupported format provided', () => {
142+
const handleChange = jest.fn();
143+
const { container } = render(<Controlled dateFormat="dd.MM.yyyy" onChange={handleChange} />);
144+
// Should render single input (no role button container present)
145+
expect(container.querySelectorAll('input').length).toBe(1);
146+
const input = container.querySelector('input');
147+
if (input) {
148+
act(() => {
149+
fireEvent.change(input, { target: { value: '2023-08-20' } });
150+
});
151+
}
152+
expect(handleChange).toHaveBeenCalledWith('2023-08-20');
153+
});
154+
155+
test('UK format placeholders and value assembly', () => {
156+
const handleChange = jest.fn();
157+
const { container } = render(
158+
<Controlled dateFormat={SupportedDateFormat.UK} onChange={handleChange} />
159+
);
160+
const wrapper = container.querySelector('[role="button"]');
161+
if (wrapper) {
162+
act(() => {
163+
fireEvent.click(wrapper);
164+
});
165+
}
166+
const inputs = Array.from(container.querySelectorAll('input'));
167+
expect(inputs[0]?.getAttribute('placeholder')).toBe('DD');
168+
expect(inputs[1]?.getAttribute('placeholder')).toBe('MM');
169+
expect(inputs[2]?.getAttribute('placeholder')).toBe('YYYY');
170+
act(() => {
171+
fireEvent.input(inputs[0]!, { target: { value: '20' } });
172+
fireEvent.input(inputs[1]!, { target: { value: '08' } });
173+
fireEvent.input(inputs[2]!, { target: { value: '2023' } });
174+
});
175+
expect(handleChange).toHaveBeenLastCalledWith('20/08/2023');
176+
});
177+
178+
test('ISO format placeholders, separator and value assembly', () => {
179+
const handleChange = jest.fn();
180+
const { container } = render(
181+
<Controlled dateFormat={SupportedDateFormat.ISO} onChange={handleChange} />
182+
);
183+
const wrapper = container.querySelector('[role="button"]');
184+
if (wrapper) {
185+
act(() => {
186+
fireEvent.click(wrapper);
187+
});
188+
}
189+
const inputs = Array.from(container.querySelectorAll('input'));
190+
expect(inputs[0]?.getAttribute('placeholder')).toBe('YYYY');
191+
expect(inputs[1]?.getAttribute('placeholder')).toBe('MM');
192+
expect(inputs[2]?.getAttribute('placeholder')).toBe('DD');
193+
// Check separator is '-'
194+
const separators = Array.from(container.querySelectorAll('span')).filter(
195+
(spanElement) => spanElement.textContent === '-'
196+
);
197+
expect(separators).toHaveLength(2);
198+
act(() => {
199+
fireEvent.input(inputs[0]!, { target: { value: '2023' } });
200+
fireEvent.input(inputs[1]!, { target: { value: '08' } });
201+
fireEvent.input(inputs[2]!, { target: { value: '20' } });
202+
});
203+
expect(handleChange).toHaveBeenLastCalledWith('2023-08-20');
204+
});
205+
});

packages/experience/src/components/InputFields/DateField/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ const DateField = (props: Props) => {
255255

256256
if (!isSupportedDateFormat) {
257257
const { dateFormat, ...restProps } = props;
258-
console.log(restProps);
259258
return (
260259
<InputField
261260
{...restProps}

0 commit comments

Comments
 (0)