@pie-lib/config-ui 12.0.0-beta.5 → 12.0.0-next.0
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.
- package/CHANGELOG.json +8 -1653
- package/CHANGELOG.md +345 -4
- package/LICENSE.md +5 -0
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/alert-dialog.js +40 -10
- package/lib/alert-dialog.js.map +1 -1
- package/lib/checkbox.js +58 -48
- package/lib/checkbox.js.map +1 -1
- package/lib/choice-configuration/feedback-menu.js +24 -26
- package/lib/choice-configuration/feedback-menu.js.map +1 -1
- package/lib/choice-configuration/index.js +182 -185
- package/lib/choice-configuration/index.js.map +1 -1
- package/lib/choice-utils.js +5 -7
- package/lib/choice-utils.js.map +1 -1
- package/lib/feedback-config/feedback-selector.js +69 -73
- package/lib/feedback-config/feedback-selector.js.map +1 -1
- package/lib/feedback-config/group.js +22 -25
- package/lib/feedback-config/group.js.map +1 -1
- package/lib/feedback-config/index.js +41 -44
- package/lib/feedback-config/index.js.map +1 -1
- package/lib/form-section.js +31 -25
- package/lib/form-section.js.map +1 -1
- package/lib/help.js +37 -47
- package/lib/help.js.map +1 -1
- package/lib/index.js +1 -2
- package/lib/index.js.map +1 -1
- package/lib/input.js +12 -17
- package/lib/input.js.map +1 -1
- package/lib/inputs.js +58 -67
- package/lib/inputs.js.map +1 -1
- package/lib/langs.js +56 -70
- package/lib/langs.js.map +1 -1
- package/lib/layout/config-layout.js +78 -47
- package/lib/layout/config-layout.js.map +1 -1
- package/lib/layout/index.js.map +1 -1
- package/lib/layout/layout-contents.js +58 -60
- package/lib/layout/layout-contents.js.map +1 -1
- package/lib/layout/settings-box.js +25 -33
- package/lib/layout/settings-box.js.map +1 -1
- package/lib/mui-box/index.js +41 -50
- package/lib/mui-box/index.js.map +1 -1
- package/lib/number-text-field-custom.js +151 -89
- package/lib/number-text-field-custom.js.map +1 -1
- package/lib/number-text-field.js +74 -63
- package/lib/number-text-field.js.map +1 -1
- package/lib/radio-with-label.js +30 -16
- package/lib/radio-with-label.js.map +1 -1
- package/lib/settings/display-size.js +16 -20
- package/lib/settings/display-size.js.map +1 -1
- package/lib/settings/index.js +13 -19
- package/lib/settings/index.js.map +1 -1
- package/lib/settings/panel.js +140 -141
- package/lib/settings/panel.js.map +1 -1
- package/lib/settings/settings-radio-label.js +29 -16
- package/lib/settings/settings-radio-label.js.map +1 -1
- package/lib/settings/toggle.js +39 -25
- package/lib/settings/toggle.js.map +1 -1
- package/lib/tabs/index.js +18 -30
- package/lib/tabs/index.js.map +1 -1
- package/lib/tags-input/index.js +49 -61
- package/lib/tags-input/index.js.map +1 -1
- package/lib/two-choice.js +33 -43
- package/lib/two-choice.js.map +1 -1
- package/lib/with-stateful-model.js +8 -12
- package/lib/with-stateful-model.js.map +1 -1
- package/package.json +22 -11
- package/src/__tests__/alert-dialog.test.jsx +283 -0
- package/src/__tests__/checkbox.test.jsx +249 -0
- package/src/__tests__/choice-utils.test.js +12 -0
- package/src/__tests__/form-section.test.jsx +334 -0
- package/src/__tests__/help.test.jsx +184 -0
- package/src/__tests__/input.test.jsx +192 -0
- package/src/__tests__/langs.test.jsx +457 -0
- package/src/__tests__/number-text-field-custom.test.jsx +438 -0
- package/src/__tests__/number-text-field.test.jsx +341 -0
- package/src/__tests__/radio-with-label.test.jsx +259 -0
- package/src/__tests__/settings-panel.test.js +187 -0
- package/src/__tests__/settings.test.jsx +515 -0
- package/src/__tests__/tabs.test.jsx +193 -0
- package/src/__tests__/two-choice.test.js +110 -0
- package/src/__tests__/with-stateful-model.test.jsx +145 -0
- package/src/alert-dialog.jsx +30 -8
- package/src/checkbox.jsx +43 -37
- package/src/choice-configuration/__tests__/feedback-menu.test.jsx +163 -0
- package/src/choice-configuration/__tests__/index.test.jsx +234 -0
- package/src/choice-configuration/feedback-menu.jsx +6 -6
- package/src/choice-configuration/index.jsx +208 -192
- package/src/feedback-config/__tests__/feedback-config.test.jsx +141 -0
- package/src/feedback-config/__tests__/feedback-selector.test.jsx +107 -0
- package/src/feedback-config/feedback-selector.jsx +52 -53
- package/src/feedback-config/group.jsx +21 -22
- package/src/feedback-config/index.jsx +27 -29
- package/src/form-section.jsx +26 -18
- package/src/help.jsx +20 -28
- package/src/input.jsx +1 -1
- package/src/inputs.jsx +35 -44
- package/src/langs.jsx +41 -46
- package/src/layout/__tests__/config.layout.test.jsx +59 -0
- package/src/layout/__tests__/layout-content.test.jsx +3 -0
- package/src/layout/config-layout.jsx +53 -23
- package/src/layout/layout-contents.jsx +38 -40
- package/src/layout/settings-box.jsx +16 -19
- package/src/mui-box/index.jsx +35 -43
- package/src/number-text-field-custom.jsx +117 -65
- package/src/number-text-field.jsx +51 -34
- package/src/radio-with-label.jsx +26 -10
- package/src/settings/display-size.jsx +12 -11
- package/src/settings/index.js +2 -1
- package/src/settings/panel.jsx +101 -92
- package/src/settings/settings-radio-label.jsx +26 -10
- package/src/settings/toggle.jsx +37 -18
- package/src/tabs/index.jsx +8 -8
- package/src/tags-input/__tests__/index.test.jsx +113 -0
- package/src/tags-input/index.jsx +35 -38
- package/src/two-choice.jsx +15 -19
- package/README.md +0 -12
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import Input from '../input';
|
|
5
|
+
|
|
6
|
+
describe('Input Component', () => {
|
|
7
|
+
const onChange = jest.fn();
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
onChange.mockClear();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('Rendering', () => {
|
|
14
|
+
it('should render input without label', () => {
|
|
15
|
+
const { container } = render(
|
|
16
|
+
<Input type="text" value="test" onChange={onChange} />,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const input = container.querySelector('input');
|
|
20
|
+
expect(input).toBeInTheDocument();
|
|
21
|
+
expect(input.value).toBe('test');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should render input with label', () => {
|
|
25
|
+
render(
|
|
26
|
+
<Input type="text" label="Email" value="test@example.com" onChange={onChange} />,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(screen.getByText('Email')).toBeInTheDocument();
|
|
30
|
+
const input = screen.getByDisplayValue('test@example.com');
|
|
31
|
+
expect(input).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should render input with different types', () => {
|
|
35
|
+
const { container: numberContainer } = render(
|
|
36
|
+
<Input type="number" value={123} onChange={onChange} />,
|
|
37
|
+
);
|
|
38
|
+
expect(numberContainer.querySelector('input[type="number"]')).toBeInTheDocument();
|
|
39
|
+
|
|
40
|
+
const { container: emailContainer } = render(
|
|
41
|
+
<Input type="email" value="test@example.com" onChange={onChange} />,
|
|
42
|
+
);
|
|
43
|
+
expect(emailContainer.querySelector('input[type="email"]')).toBeInTheDocument();
|
|
44
|
+
|
|
45
|
+
const { container: passwordContainer } = render(
|
|
46
|
+
<Input type="password" value="secret" onChange={onChange} />,
|
|
47
|
+
);
|
|
48
|
+
expect(passwordContainer.querySelector('input[type="password"]')).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Value handling', () => {
|
|
53
|
+
it('should handle string values', () => {
|
|
54
|
+
const { container } = render(
|
|
55
|
+
<Input type="text" value="hello" onChange={onChange} />,
|
|
56
|
+
);
|
|
57
|
+
expect(container.querySelector('input').value).toBe('hello');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle number values', () => {
|
|
61
|
+
const { container } = render(
|
|
62
|
+
<Input type="number" value={42} onChange={onChange} />,
|
|
63
|
+
);
|
|
64
|
+
expect(container.querySelector('input').value).toBe('42');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle empty values', () => {
|
|
68
|
+
const { container } = render(
|
|
69
|
+
<Input type="text" value="" onChange={onChange} />,
|
|
70
|
+
);
|
|
71
|
+
expect(container.querySelector('input').value).toBe('');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should update when prop changes', () => {
|
|
75
|
+
const { container, rerender } = render(
|
|
76
|
+
<Input type="text" value="initial" onChange={onChange} />,
|
|
77
|
+
);
|
|
78
|
+
expect(container.querySelector('input').value).toBe('initial');
|
|
79
|
+
|
|
80
|
+
rerender(<Input type="text" value="updated" onChange={onChange} />);
|
|
81
|
+
expect(container.querySelector('input').value).toBe('updated');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('onChange behavior', () => {
|
|
86
|
+
it('should call onChange on input change', async () => {
|
|
87
|
+
const user = userEvent.setup();
|
|
88
|
+
const { container } = render(
|
|
89
|
+
<Input type="text" value="" onChange={onChange} />,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const input = container.querySelector('input');
|
|
93
|
+
await user.type(input, 'hello');
|
|
94
|
+
|
|
95
|
+
expect(onChange).toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle custom error function', async () => {
|
|
99
|
+
const user = userEvent.setup();
|
|
100
|
+
const customError = jest.fn((value) => value.length < 3);
|
|
101
|
+
|
|
102
|
+
const { container } = render(
|
|
103
|
+
<Input type="text" value="" onChange={onChange} error={customError} />,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const input = container.querySelector('input');
|
|
107
|
+
await user.type(input, 'ab');
|
|
108
|
+
|
|
109
|
+
expect(customError).toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Error state', () => {
|
|
114
|
+
it('should display error state when validation fails', async () => {
|
|
115
|
+
const user = userEvent.setup();
|
|
116
|
+
const customError = jest.fn(() => true);
|
|
117
|
+
|
|
118
|
+
const { container } = render(
|
|
119
|
+
<Input type="text" value="" onChange={onChange} error={customError} />,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const input = container.querySelector('input');
|
|
123
|
+
await user.type(input, 'test');
|
|
124
|
+
|
|
125
|
+
expect(customError).toHaveBeenCalled();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should clear error state when validation passes', async () => {
|
|
129
|
+
const user = userEvent.setup();
|
|
130
|
+
const customError = jest.fn((value) => !value);
|
|
131
|
+
|
|
132
|
+
const { container } = render(
|
|
133
|
+
<Input type="text" value="" onChange={onChange} error={customError} />,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const input = container.querySelector('input');
|
|
137
|
+
await user.type(input, 'test');
|
|
138
|
+
|
|
139
|
+
// After typing valid content, error should be false
|
|
140
|
+
expect(customError).toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('Props spreading', () => {
|
|
145
|
+
it('should handle disabled state', () => {
|
|
146
|
+
const { container } = render(
|
|
147
|
+
<Input type="text" value="" onChange={onChange} disabled />,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const input = container.querySelector('input');
|
|
151
|
+
expect(input).toBeDisabled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle readonly state', () => {
|
|
155
|
+
const { container } = render(
|
|
156
|
+
<Input type="text" value="readonly" onChange={onChange} readOnly />,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const input = container.querySelector('input');
|
|
160
|
+
expect(input).toHaveAttribute('readonly');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('Default props', () => {
|
|
165
|
+
it('should use default type of text', () => {
|
|
166
|
+
const { container } = render(
|
|
167
|
+
<Input value="" onChange={onChange} />,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const input = container.querySelector('input');
|
|
171
|
+
expect(input.type).toBe('text');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should use default error function that validates number type', () => {
|
|
175
|
+
const { container } = render(
|
|
176
|
+
<Input type="number" value="" onChange={onChange} />,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const input = container.querySelector('input');
|
|
180
|
+
expect(input).toBeInTheDocument();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should have noModelUpdateOnError default to false', () => {
|
|
184
|
+
const { container } = render(
|
|
185
|
+
<Input type="text" value="" onChange={onChange} />,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const input = container.querySelector('input');
|
|
189
|
+
expect(input).toBeInTheDocument();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Langs, LanguageControls } from '../langs';
|
|
5
|
+
|
|
6
|
+
describe('Langs Component', () => {
|
|
7
|
+
let onChange;
|
|
8
|
+
const renderComponent = (extras = {}) => {
|
|
9
|
+
const defaults = {
|
|
10
|
+
uid: '1',
|
|
11
|
+
onChange,
|
|
12
|
+
langs: ['en-US', 'es-ES'],
|
|
13
|
+
selected: 'en-US',
|
|
14
|
+
};
|
|
15
|
+
const props = { ...defaults, ...extras };
|
|
16
|
+
return render(<Langs {...props} />);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
onChange = jest.fn();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('rendering', () => {
|
|
24
|
+
it('renders language selector with options', () => {
|
|
25
|
+
renderComponent();
|
|
26
|
+
|
|
27
|
+
// Select should be present
|
|
28
|
+
const select = screen.getByRole('combobox');
|
|
29
|
+
expect(select).toBeInTheDocument();
|
|
30
|
+
|
|
31
|
+
// Should show selected value - MUI Select displays value as text content
|
|
32
|
+
expect(select).toHaveTextContent('en-US');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders with custom label', () => {
|
|
36
|
+
renderComponent({ label: 'Choose Language' });
|
|
37
|
+
expect(screen.getByText('Choose Language')).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('renders all language options', () => {
|
|
41
|
+
const { container } = renderComponent({
|
|
42
|
+
langs: ['en-US', 'es-ES', 'fr-FR', 'de-DE'],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const select = screen.getByRole('combobox');
|
|
46
|
+
expect(select).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders with multiple languages', () => {
|
|
50
|
+
const langs = ['en-US', 'es-ES', 'fr-FR', 'pt-BR'];
|
|
51
|
+
renderComponent({ langs });
|
|
52
|
+
|
|
53
|
+
const select = screen.getByRole('combobox');
|
|
54
|
+
expect(select).toHaveTextContent('en-US');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('renders FormControl with proper structure', () => {
|
|
58
|
+
const { container } = renderComponent();
|
|
59
|
+
|
|
60
|
+
expect(container.querySelector('[class*="MuiFormControl"]')).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('user interactions', () => {
|
|
65
|
+
it('calls onChange when user selects a language', async () => {
|
|
66
|
+
const user = userEvent.setup();
|
|
67
|
+
renderComponent();
|
|
68
|
+
|
|
69
|
+
const select = screen.getByRole('combobox');
|
|
70
|
+
|
|
71
|
+
// Open the select and choose es-ES
|
|
72
|
+
await user.click(select);
|
|
73
|
+
await user.click(screen.getByRole('option', { name: 'es-ES' }));
|
|
74
|
+
|
|
75
|
+
expect(onChange).toHaveBeenCalledWith('es-ES');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('calls onChange with correct value', async () => {
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
renderComponent({ selected: 'es-ES' });
|
|
81
|
+
|
|
82
|
+
const select = screen.getByRole('combobox');
|
|
83
|
+
|
|
84
|
+
await user.click(select);
|
|
85
|
+
await user.click(screen.getByRole('option', { name: 'en-US' }));
|
|
86
|
+
|
|
87
|
+
expect(onChange).toHaveBeenCalledWith('en-US');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('opens dropdown on click', async () => {
|
|
91
|
+
const user = userEvent.setup();
|
|
92
|
+
renderComponent({
|
|
93
|
+
langs: ['en-US', 'es-ES', 'fr-FR'],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const select = screen.getByRole('combobox');
|
|
97
|
+
await user.click(select);
|
|
98
|
+
|
|
99
|
+
expect(screen.getByRole('option', { name: 'en-US' })).toBeInTheDocument();
|
|
100
|
+
expect(screen.getByRole('option', { name: 'es-ES' })).toBeInTheDocument();
|
|
101
|
+
expect(screen.getByRole('option', { name: 'fr-FR' })).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('does not call onChange if no onChange prop provided', async () => {
|
|
105
|
+
const user = userEvent.setup();
|
|
106
|
+
const { container } = render(
|
|
107
|
+
<Langs
|
|
108
|
+
uid="1"
|
|
109
|
+
langs={['en-US', 'es-ES']}
|
|
110
|
+
selected="en-US"
|
|
111
|
+
/>,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const select = screen.getByRole('combobox');
|
|
115
|
+
await user.click(select);
|
|
116
|
+
await user.click(screen.getByRole('option', { name: 'es-ES' }));
|
|
117
|
+
|
|
118
|
+
// Should not throw any error
|
|
119
|
+
expect(select).toBeInTheDocument();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('props handling', () => {
|
|
124
|
+
it('accepts custom uid', () => {
|
|
125
|
+
const customUid = 'custom-uid-123';
|
|
126
|
+
const { container } = renderComponent({ uid: customUid });
|
|
127
|
+
|
|
128
|
+
const input = container.querySelector(`#${customUid}`);
|
|
129
|
+
expect(input).toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('generates random uid if not provided', () => {
|
|
133
|
+
const { container } = render(
|
|
134
|
+
<Langs
|
|
135
|
+
onChange={onChange}
|
|
136
|
+
langs={['en-US', 'es-ES']}
|
|
137
|
+
selected="en-US"
|
|
138
|
+
/>,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const select = screen.getByRole('combobox');
|
|
142
|
+
expect(select).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('handles empty label', () => {
|
|
146
|
+
renderComponent({ label: '' });
|
|
147
|
+
|
|
148
|
+
const select = screen.getByRole('combobox');
|
|
149
|
+
expect(select).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('handles very long label', () => {
|
|
153
|
+
const longLabel = 'Choose the language you would like to use for editing this content';
|
|
154
|
+
renderComponent({ label: longLabel });
|
|
155
|
+
|
|
156
|
+
expect(screen.getByText(longLabel)).toBeInTheDocument();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('value updates', () => {
|
|
161
|
+
it('updates selected value when prop changes', () => {
|
|
162
|
+
const { rerender } = renderComponent({ selected: 'en-US' });
|
|
163
|
+
|
|
164
|
+
let select = screen.getByRole('combobox');
|
|
165
|
+
expect(select).toHaveTextContent('en-US');
|
|
166
|
+
|
|
167
|
+
rerender(
|
|
168
|
+
<Langs
|
|
169
|
+
uid="1"
|
|
170
|
+
onChange={onChange}
|
|
171
|
+
langs={['en-US', 'es-ES']}
|
|
172
|
+
selected="es-ES"
|
|
173
|
+
/>,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
select = screen.getByRole('combobox');
|
|
177
|
+
expect(select).toHaveTextContent('es-ES');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('maintains state when language options change', async () => {
|
|
181
|
+
const user = userEvent.setup();
|
|
182
|
+
const { rerender } = renderComponent({
|
|
183
|
+
langs: ['en-US', 'es-ES'],
|
|
184
|
+
selected: 'en-US',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
rerender(
|
|
188
|
+
<Langs
|
|
189
|
+
uid="1"
|
|
190
|
+
onChange={onChange}
|
|
191
|
+
langs={['en-US', 'es-ES', 'fr-FR']}
|
|
192
|
+
selected="en-US"
|
|
193
|
+
/>,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const select = screen.getByRole('combobox');
|
|
197
|
+
expect(select).toHaveTextContent('en-US');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('keyboard navigation', () => {
|
|
202
|
+
it('opens dropdown with keyboard', async () => {
|
|
203
|
+
const user = userEvent.setup();
|
|
204
|
+
renderComponent({
|
|
205
|
+
langs: ['en-US', 'es-ES'],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const select = screen.getByRole('combobox');
|
|
209
|
+
select.focus();
|
|
210
|
+
|
|
211
|
+
await user.keyboard(' ');
|
|
212
|
+
|
|
213
|
+
expect(screen.getByRole('option', { name: 'en-US' })).toBeInTheDocument();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('accessibility', () => {
|
|
218
|
+
it('has proper label association', () => {
|
|
219
|
+
renderComponent({ label: 'Select Language' });
|
|
220
|
+
|
|
221
|
+
const select = screen.getByRole('combobox');
|
|
222
|
+
expect(select).toBeInTheDocument();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('renders options with proper roles', async () => {
|
|
226
|
+
const user = userEvent.setup();
|
|
227
|
+
renderComponent({
|
|
228
|
+
langs: ['en-US', 'es-ES', 'fr-FR'],
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const select = screen.getByRole('combobox');
|
|
232
|
+
await user.click(select);
|
|
233
|
+
|
|
234
|
+
const options = screen.getAllByRole('option');
|
|
235
|
+
expect(options.length).toBe(3);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('LanguageControls Component', () => {
|
|
241
|
+
const defaultProps = {
|
|
242
|
+
langs: ['en-US', 'es-ES', 'fr-FR'],
|
|
243
|
+
activeLang: 'en-US',
|
|
244
|
+
defaultLang: 'en-US',
|
|
245
|
+
onActiveLangChange: jest.fn(),
|
|
246
|
+
onDefaultLangChange: jest.fn(),
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
beforeEach(() => {
|
|
250
|
+
jest.clearAllMocks();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('rendering', () => {
|
|
254
|
+
it('renders both language selectors', () => {
|
|
255
|
+
render(<LanguageControls {...defaultProps} />);
|
|
256
|
+
|
|
257
|
+
expect(screen.getByText('Choose language to edit')).toBeInTheDocument();
|
|
258
|
+
expect(screen.getByText('Default language')).toBeInTheDocument();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('renders with correct initial values', () => {
|
|
262
|
+
render(<LanguageControls {...defaultProps} />);
|
|
263
|
+
|
|
264
|
+
const selects = screen.getAllByRole('combobox');
|
|
265
|
+
expect(selects).toHaveLength(2);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('applies custom className', () => {
|
|
269
|
+
const { container } = render(
|
|
270
|
+
<LanguageControls {...defaultProps} className="custom-class" />,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
expect(container.querySelector('.custom-class')).toBeInTheDocument();
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('user interactions', () => {
|
|
278
|
+
it('calls onActiveLangChange when active language is changed', async () => {
|
|
279
|
+
const user = userEvent.setup();
|
|
280
|
+
const onActiveLangChange = jest.fn();
|
|
281
|
+
|
|
282
|
+
render(
|
|
283
|
+
<LanguageControls
|
|
284
|
+
{...defaultProps}
|
|
285
|
+
activeLang="en-US"
|
|
286
|
+
onActiveLangChange={onActiveLangChange}
|
|
287
|
+
/>,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const selects = screen.getAllByRole('combobox');
|
|
291
|
+
const activeSelect = selects[0];
|
|
292
|
+
|
|
293
|
+
await user.click(activeSelect);
|
|
294
|
+
await user.click(screen.getByRole('option', { name: 'es-ES' }));
|
|
295
|
+
|
|
296
|
+
expect(onActiveLangChange).toHaveBeenCalledWith('es-ES');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('calls onDefaultLangChange when default language is changed', async () => {
|
|
300
|
+
const user = userEvent.setup();
|
|
301
|
+
const onDefaultLangChange = jest.fn();
|
|
302
|
+
|
|
303
|
+
render(
|
|
304
|
+
<LanguageControls
|
|
305
|
+
{...defaultProps}
|
|
306
|
+
defaultLang="en-US"
|
|
307
|
+
onDefaultLangChange={onDefaultLangChange}
|
|
308
|
+
/>,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const selects = screen.getAllByRole('combobox');
|
|
312
|
+
const defaultSelect = selects[1];
|
|
313
|
+
|
|
314
|
+
await user.click(defaultSelect);
|
|
315
|
+
await user.click(screen.getByRole('option', { name: 'fr-FR' }));
|
|
316
|
+
|
|
317
|
+
expect(onDefaultLangChange).toHaveBeenCalledWith('fr-FR');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('handles independent language changes', async () => {
|
|
321
|
+
const user = userEvent.setup();
|
|
322
|
+
const onActiveLangChange = jest.fn();
|
|
323
|
+
const onDefaultLangChange = jest.fn();
|
|
324
|
+
|
|
325
|
+
render(
|
|
326
|
+
<LanguageControls
|
|
327
|
+
{...defaultProps}
|
|
328
|
+
activeLang="en-US"
|
|
329
|
+
defaultLang="en-US"
|
|
330
|
+
onActiveLangChange={onActiveLangChange}
|
|
331
|
+
onDefaultLangChange={onDefaultLangChange}
|
|
332
|
+
/>,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const selects = screen.getAllByRole('combobox');
|
|
336
|
+
|
|
337
|
+
// Change active language
|
|
338
|
+
await user.click(selects[0]);
|
|
339
|
+
await user.click(screen.getByRole('option', { name: 'es-ES' }));
|
|
340
|
+
|
|
341
|
+
expect(onActiveLangChange).toHaveBeenCalledWith('es-ES');
|
|
342
|
+
expect(onDefaultLangChange).not.toHaveBeenCalled();
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe('props handling', () => {
|
|
347
|
+
it('displays correct language options', async () => {
|
|
348
|
+
const user = userEvent.setup();
|
|
349
|
+
render(
|
|
350
|
+
<LanguageControls
|
|
351
|
+
{...defaultProps}
|
|
352
|
+
langs={['en-US', 'es-ES', 'fr-FR', 'de-DE']}
|
|
353
|
+
/>,
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const selects = screen.getAllByRole('combobox');
|
|
357
|
+
await user.click(selects[0]);
|
|
358
|
+
|
|
359
|
+
expect(screen.getByRole('option', { name: 'en-US' })).toBeInTheDocument();
|
|
360
|
+
expect(screen.getByRole('option', { name: 'es-ES' })).toBeInTheDocument();
|
|
361
|
+
expect(screen.getByRole('option', { name: 'fr-FR' })).toBeInTheDocument();
|
|
362
|
+
expect(screen.getByRole('option', { name: 'de-DE' })).toBeInTheDocument();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('maintains different active and default languages', async () => {
|
|
366
|
+
const user = userEvent.setup();
|
|
367
|
+
render(
|
|
368
|
+
<LanguageControls
|
|
369
|
+
{...defaultProps}
|
|
370
|
+
activeLang="es-ES"
|
|
371
|
+
defaultLang="en-US"
|
|
372
|
+
/>,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const selects = screen.getAllByRole('combobox');
|
|
376
|
+
expect(selects[0]).toHaveTextContent('es-ES');
|
|
377
|
+
expect(selects[1]).toHaveTextContent('en-US');
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe('updates', () => {
|
|
382
|
+
it('updates when activeLang prop changes', () => {
|
|
383
|
+
const { rerender } = render(
|
|
384
|
+
<LanguageControls
|
|
385
|
+
{...defaultProps}
|
|
386
|
+
activeLang="en-US"
|
|
387
|
+
/>,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
let selects = screen.getAllByRole('combobox');
|
|
391
|
+
expect(selects[0]).toHaveTextContent('en-US');
|
|
392
|
+
|
|
393
|
+
rerender(
|
|
394
|
+
<LanguageControls
|
|
395
|
+
{...defaultProps}
|
|
396
|
+
activeLang="es-ES"
|
|
397
|
+
/>,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
selects = screen.getAllByRole('combobox');
|
|
401
|
+
expect(selects[0]).toHaveTextContent('es-ES');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('updates when defaultLang prop changes', () => {
|
|
405
|
+
const { rerender } = render(
|
|
406
|
+
<LanguageControls
|
|
407
|
+
{...defaultProps}
|
|
408
|
+
defaultLang="en-US"
|
|
409
|
+
/>,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
let selects = screen.getAllByRole('combobox');
|
|
413
|
+
expect(selects[1]).toHaveTextContent('en-US');
|
|
414
|
+
|
|
415
|
+
rerender(
|
|
416
|
+
<LanguageControls
|
|
417
|
+
{...defaultProps}
|
|
418
|
+
defaultLang="fr-FR"
|
|
419
|
+
/>,
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
selects = screen.getAllByRole('combobox');
|
|
423
|
+
expect(selects[1]).toHaveTextContent('fr-FR');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('updates when langs prop changes', async () => {
|
|
427
|
+
const user = userEvent.setup();
|
|
428
|
+
const { rerender } = render(
|
|
429
|
+
<LanguageControls
|
|
430
|
+
{...defaultProps}
|
|
431
|
+
langs={['en-US', 'es-ES']}
|
|
432
|
+
/>,
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
rerender(
|
|
436
|
+
<LanguageControls
|
|
437
|
+
{...defaultProps}
|
|
438
|
+
langs={['en-US', 'es-ES', 'fr-FR']}
|
|
439
|
+
/>,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const selects = screen.getAllByRole('combobox');
|
|
443
|
+
await user.click(selects[0]);
|
|
444
|
+
|
|
445
|
+
expect(screen.getByRole('option', { name: 'fr-FR' })).toBeInTheDocument();
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
describe('accessibility', () => {
|
|
450
|
+
it('has proper semantic structure', () => {
|
|
451
|
+
const { container } = render(<LanguageControls {...defaultProps} />);
|
|
452
|
+
|
|
453
|
+
const selects = screen.getAllByRole('combobox');
|
|
454
|
+
expect(selects.length).toBe(2);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
});
|