@capillarytech/creatives-library 8.0.207 → 8.0.209
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/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/package.json +16 -2
- package/v2Components/HtmlEditor/HTMLEditor.js +508 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1809 -0
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +532 -0
- package/v2Components/HtmlEditor/_htmlEditor.scss +304 -0
- package/v2Components/HtmlEditor/_index.lazy.scss +26 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +376 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +331 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/__tests__/index.test.js +314 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +244 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +111 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/PreviewModeGroup.js +72 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/__tests__/PreviewModeGroup.test.js +1594 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +113 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/_previewModeGroup.scss +82 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +115 -0
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +57 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/ContentOverlay.js +90 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +60 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/LayoutSelector.js +58 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/ContentOverlay.test.js +389 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +424 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/LayoutSelector.test.js +248 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +253 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +104 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +179 -0
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +220 -0
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +229 -0
- package/v2Components/HtmlEditor/components/SplitContainer/SplitContainer.js +276 -0
- package/v2Components/HtmlEditor/components/SplitContainer/__tests__/SplitContainer.test.js +295 -0
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +257 -0
- package/v2Components/HtmlEditor/components/SplitContainer/index.js +7 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +31 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +70 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/__tests__/index.test.js +98 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +311 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +297 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/messages.js +57 -0
- package/v2Components/HtmlEditor/components/common/EditorContext.js +84 -0
- package/v2Components/HtmlEditor/components/common/__tests__/EditorContext.test.js +660 -0
- package/v2Components/HtmlEditor/constants.js +241 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useEditorContent.test.js +450 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +785 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useLayoutState.test.js +580 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.enhanced.test.js +768 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +590 -0
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +274 -0
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +407 -0
- package/v2Components/HtmlEditor/hooks/useLayoutState.js +247 -0
- package/v2Components/HtmlEditor/hooks/useValidation.js +325 -0
- package/v2Components/HtmlEditor/index.js +29 -0
- package/v2Components/HtmlEditor/index.lazy.js +114 -0
- package/v2Components/HtmlEditor/messages.js +389 -0
- package/v2Components/HtmlEditor/utils/__tests__/contentSanitizer.test.js +741 -0
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +1042 -0
- package/v2Components/HtmlEditor/utils/__tests__/liquidTemplateSupport.test.js +515 -0
- package/v2Components/HtmlEditor/utils/__tests__/properSyntaxHighlighting.test.js +473 -0
- package/v2Components/HtmlEditor/utils/__tests__/simplePerformance.test.js +1109 -0
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +240 -0
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +433 -0
- package/v2Components/HtmlEditor/utils/htmlValidator.js +508 -0
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +524 -0
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +163 -0
- package/v2Components/HtmlEditor/utils/simplePerformance.js +145 -0
- package/v2Components/HtmlEditor/utils/validationAdapter.js +130 -0
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +200 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +545 -0
- package/v2Containers/EmailWrapper/index.js +8 -1
- package/v2Containers/Templates/constants.js +8 -0
- package/v2Containers/Templates/index.js +56 -28
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +5 -14
- package/v2Containers/Whatsapp/constants.js +26 -2
- package/v2Containers/Whatsapp/index.js +4 -1
- package/v2Containers/Whatsapp/tests/index.test.js +460 -18
|
@@ -0,0 +1,1594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreviewModeGroup Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the PreviewModeGroup component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
9
|
+
import '@testing-library/jest-dom';
|
|
10
|
+
import { IntlProvider } from 'react-intl';
|
|
11
|
+
import PreviewModeGroup from '../PreviewModeGroup';
|
|
12
|
+
import { PREVIEW_MODES } from '../../../constants';
|
|
13
|
+
|
|
14
|
+
// Mock Cap UI components
|
|
15
|
+
jest.mock('@capillarytech/cap-ui-library/CapButton', () => {
|
|
16
|
+
return function MockCapButton({ children, onClick, disabled, className, type, value, 'aria-label': ariaLabel, 'aria-pressed': ariaPressed, ...rest }) {
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
onClick={onClick}
|
|
20
|
+
disabled={disabled}
|
|
21
|
+
className={className}
|
|
22
|
+
data-type={type}
|
|
23
|
+
value={value}
|
|
24
|
+
aria-label={ariaLabel}
|
|
25
|
+
aria-pressed={ariaPressed}
|
|
26
|
+
{...rest}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</button>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
jest.mock('@capillarytech/cap-ui-library/CapIcon', () => {
|
|
35
|
+
return function MockCapIcon({ type, size }) {
|
|
36
|
+
return <span data-testid={`icon-${type}`} data-size={size}>{type}</span>;
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Test wrapper with IntlProvider
|
|
41
|
+
const renderWithIntl = (component) => {
|
|
42
|
+
return render(
|
|
43
|
+
<IntlProvider locale="en" messages={{}}>
|
|
44
|
+
{component}
|
|
45
|
+
</IntlProvider>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe('PreviewModeGroup', () => {
|
|
50
|
+
const mockOnChange = jest.fn();
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
jest.clearAllMocks();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('Basic Rendering', () => {
|
|
57
|
+
it('renders without crashing', () => {
|
|
58
|
+
renderWithIntl(
|
|
59
|
+
<PreviewModeGroup
|
|
60
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
61
|
+
onChange={mockOnChange}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(screen.getByTestId('icon-desktop')).toBeInTheDocument();
|
|
66
|
+
expect(screen.getByTestId('icon-mobile')).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('renders both desktop and mobile buttons', () => {
|
|
70
|
+
renderWithIntl(
|
|
71
|
+
<PreviewModeGroup
|
|
72
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
73
|
+
onChange={mockOnChange}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const buttons = screen.getAllByRole('button');
|
|
78
|
+
expect(buttons).toHaveLength(2);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('renders desktop icon', () => {
|
|
82
|
+
renderWithIntl(
|
|
83
|
+
<PreviewModeGroup
|
|
84
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
85
|
+
onChange={mockOnChange}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const desktopIcon = screen.getByTestId('icon-desktop');
|
|
90
|
+
expect(desktopIcon).toBeInTheDocument();
|
|
91
|
+
expect(desktopIcon).toHaveAttribute('data-size', 'm');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('renders mobile icon', () => {
|
|
95
|
+
renderWithIntl(
|
|
96
|
+
<PreviewModeGroup
|
|
97
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
98
|
+
onChange={mockOnChange}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const mobileIcon = screen.getByTestId('icon-mobile');
|
|
103
|
+
expect(mobileIcon).toBeInTheDocument();
|
|
104
|
+
expect(mobileIcon).toHaveAttribute('data-size', 'm');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('renders with correct container structure', () => {
|
|
108
|
+
const { container } = renderWithIntl(
|
|
109
|
+
<PreviewModeGroup
|
|
110
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
111
|
+
onChange={mockOnChange}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const previewGroup = container.querySelector('.preview-mode-group');
|
|
116
|
+
expect(previewGroup).toBeInTheDocument();
|
|
117
|
+
expect(previewGroup).toHaveClass('preview-mode-group');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Active State', () => {
|
|
122
|
+
it('marks desktop button as active when desktop mode is selected', () => {
|
|
123
|
+
renderWithIntl(
|
|
124
|
+
<PreviewModeGroup
|
|
125
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
126
|
+
onChange={mockOnChange}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const buttons = screen.getAllByRole('button');
|
|
131
|
+
const desktopButton = buttons[0];
|
|
132
|
+
|
|
133
|
+
expect(desktopButton).toHaveClass('preview-mode-group__btn--active');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('marks mobile button as active when mobile mode is selected', () => {
|
|
137
|
+
renderWithIntl(
|
|
138
|
+
<PreviewModeGroup
|
|
139
|
+
value={PREVIEW_MODES.MOBILE}
|
|
140
|
+
onChange={mockOnChange}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const buttons = screen.getAllByRole('button');
|
|
145
|
+
const mobileButton = buttons[1];
|
|
146
|
+
|
|
147
|
+
expect(mobileButton).toHaveClass('preview-mode-group__btn--active');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('only one button is active at a time', () => {
|
|
151
|
+
renderWithIntl(
|
|
152
|
+
<PreviewModeGroup
|
|
153
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
154
|
+
onChange={mockOnChange}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const buttons = screen.getAllByRole('button');
|
|
159
|
+
const activeButtons = buttons.filter(btn =>
|
|
160
|
+
btn.className.includes('preview-mode-group__btn--active')
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(activeButtons).toHaveLength(1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('applies base class to all buttons', () => {
|
|
167
|
+
renderWithIntl(
|
|
168
|
+
<PreviewModeGroup
|
|
169
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
170
|
+
onChange={mockOnChange}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const buttons = screen.getAllByRole('button');
|
|
175
|
+
|
|
176
|
+
buttons.forEach(button => {
|
|
177
|
+
expect(button).toHaveClass('preview-mode-group__btn');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('User Interactions', () => {
|
|
183
|
+
it('calls onChange with DESKTOP when desktop button is clicked', () => {
|
|
184
|
+
renderWithIntl(
|
|
185
|
+
<PreviewModeGroup
|
|
186
|
+
value={PREVIEW_MODES.MOBILE}
|
|
187
|
+
onChange={mockOnChange}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const buttons = screen.getAllByRole('button');
|
|
192
|
+
const desktopButton = buttons[0];
|
|
193
|
+
|
|
194
|
+
fireEvent.click(desktopButton);
|
|
195
|
+
|
|
196
|
+
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.DESKTOP);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('calls onChange with MOBILE when mobile button is clicked', () => {
|
|
201
|
+
renderWithIntl(
|
|
202
|
+
<PreviewModeGroup
|
|
203
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
204
|
+
onChange={mockOnChange}
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const buttons = screen.getAllByRole('button');
|
|
209
|
+
const mobileButton = buttons[1];
|
|
210
|
+
|
|
211
|
+
fireEvent.click(mobileButton);
|
|
212
|
+
|
|
213
|
+
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
|
214
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('allows clicking the already active button', () => {
|
|
218
|
+
renderWithIntl(
|
|
219
|
+
<PreviewModeGroup
|
|
220
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
221
|
+
onChange={mockOnChange}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const buttons = screen.getAllByRole('button');
|
|
226
|
+
const desktopButton = buttons[0];
|
|
227
|
+
|
|
228
|
+
fireEvent.click(desktopButton);
|
|
229
|
+
|
|
230
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.DESKTOP);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('handles multiple clicks', () => {
|
|
234
|
+
renderWithIntl(
|
|
235
|
+
<PreviewModeGroup
|
|
236
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
237
|
+
onChange={mockOnChange}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const buttons = screen.getAllByRole('button');
|
|
242
|
+
const desktopButton = buttons[0];
|
|
243
|
+
const mobileButton = buttons[1];
|
|
244
|
+
|
|
245
|
+
fireEvent.click(mobileButton);
|
|
246
|
+
fireEvent.click(desktopButton);
|
|
247
|
+
fireEvent.click(mobileButton);
|
|
248
|
+
|
|
249
|
+
expect(mockOnChange).toHaveBeenCalledTimes(3);
|
|
250
|
+
expect(mockOnChange).toHaveBeenNthCalledWith(1, PREVIEW_MODES.MOBILE);
|
|
251
|
+
expect(mockOnChange).toHaveBeenNthCalledWith(2, PREVIEW_MODES.DESKTOP);
|
|
252
|
+
expect(mockOnChange).toHaveBeenNthCalledWith(3, PREVIEW_MODES.MOBILE);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('Disabled State', () => {
|
|
257
|
+
it('disables both buttons when disabled prop is true', () => {
|
|
258
|
+
renderWithIntl(
|
|
259
|
+
<PreviewModeGroup
|
|
260
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
261
|
+
onChange={mockOnChange}
|
|
262
|
+
disabled={true}
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const buttons = screen.getAllByRole('button');
|
|
267
|
+
|
|
268
|
+
buttons.forEach(button => {
|
|
269
|
+
expect(button).toBeDisabled();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('does not call onChange when disabled button is clicked', () => {
|
|
274
|
+
renderWithIntl(
|
|
275
|
+
<PreviewModeGroup
|
|
276
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
277
|
+
onChange={mockOnChange}
|
|
278
|
+
disabled={true}
|
|
279
|
+
/>
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const buttons = screen.getAllByRole('button');
|
|
283
|
+
const mobileButton = buttons[1];
|
|
284
|
+
|
|
285
|
+
fireEvent.click(mobileButton);
|
|
286
|
+
|
|
287
|
+
expect(mockOnChange).not.toHaveBeenCalled();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('enables buttons when disabled prop is false', () => {
|
|
291
|
+
renderWithIntl(
|
|
292
|
+
<PreviewModeGroup
|
|
293
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
294
|
+
onChange={mockOnChange}
|
|
295
|
+
disabled={false}
|
|
296
|
+
/>
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const buttons = screen.getAllByRole('button');
|
|
300
|
+
|
|
301
|
+
buttons.forEach(button => {
|
|
302
|
+
expect(button).not.toBeDisabled();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('enables buttons by default when disabled prop is not provided', () => {
|
|
307
|
+
renderWithIntl(
|
|
308
|
+
<PreviewModeGroup
|
|
309
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
310
|
+
onChange={mockOnChange}
|
|
311
|
+
/>
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const buttons = screen.getAllByRole('button');
|
|
315
|
+
|
|
316
|
+
buttons.forEach(button => {
|
|
317
|
+
expect(button).not.toBeDisabled();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('can transition from disabled to enabled', () => {
|
|
322
|
+
const { rerender } = renderWithIntl(
|
|
323
|
+
<PreviewModeGroup
|
|
324
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
325
|
+
onChange={mockOnChange}
|
|
326
|
+
disabled={true}
|
|
327
|
+
/>
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
let buttons = screen.getAllByRole('button');
|
|
331
|
+
expect(buttons[0]).toBeDisabled();
|
|
332
|
+
|
|
333
|
+
rerender(
|
|
334
|
+
<IntlProvider locale="en" messages={{}}>
|
|
335
|
+
<PreviewModeGroup
|
|
336
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
337
|
+
onChange={mockOnChange}
|
|
338
|
+
disabled={false}
|
|
339
|
+
/>
|
|
340
|
+
</IntlProvider>
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
buttons = screen.getAllByRole('button');
|
|
344
|
+
expect(buttons[0]).not.toBeDisabled();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('Button Types', () => {
|
|
349
|
+
it('renders buttons with flat type', () => {
|
|
350
|
+
renderWithIntl(
|
|
351
|
+
<PreviewModeGroup
|
|
352
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
353
|
+
onChange={mockOnChange}
|
|
354
|
+
/>
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const buttons = screen.getAllByRole('button');
|
|
358
|
+
|
|
359
|
+
buttons.forEach(button => {
|
|
360
|
+
expect(button).toHaveAttribute('data-type', 'flat');
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe('Value Updates', () => {
|
|
366
|
+
it('updates active state when value prop changes', () => {
|
|
367
|
+
const { rerender } = renderWithIntl(
|
|
368
|
+
<PreviewModeGroup
|
|
369
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
370
|
+
onChange={mockOnChange}
|
|
371
|
+
/>
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
let buttons = screen.getAllByRole('button');
|
|
375
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
376
|
+
expect(buttons[1]).not.toHaveClass('preview-mode-group__btn--active');
|
|
377
|
+
|
|
378
|
+
rerender(
|
|
379
|
+
<IntlProvider locale="en" messages={{}}>
|
|
380
|
+
<PreviewModeGroup
|
|
381
|
+
value={PREVIEW_MODES.MOBILE}
|
|
382
|
+
onChange={mockOnChange}
|
|
383
|
+
/>
|
|
384
|
+
</IntlProvider>
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
buttons = screen.getAllByRole('button');
|
|
388
|
+
expect(buttons[0]).not.toHaveClass('preview-mode-group__btn--active');
|
|
389
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('maintains correct state through multiple value changes', () => {
|
|
393
|
+
const { rerender } = renderWithIntl(
|
|
394
|
+
<PreviewModeGroup
|
|
395
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
396
|
+
onChange={mockOnChange}
|
|
397
|
+
/>
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// Change to mobile
|
|
401
|
+
rerender(
|
|
402
|
+
<IntlProvider locale="en" messages={{}}>
|
|
403
|
+
<PreviewModeGroup
|
|
404
|
+
value={PREVIEW_MODES.MOBILE}
|
|
405
|
+
onChange={mockOnChange}
|
|
406
|
+
/>
|
|
407
|
+
</IntlProvider>
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
let buttons = screen.getAllByRole('button');
|
|
411
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
412
|
+
|
|
413
|
+
// Change back to desktop
|
|
414
|
+
rerender(
|
|
415
|
+
<IntlProvider locale="en" messages={{}}>
|
|
416
|
+
<PreviewModeGroup
|
|
417
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
418
|
+
onChange={mockOnChange}
|
|
419
|
+
/>
|
|
420
|
+
</IntlProvider>
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
buttons = screen.getAllByRole('button');
|
|
424
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe('PropTypes and Validation', () => {
|
|
429
|
+
it('accepts valid PREVIEW_MODES values', () => {
|
|
430
|
+
expect(() => {
|
|
431
|
+
renderWithIntl(
|
|
432
|
+
<PreviewModeGroup
|
|
433
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
434
|
+
onChange={mockOnChange}
|
|
435
|
+
/>
|
|
436
|
+
);
|
|
437
|
+
}).not.toThrow();
|
|
438
|
+
|
|
439
|
+
expect(() => {
|
|
440
|
+
renderWithIntl(
|
|
441
|
+
<PreviewModeGroup
|
|
442
|
+
value={PREVIEW_MODES.MOBILE}
|
|
443
|
+
onChange={mockOnChange}
|
|
444
|
+
/>
|
|
445
|
+
);
|
|
446
|
+
}).not.toThrow();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('accepts onChange function', () => {
|
|
450
|
+
expect(() => {
|
|
451
|
+
renderWithIntl(
|
|
452
|
+
<PreviewModeGroup
|
|
453
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
454
|
+
onChange={jest.fn()}
|
|
455
|
+
/>
|
|
456
|
+
);
|
|
457
|
+
}).not.toThrow();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('accepts disabled boolean', () => {
|
|
461
|
+
expect(() => {
|
|
462
|
+
renderWithIntl(
|
|
463
|
+
<PreviewModeGroup
|
|
464
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
465
|
+
onChange={mockOnChange}
|
|
466
|
+
disabled={true}
|
|
467
|
+
/>
|
|
468
|
+
);
|
|
469
|
+
}).not.toThrow();
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe('Integration with IntlProvider', () => {
|
|
474
|
+
it('renders correctly within IntlProvider', () => {
|
|
475
|
+
const { container } = renderWithIntl(
|
|
476
|
+
<PreviewModeGroup
|
|
477
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
478
|
+
onChange={mockOnChange}
|
|
479
|
+
/>
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('works with different locales', () => {
|
|
486
|
+
const { container: enContainer } = render(
|
|
487
|
+
<IntlProvider locale="en" messages={{}}>
|
|
488
|
+
<PreviewModeGroup
|
|
489
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
490
|
+
onChange={mockOnChange}
|
|
491
|
+
/>
|
|
492
|
+
</IntlProvider>
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
expect(enContainer.firstChild).toBeInTheDocument();
|
|
496
|
+
|
|
497
|
+
const { container: jaContainer } = render(
|
|
498
|
+
<IntlProvider locale="ja" messages={{}}>
|
|
499
|
+
<PreviewModeGroup
|
|
500
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
501
|
+
onChange={mockOnChange}
|
|
502
|
+
/>
|
|
503
|
+
</IntlProvider>
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
expect(jaContainer.firstChild).toBeInTheDocument();
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe('Edge Cases', () => {
|
|
511
|
+
it('handles rapid successive clicks', () => {
|
|
512
|
+
renderWithIntl(
|
|
513
|
+
<PreviewModeGroup
|
|
514
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
515
|
+
onChange={mockOnChange}
|
|
516
|
+
/>
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
const buttons = screen.getAllByRole('button');
|
|
520
|
+
const mobileButton = buttons[1];
|
|
521
|
+
|
|
522
|
+
// Deterministic rapid clicks - each click is processed immediately
|
|
523
|
+
for (let i = 0; i < 5; i++) {
|
|
524
|
+
fireEvent.click(mobileButton);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// All clicks should be processed synchronously
|
|
528
|
+
expect(mockOnChange).toHaveBeenCalledTimes(5);
|
|
529
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('handles onChange being replaced', () => {
|
|
533
|
+
const firstOnChange = jest.fn();
|
|
534
|
+
const secondOnChange = jest.fn();
|
|
535
|
+
|
|
536
|
+
const { rerender } = renderWithIntl(
|
|
537
|
+
<PreviewModeGroup
|
|
538
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
539
|
+
onChange={firstOnChange}
|
|
540
|
+
/>
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
const buttons = screen.getAllByRole('button');
|
|
544
|
+
fireEvent.click(buttons[1]);
|
|
545
|
+
expect(firstOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
546
|
+
|
|
547
|
+
rerender(
|
|
548
|
+
<IntlProvider locale="en" messages={{}}>
|
|
549
|
+
<PreviewModeGroup
|
|
550
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
551
|
+
onChange={secondOnChange}
|
|
552
|
+
/>
|
|
553
|
+
</IntlProvider>
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
fireEvent.click(buttons[1]);
|
|
557
|
+
expect(secondOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
558
|
+
expect(firstOnChange).toHaveBeenCalledTimes(1);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
describe('Accessibility', () => {
|
|
563
|
+
it('renders buttons with proper role', () => {
|
|
564
|
+
renderWithIntl(
|
|
565
|
+
<PreviewModeGroup
|
|
566
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
567
|
+
onChange={mockOnChange}
|
|
568
|
+
/>
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
const buttons = screen.getAllByRole('button');
|
|
572
|
+
expect(buttons).toHaveLength(2);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('disabled buttons are not clickable', () => {
|
|
576
|
+
renderWithIntl(
|
|
577
|
+
<PreviewModeGroup
|
|
578
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
579
|
+
onChange={mockOnChange}
|
|
580
|
+
disabled={true}
|
|
581
|
+
/>
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
const buttons = screen.getAllByRole('button');
|
|
585
|
+
|
|
586
|
+
buttons.forEach(button => {
|
|
587
|
+
expect(button).toBeDisabled();
|
|
588
|
+
fireEvent.click(button);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
expect(mockOnChange).not.toHaveBeenCalled();
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('maintains focus management', () => {
|
|
595
|
+
renderWithIntl(
|
|
596
|
+
<PreviewModeGroup
|
|
597
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
598
|
+
onChange={mockOnChange}
|
|
599
|
+
/>
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
const buttons = screen.getAllByRole('button');
|
|
603
|
+
const desktopButton = buttons[0];
|
|
604
|
+
|
|
605
|
+
desktopButton.focus();
|
|
606
|
+
expect(document.activeElement).toBe(desktopButton);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('supports keyboard navigation', () => {
|
|
610
|
+
renderWithIntl(
|
|
611
|
+
<PreviewModeGroup
|
|
612
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
613
|
+
onChange={mockOnChange}
|
|
614
|
+
/>
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
const buttons = screen.getAllByRole('button');
|
|
618
|
+
|
|
619
|
+
// Tab through buttons
|
|
620
|
+
buttons.forEach(button => {
|
|
621
|
+
button.focus();
|
|
622
|
+
expect(document.activeElement).toBe(button);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
describe('Performance and Memory', () => {
|
|
628
|
+
it('handles frequent re-renders efficiently', () => {
|
|
629
|
+
const { rerender } = renderWithIntl(
|
|
630
|
+
<PreviewModeGroup
|
|
631
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
632
|
+
onChange={mockOnChange}
|
|
633
|
+
/>
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
// Multiple re-renders
|
|
637
|
+
for (let i = 0; i < 20; i++) {
|
|
638
|
+
const value = i % 2 === 0 ? PREVIEW_MODES.DESKTOP : PREVIEW_MODES.MOBILE;
|
|
639
|
+
rerender(
|
|
640
|
+
<IntlProvider locale="en" messages={{}}>
|
|
641
|
+
<PreviewModeGroup
|
|
642
|
+
value={value}
|
|
643
|
+
onChange={mockOnChange}
|
|
644
|
+
/>
|
|
645
|
+
</IntlProvider>
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const buttons = screen.getAllByRole('button');
|
|
650
|
+
expect(buttons).toHaveLength(2);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it('maintains consistent button references', () => {
|
|
654
|
+
const { rerender } = renderWithIntl(
|
|
655
|
+
<PreviewModeGroup
|
|
656
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
657
|
+
onChange={mockOnChange}
|
|
658
|
+
/>
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
const initialButtons = screen.getAllByRole('button');
|
|
662
|
+
|
|
663
|
+
rerender(
|
|
664
|
+
<IntlProvider locale="en" messages={{}}>
|
|
665
|
+
<PreviewModeGroup
|
|
666
|
+
value={PREVIEW_MODES.MOBILE}
|
|
667
|
+
onChange={mockOnChange}
|
|
668
|
+
/>
|
|
669
|
+
</IntlProvider>
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
const updatedButtons = screen.getAllByRole('button');
|
|
673
|
+
expect(updatedButtons).toHaveLength(initialButtons.length);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('handles rapid prop changes without memory leaks', () => {
|
|
677
|
+
const { rerender } = renderWithIntl(
|
|
678
|
+
<PreviewModeGroup
|
|
679
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
680
|
+
onChange={mockOnChange}
|
|
681
|
+
/>
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
// Rapid prop changes
|
|
685
|
+
for (let i = 0; i < 50; i++) {
|
|
686
|
+
rerender(
|
|
687
|
+
<IntlProvider locale="en" messages={{}}>
|
|
688
|
+
<PreviewModeGroup
|
|
689
|
+
value={i % 2 === 0 ? PREVIEW_MODES.DESKTOP : PREVIEW_MODES.MOBILE}
|
|
690
|
+
onChange={mockOnChange}
|
|
691
|
+
disabled={i % 3 === 0}
|
|
692
|
+
/>
|
|
693
|
+
</IntlProvider>
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
expect(screen.getAllByRole('button')).toHaveLength(2);
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
describe('Component Lifecycle', () => {
|
|
702
|
+
it('initializes with correct state', () => {
|
|
703
|
+
renderWithIntl(
|
|
704
|
+
<PreviewModeGroup
|
|
705
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
706
|
+
onChange={mockOnChange}
|
|
707
|
+
/>
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
const buttons = screen.getAllByRole('button');
|
|
711
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
712
|
+
expect(buttons[1]).not.toHaveClass('preview-mode-group__btn--active');
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('cleans up properly on unmount', () => {
|
|
716
|
+
const { unmount } = renderWithIntl(
|
|
717
|
+
<PreviewModeGroup
|
|
718
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
719
|
+
onChange={mockOnChange}
|
|
720
|
+
/>
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
expect(() => unmount()).not.toThrow();
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it('handles component updates correctly', () => {
|
|
727
|
+
const { rerender } = renderWithIntl(
|
|
728
|
+
<PreviewModeGroup
|
|
729
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
730
|
+
onChange={mockOnChange}
|
|
731
|
+
disabled={false}
|
|
732
|
+
/>
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
// Update multiple props
|
|
736
|
+
rerender(
|
|
737
|
+
<IntlProvider locale="en" messages={{}}>
|
|
738
|
+
<PreviewModeGroup
|
|
739
|
+
value={PREVIEW_MODES.MOBILE}
|
|
740
|
+
onChange={jest.fn()}
|
|
741
|
+
disabled={true}
|
|
742
|
+
/>
|
|
743
|
+
</IntlProvider>
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
const buttons = screen.getAllByRole('button');
|
|
747
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
748
|
+
expect(buttons[0]).toBeDisabled();
|
|
749
|
+
expect(buttons[1]).toBeDisabled();
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
describe('Error Handling and Edge Cases', () => {
|
|
754
|
+
it('handles null onChange gracefully', () => {
|
|
755
|
+
// This should not crash the component
|
|
756
|
+
expect(() => {
|
|
757
|
+
renderWithIntl(
|
|
758
|
+
<PreviewModeGroup
|
|
759
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
760
|
+
onChange={null}
|
|
761
|
+
/>
|
|
762
|
+
);
|
|
763
|
+
}).not.toThrow();
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it('handles undefined value gracefully', () => {
|
|
767
|
+
// Component should still render even with undefined value
|
|
768
|
+
expect(() => {
|
|
769
|
+
renderWithIntl(
|
|
770
|
+
<PreviewModeGroup
|
|
771
|
+
value={undefined}
|
|
772
|
+
onChange={mockOnChange}
|
|
773
|
+
/>
|
|
774
|
+
);
|
|
775
|
+
}).not.toThrow();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('maintains functionality with concurrent clicks', () => {
|
|
779
|
+
// Use fake timers for deterministic behavior
|
|
780
|
+
jest.useFakeTimers();
|
|
781
|
+
|
|
782
|
+
renderWithIntl(
|
|
783
|
+
<PreviewModeGroup
|
|
784
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
785
|
+
onChange={mockOnChange}
|
|
786
|
+
/>
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
const buttons = screen.getAllByRole('button');
|
|
790
|
+
|
|
791
|
+
// Simulate deterministic "concurrent" clicks using fake timers
|
|
792
|
+
for (let i = 0; i < 10; i++) {
|
|
793
|
+
setTimeout(() => {
|
|
794
|
+
fireEvent.click(buttons[i % 2]);
|
|
795
|
+
}, i * 10); // Deterministic delays: 0ms, 10ms, 20ms, etc.
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Advance timers to execute all scheduled clicks
|
|
799
|
+
jest.advanceTimersByTime(100); // Advance past all scheduled timeouts
|
|
800
|
+
|
|
801
|
+
expect(mockOnChange).toHaveBeenCalledTimes(10);
|
|
802
|
+
|
|
803
|
+
// Restore real timers
|
|
804
|
+
jest.useRealTimers();
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('handles extreme prop combinations', () => {
|
|
808
|
+
expect(() => {
|
|
809
|
+
renderWithIntl(
|
|
810
|
+
<PreviewModeGroup
|
|
811
|
+
value={PREVIEW_MODES.MOBILE}
|
|
812
|
+
onChange={mockOnChange}
|
|
813
|
+
disabled={true}
|
|
814
|
+
/>
|
|
815
|
+
);
|
|
816
|
+
}).not.toThrow();
|
|
817
|
+
|
|
818
|
+
const buttons = screen.getAllByRole('button');
|
|
819
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
820
|
+
expect(buttons[1]).toBeDisabled();
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
describe('Constants Integration', () => {
|
|
825
|
+
it('uses PREVIEW_MODES constants correctly', () => {
|
|
826
|
+
renderWithIntl(
|
|
827
|
+
<PreviewModeGroup
|
|
828
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
829
|
+
onChange={mockOnChange}
|
|
830
|
+
/>
|
|
831
|
+
);
|
|
832
|
+
|
|
833
|
+
const buttons = screen.getAllByRole('button');
|
|
834
|
+
fireEvent.click(buttons[1]);
|
|
835
|
+
|
|
836
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it('handles all valid PREVIEW_MODES values', () => {
|
|
840
|
+
const modes = Object.values(PREVIEW_MODES);
|
|
841
|
+
|
|
842
|
+
modes.forEach(mode => {
|
|
843
|
+
const { unmount } = renderWithIntl(
|
|
844
|
+
<PreviewModeGroup
|
|
845
|
+
value={mode}
|
|
846
|
+
onChange={mockOnChange}
|
|
847
|
+
/>
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
expect(screen.getAllByRole('button')).toHaveLength(2);
|
|
851
|
+
unmount();
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
it('maintains consistency with PREVIEW_MODES enum', () => {
|
|
856
|
+
expect(PREVIEW_MODES.DESKTOP).toBeDefined();
|
|
857
|
+
expect(PREVIEW_MODES.MOBILE).toBeDefined();
|
|
858
|
+
expect(typeof PREVIEW_MODES.DESKTOP).toBe('string');
|
|
859
|
+
expect(typeof PREVIEW_MODES.MOBILE).toBe('string');
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
describe('Cap UI Integration', () => {
|
|
864
|
+
it('passes correct props to CapButton components', () => {
|
|
865
|
+
renderWithIntl(
|
|
866
|
+
<PreviewModeGroup
|
|
867
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
868
|
+
onChange={mockOnChange}
|
|
869
|
+
disabled={true}
|
|
870
|
+
/>
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
const buttons = screen.getAllByRole('button');
|
|
874
|
+
|
|
875
|
+
buttons.forEach(button => {
|
|
876
|
+
expect(button).toHaveAttribute('data-type', 'flat');
|
|
877
|
+
expect(button).toBeDisabled();
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('passes correct props to CapIcon components', () => {
|
|
882
|
+
renderWithIntl(
|
|
883
|
+
<PreviewModeGroup
|
|
884
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
885
|
+
onChange={mockOnChange}
|
|
886
|
+
/>
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
const desktopIcon = screen.getByTestId('icon-desktop');
|
|
890
|
+
const mobileIcon = screen.getByTestId('icon-mobile');
|
|
891
|
+
|
|
892
|
+
expect(desktopIcon).toHaveAttribute('data-size', 'm');
|
|
893
|
+
expect(mobileIcon).toHaveAttribute('data-size', 'm');
|
|
894
|
+
expect(desktopIcon).toHaveTextContent('desktop');
|
|
895
|
+
expect(mobileIcon).toHaveTextContent('mobile');
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it('maintains Cap UI component structure', () => {
|
|
899
|
+
const { container } = renderWithIntl(
|
|
900
|
+
<PreviewModeGroup
|
|
901
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
902
|
+
onChange={mockOnChange}
|
|
903
|
+
/>
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
// Should have the preview-mode-group container
|
|
907
|
+
expect(container.querySelector('.preview-mode-group')).toBeInTheDocument();
|
|
908
|
+
|
|
909
|
+
// Should have buttons with Cap UI structure
|
|
910
|
+
const buttons = container.querySelectorAll('button');
|
|
911
|
+
expect(buttons).toHaveLength(2);
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
describe('Styling and Layout', () => {
|
|
916
|
+
it('applies correct container styles', () => {
|
|
917
|
+
const { container } = renderWithIntl(
|
|
918
|
+
<PreviewModeGroup
|
|
919
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
920
|
+
onChange={mockOnChange}
|
|
921
|
+
/>
|
|
922
|
+
);
|
|
923
|
+
|
|
924
|
+
const previewGroup = container.querySelector('.preview-mode-group');
|
|
925
|
+
expect(previewGroup).toHaveClass('preview-mode-group');
|
|
926
|
+
|
|
927
|
+
// Verify the container has the expected structure for CSS styling
|
|
928
|
+
expect(previewGroup.tagName.toLowerCase()).toBe('div');
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
it('applies correct button classes based on state', () => {
|
|
932
|
+
const { rerender } = renderWithIntl(
|
|
933
|
+
<PreviewModeGroup
|
|
934
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
935
|
+
onChange={mockOnChange}
|
|
936
|
+
/>
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
let buttons = screen.getAllByRole('button');
|
|
940
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn', 'preview-mode-group__btn--active');
|
|
941
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn');
|
|
942
|
+
expect(buttons[1]).not.toHaveClass('preview-mode-group__btn--active');
|
|
943
|
+
|
|
944
|
+
rerender(
|
|
945
|
+
<IntlProvider locale="en" messages={{}}>
|
|
946
|
+
<PreviewModeGroup
|
|
947
|
+
value={PREVIEW_MODES.MOBILE}
|
|
948
|
+
onChange={mockOnChange}
|
|
949
|
+
/>
|
|
950
|
+
</IntlProvider>
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
buttons = screen.getAllByRole('button');
|
|
954
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn');
|
|
955
|
+
expect(buttons[0]).not.toHaveClass('preview-mode-group__btn--active');
|
|
956
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn', 'preview-mode-group__btn--active');
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
it('maintains consistent layout structure', () => {
|
|
960
|
+
const { container } = renderWithIntl(
|
|
961
|
+
<PreviewModeGroup
|
|
962
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
963
|
+
onChange={mockOnChange}
|
|
964
|
+
/>
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
const previewGroup = container.querySelector('.preview-mode-group');
|
|
968
|
+
const buttons = previewGroup.querySelectorAll('button');
|
|
969
|
+
|
|
970
|
+
expect(buttons).toHaveLength(2);
|
|
971
|
+
expect(previewGroup.children).toHaveLength(2);
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
describe('Accessibility and ARIA Attributes', () => {
|
|
976
|
+
it('sets correct aria-label attributes for desktop button', () => {
|
|
977
|
+
renderWithIntl(
|
|
978
|
+
<PreviewModeGroup
|
|
979
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
980
|
+
onChange={mockOnChange}
|
|
981
|
+
/>
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
const buttons = screen.getAllByRole('button');
|
|
985
|
+
const desktopButton = buttons[0];
|
|
986
|
+
|
|
987
|
+
expect(desktopButton).toHaveAttribute('aria-label');
|
|
988
|
+
expect(desktopButton).toHaveAttribute('aria-pressed', 'true');
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it('sets correct aria-label attributes for mobile button', () => {
|
|
992
|
+
renderWithIntl(
|
|
993
|
+
<PreviewModeGroup
|
|
994
|
+
value={PREVIEW_MODES.MOBILE}
|
|
995
|
+
onChange={mockOnChange}
|
|
996
|
+
/>
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
const buttons = screen.getAllByRole('button');
|
|
1000
|
+
const mobileButton = buttons[1];
|
|
1001
|
+
|
|
1002
|
+
expect(mobileButton).toHaveAttribute('aria-label');
|
|
1003
|
+
expect(mobileButton).toHaveAttribute('aria-pressed', 'true');
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it('updates aria-pressed attributes correctly when value changes', () => {
|
|
1007
|
+
const { rerender } = renderWithIntl(
|
|
1008
|
+
<PreviewModeGroup
|
|
1009
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1010
|
+
onChange={mockOnChange}
|
|
1011
|
+
/>
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
let buttons = screen.getAllByRole('button');
|
|
1015
|
+
expect(buttons[0]).toHaveAttribute('aria-pressed', 'true');
|
|
1016
|
+
expect(buttons[1]).toHaveAttribute('aria-pressed', 'false');
|
|
1017
|
+
|
|
1018
|
+
rerender(
|
|
1019
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1020
|
+
<PreviewModeGroup
|
|
1021
|
+
value={PREVIEW_MODES.MOBILE}
|
|
1022
|
+
onChange={mockOnChange}
|
|
1023
|
+
/>
|
|
1024
|
+
</IntlProvider>
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
buttons = screen.getAllByRole('button');
|
|
1028
|
+
expect(buttons[0]).toHaveAttribute('aria-pressed', 'false');
|
|
1029
|
+
expect(buttons[1]).toHaveAttribute('aria-pressed', 'true');
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
it('maintains aria attributes when disabled', () => {
|
|
1033
|
+
renderWithIntl(
|
|
1034
|
+
<PreviewModeGroup
|
|
1035
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1036
|
+
onChange={mockOnChange}
|
|
1037
|
+
disabled={true}
|
|
1038
|
+
/>
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
const buttons = screen.getAllByRole('button');
|
|
1042
|
+
|
|
1043
|
+
buttons.forEach(button => {
|
|
1044
|
+
expect(button).toHaveAttribute('aria-label');
|
|
1045
|
+
expect(button).toHaveAttribute('aria-pressed');
|
|
1046
|
+
});
|
|
1047
|
+
});
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
describe('Internationalization (i18n)', () => {
|
|
1051
|
+
it('formats desktop message using intl', () => {
|
|
1052
|
+
const customMessages = {
|
|
1053
|
+
'app.components.HtmlEditor.desktop': 'Desktop View',
|
|
1054
|
+
'app.components.HtmlEditor.mobile': 'Mobile View'
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
render(
|
|
1058
|
+
<IntlProvider locale="en" messages={customMessages}>
|
|
1059
|
+
<PreviewModeGroup
|
|
1060
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1061
|
+
onChange={mockOnChange}
|
|
1062
|
+
/>
|
|
1063
|
+
</IntlProvider>
|
|
1064
|
+
);
|
|
1065
|
+
|
|
1066
|
+
const buttons = screen.getAllByRole('button');
|
|
1067
|
+
// The aria-label should be formatted by intl
|
|
1068
|
+
expect(buttons[0]).toHaveAttribute('aria-label');
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
it('formats mobile message using intl', () => {
|
|
1072
|
+
const customMessages = {
|
|
1073
|
+
'app.components.HtmlEditor.desktop': 'Desktop View',
|
|
1074
|
+
'app.components.HtmlEditor.mobile': 'Mobile View'
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
render(
|
|
1078
|
+
<IntlProvider locale="en" messages={customMessages}>
|
|
1079
|
+
<PreviewModeGroup
|
|
1080
|
+
value={PREVIEW_MODES.MOBILE}
|
|
1081
|
+
onChange={mockOnChange}
|
|
1082
|
+
/>
|
|
1083
|
+
</IntlProvider>
|
|
1084
|
+
);
|
|
1085
|
+
|
|
1086
|
+
const buttons = screen.getAllByRole('button');
|
|
1087
|
+
// The aria-label should be formatted by intl
|
|
1088
|
+
expect(buttons[1]).toHaveAttribute('aria-label');
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
it('handles missing message keys gracefully', () => {
|
|
1092
|
+
render(
|
|
1093
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1094
|
+
<PreviewModeGroup
|
|
1095
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1096
|
+
onChange={mockOnChange}
|
|
1097
|
+
/>
|
|
1098
|
+
</IntlProvider>
|
|
1099
|
+
);
|
|
1100
|
+
|
|
1101
|
+
// Should still render without crashing even with missing messages
|
|
1102
|
+
const buttons = screen.getAllByRole('button');
|
|
1103
|
+
expect(buttons).toHaveLength(2);
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
it('works with different locale formats', () => {
|
|
1107
|
+
const frenchMessages = {
|
|
1108
|
+
'app.components.HtmlEditor.desktop': 'Vue Bureau',
|
|
1109
|
+
'app.components.HtmlEditor.mobile': 'Vue Mobile'
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
render(
|
|
1113
|
+
<IntlProvider locale="fr" messages={frenchMessages}>
|
|
1114
|
+
<PreviewModeGroup
|
|
1115
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1116
|
+
onChange={mockOnChange}
|
|
1117
|
+
/>
|
|
1118
|
+
</IntlProvider>
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
const buttons = screen.getAllByRole('button');
|
|
1122
|
+
expect(buttons).toHaveLength(2);
|
|
1123
|
+
expect(buttons[0]).toHaveAttribute('aria-label');
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
describe('Component Internal Functions', () => {
|
|
1128
|
+
it('handleChange function exists and can be called', () => {
|
|
1129
|
+
// Test the internal handleChange function by creating a scenario where it might be used
|
|
1130
|
+
const TestComponent = () => {
|
|
1131
|
+
const [value, setValue] = React.useState(PREVIEW_MODES.DESKTOP);
|
|
1132
|
+
|
|
1133
|
+
const handleChange = (newValue) => {
|
|
1134
|
+
setValue(newValue);
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
return (
|
|
1138
|
+
<PreviewModeGroup
|
|
1139
|
+
value={value}
|
|
1140
|
+
onChange={handleChange}
|
|
1141
|
+
/>
|
|
1142
|
+
);
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
render(
|
|
1146
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1147
|
+
<TestComponent />
|
|
1148
|
+
</IntlProvider>
|
|
1149
|
+
);
|
|
1150
|
+
|
|
1151
|
+
const buttons = screen.getAllByRole('button');
|
|
1152
|
+
fireEvent.click(buttons[1]);
|
|
1153
|
+
|
|
1154
|
+
// Should update the state through the handleChange function
|
|
1155
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
it('handles button click to change mode from desktop to mobile', () => {
|
|
1159
|
+
// Test button click interaction to change preview mode
|
|
1160
|
+
renderWithIntl(
|
|
1161
|
+
<PreviewModeGroup
|
|
1162
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1163
|
+
onChange={mockOnChange}
|
|
1164
|
+
/>
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1167
|
+
const buttons = screen.getAllByRole('button');
|
|
1168
|
+
const mobileButton = buttons[1]; // Second button is mobile
|
|
1169
|
+
|
|
1170
|
+
fireEvent.click(mobileButton);
|
|
1171
|
+
|
|
1172
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
it('changes preview mode through user interaction with mobile button', () => {
|
|
1176
|
+
// Test real component interaction - user clicking mobile button
|
|
1177
|
+
renderWithIntl(
|
|
1178
|
+
<PreviewModeGroup
|
|
1179
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1180
|
+
onChange={mockOnChange}
|
|
1181
|
+
/>
|
|
1182
|
+
);
|
|
1183
|
+
|
|
1184
|
+
// Find the mobile button (second button in the group)
|
|
1185
|
+
const buttons = screen.getAllByRole('button');
|
|
1186
|
+
const mobileButton = buttons[1];
|
|
1187
|
+
|
|
1188
|
+
// Verify initial state - desktop should be active
|
|
1189
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
1190
|
+
expect(mobileButton).not.toHaveClass('preview-mode-group__btn--active');
|
|
1191
|
+
|
|
1192
|
+
// Simulate user clicking the mobile button
|
|
1193
|
+
fireEvent.click(mobileButton);
|
|
1194
|
+
|
|
1195
|
+
// Assert that onChange was called with the correct mode
|
|
1196
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1197
|
+
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
it('mock CapButton forwards value prop and other props to DOM button elements', () => {
|
|
1201
|
+
// Test that our mock CapButton properly forwards props
|
|
1202
|
+
const MockCapButton = jest.requireMock('@capillarytech/cap-ui-library/CapButton');
|
|
1203
|
+
|
|
1204
|
+
const TestComponent = () => (
|
|
1205
|
+
<MockCapButton
|
|
1206
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1207
|
+
onClick={mockOnChange}
|
|
1208
|
+
data-testid="test-button"
|
|
1209
|
+
custom-attribute="test-value"
|
|
1210
|
+
>
|
|
1211
|
+
Test Button
|
|
1212
|
+
</MockCapButton>
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1215
|
+
render(<TestComponent />);
|
|
1216
|
+
|
|
1217
|
+
const button = screen.getByTestId('test-button');
|
|
1218
|
+
|
|
1219
|
+
// Check that value and custom attributes are forwarded via ...rest
|
|
1220
|
+
expect(button).toHaveAttribute('value', PREVIEW_MODES.DESKTOP);
|
|
1221
|
+
expect(button).toHaveAttribute('custom-attribute', 'test-value');
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
it('enables event.target.value access in event handlers', () => {
|
|
1225
|
+
const handleChangeWithEvent = jest.fn((e) => {
|
|
1226
|
+
// This test verifies that e.target.value is available
|
|
1227
|
+
expect(e.target.value).toBeDefined();
|
|
1228
|
+
mockOnChange(e.target.value);
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
const TestComponent = () => {
|
|
1232
|
+
return (
|
|
1233
|
+
<div>
|
|
1234
|
+
<button
|
|
1235
|
+
value={PREVIEW_MODES.MOBILE}
|
|
1236
|
+
onClick={handleChangeWithEvent}
|
|
1237
|
+
data-testid="test-button"
|
|
1238
|
+
>
|
|
1239
|
+
Test Button
|
|
1240
|
+
</button>
|
|
1241
|
+
</div>
|
|
1242
|
+
);
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
renderWithIntl(<TestComponent />);
|
|
1246
|
+
|
|
1247
|
+
const testButton = screen.getByTestId('test-button');
|
|
1248
|
+
fireEvent.click(testButton);
|
|
1249
|
+
|
|
1250
|
+
expect(handleChangeWithEvent).toHaveBeenCalled();
|
|
1251
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1252
|
+
});
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
describe('Branch Coverage Tests', () => {
|
|
1256
|
+
it('covers all conditional branches for desktop active state', () => {
|
|
1257
|
+
const { rerender } = renderWithIntl(
|
|
1258
|
+
<PreviewModeGroup
|
|
1259
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1260
|
+
onChange={mockOnChange}
|
|
1261
|
+
/>
|
|
1262
|
+
);
|
|
1263
|
+
|
|
1264
|
+
let buttons = screen.getAllByRole('button');
|
|
1265
|
+
// Desktop button should have active class
|
|
1266
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
1267
|
+
// Mobile button should not have active class
|
|
1268
|
+
expect(buttons[1]).not.toHaveClass('preview-mode-group__btn--active');
|
|
1269
|
+
|
|
1270
|
+
// Test the opposite condition
|
|
1271
|
+
rerender(
|
|
1272
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1273
|
+
<PreviewModeGroup
|
|
1274
|
+
value={PREVIEW_MODES.MOBILE}
|
|
1275
|
+
onChange={mockOnChange}
|
|
1276
|
+
/>
|
|
1277
|
+
</IntlProvider>
|
|
1278
|
+
);
|
|
1279
|
+
|
|
1280
|
+
buttons = screen.getAllByRole('button');
|
|
1281
|
+
// Desktop button should not have active class
|
|
1282
|
+
expect(buttons[0]).not.toHaveClass('preview-mode-group__btn--active');
|
|
1283
|
+
// Mobile button should have active class
|
|
1284
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
it('covers all conditional branches for mobile active state', () => {
|
|
1288
|
+
const { rerender } = renderWithIntl(
|
|
1289
|
+
<PreviewModeGroup
|
|
1290
|
+
value={PREVIEW_MODES.MOBILE}
|
|
1291
|
+
onChange={mockOnChange}
|
|
1292
|
+
/>
|
|
1293
|
+
);
|
|
1294
|
+
|
|
1295
|
+
let buttons = screen.getAllByRole('button');
|
|
1296
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
1297
|
+
expect(buttons[0]).not.toHaveClass('preview-mode-group__btn--active');
|
|
1298
|
+
|
|
1299
|
+
// Test aria-pressed attributes
|
|
1300
|
+
expect(buttons[1]).toHaveAttribute('aria-pressed', 'true');
|
|
1301
|
+
expect(buttons[0]).toHaveAttribute('aria-pressed', 'false');
|
|
1302
|
+
|
|
1303
|
+
// Switch to desktop to test the other branch
|
|
1304
|
+
rerender(
|
|
1305
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1306
|
+
<PreviewModeGroup
|
|
1307
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1308
|
+
onChange={mockOnChange}
|
|
1309
|
+
/>
|
|
1310
|
+
</IntlProvider>
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
buttons = screen.getAllByRole('button');
|
|
1314
|
+
expect(buttons[0]).toHaveAttribute('aria-pressed', 'true');
|
|
1315
|
+
expect(buttons[1]).toHaveAttribute('aria-pressed', 'false');
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
it('covers disabled prop branches', () => {
|
|
1319
|
+
const { rerender } = renderWithIntl(
|
|
1320
|
+
<PreviewModeGroup
|
|
1321
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1322
|
+
onChange={mockOnChange}
|
|
1323
|
+
disabled={true}
|
|
1324
|
+
/>
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
let buttons = screen.getAllByRole('button');
|
|
1328
|
+
buttons.forEach(button => {
|
|
1329
|
+
expect(button).toBeDisabled();
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
// Test enabled state
|
|
1333
|
+
rerender(
|
|
1334
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1335
|
+
<PreviewModeGroup
|
|
1336
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1337
|
+
onChange={mockOnChange}
|
|
1338
|
+
disabled={false}
|
|
1339
|
+
/>
|
|
1340
|
+
</IntlProvider>
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1343
|
+
buttons = screen.getAllByRole('button');
|
|
1344
|
+
buttons.forEach(button => {
|
|
1345
|
+
expect(button).not.toBeDisabled();
|
|
1346
|
+
});
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
it('covers className conditional branches thoroughly', () => {
|
|
1350
|
+
// Test all combinations of active states and class assignments
|
|
1351
|
+
const testCases = [
|
|
1352
|
+
{ value: PREVIEW_MODES.DESKTOP, expectedActiveIndex: 0 },
|
|
1353
|
+
{ value: PREVIEW_MODES.MOBILE, expectedActiveIndex: 1 }
|
|
1354
|
+
];
|
|
1355
|
+
|
|
1356
|
+
testCases.forEach(({ value, expectedActiveIndex }) => {
|
|
1357
|
+
const { unmount } = renderWithIntl(
|
|
1358
|
+
<PreviewModeGroup
|
|
1359
|
+
value={value}
|
|
1360
|
+
onChange={mockOnChange}
|
|
1361
|
+
/>
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1364
|
+
const buttons = screen.getAllByRole('button');
|
|
1365
|
+
|
|
1366
|
+
buttons.forEach((button, index) => {
|
|
1367
|
+
expect(button).toHaveClass('preview-mode-group__btn');
|
|
1368
|
+
|
|
1369
|
+
if (index === expectedActiveIndex) {
|
|
1370
|
+
expect(button).toHaveClass('preview-mode-group__btn--active');
|
|
1371
|
+
} else {
|
|
1372
|
+
expect(button).not.toHaveClass('preview-mode-group__btn--active');
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
unmount();
|
|
1377
|
+
});
|
|
1378
|
+
});
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
describe('Advanced Edge Cases', () => {
|
|
1382
|
+
it('handles component re-mounting', () => {
|
|
1383
|
+
const { unmount } = renderWithIntl(
|
|
1384
|
+
<PreviewModeGroup
|
|
1385
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1386
|
+
onChange={mockOnChange}
|
|
1387
|
+
/>
|
|
1388
|
+
);
|
|
1389
|
+
|
|
1390
|
+
unmount();
|
|
1391
|
+
|
|
1392
|
+
// Re-mount with new render call
|
|
1393
|
+
renderWithIntl(
|
|
1394
|
+
<PreviewModeGroup
|
|
1395
|
+
value={PREVIEW_MODES.MOBILE}
|
|
1396
|
+
onChange={mockOnChange}
|
|
1397
|
+
/>
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
const buttons = screen.getAllByRole('button');
|
|
1401
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
it('handles invalid PREVIEW_MODES values gracefully', () => {
|
|
1405
|
+
// Test with an invalid value
|
|
1406
|
+
expect(() => {
|
|
1407
|
+
renderWithIntl(
|
|
1408
|
+
<PreviewModeGroup
|
|
1409
|
+
value="invalid-mode"
|
|
1410
|
+
onChange={mockOnChange}
|
|
1411
|
+
/>
|
|
1412
|
+
);
|
|
1413
|
+
}).not.toThrow();
|
|
1414
|
+
|
|
1415
|
+
const buttons = screen.getAllByRole('button');
|
|
1416
|
+
expect(buttons).toHaveLength(2);
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
it('maintains functionality with complex prop combinations', () => {
|
|
1420
|
+
const complexProps = {
|
|
1421
|
+
value: PREVIEW_MODES.DESKTOP,
|
|
1422
|
+
onChange: mockOnChange,
|
|
1423
|
+
disabled: false
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
renderWithIntl(<PreviewModeGroup {...complexProps} />);
|
|
1427
|
+
|
|
1428
|
+
const buttons = screen.getAllByRole('button');
|
|
1429
|
+
fireEvent.click(buttons[1]);
|
|
1430
|
+
|
|
1431
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
it('handles onChange function replacement during lifecycle', () => {
|
|
1435
|
+
const firstHandler = jest.fn();
|
|
1436
|
+
const secondHandler = jest.fn();
|
|
1437
|
+
|
|
1438
|
+
const { rerender } = renderWithIntl(
|
|
1439
|
+
<PreviewModeGroup
|
|
1440
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1441
|
+
onChange={firstHandler}
|
|
1442
|
+
/>
|
|
1443
|
+
);
|
|
1444
|
+
|
|
1445
|
+
const buttons = screen.getAllByRole('button');
|
|
1446
|
+
fireEvent.click(buttons[1]);
|
|
1447
|
+
expect(firstHandler).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1448
|
+
|
|
1449
|
+
rerender(
|
|
1450
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1451
|
+
<PreviewModeGroup
|
|
1452
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1453
|
+
onChange={secondHandler}
|
|
1454
|
+
/>
|
|
1455
|
+
</IntlProvider>
|
|
1456
|
+
);
|
|
1457
|
+
|
|
1458
|
+
fireEvent.click(buttons[1]);
|
|
1459
|
+
expect(secondHandler).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1460
|
+
expect(firstHandler).toHaveBeenCalledTimes(1);
|
|
1461
|
+
});
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
describe('Component State Management', () => {
|
|
1465
|
+
it('maintains consistent state across re-renders', () => {
|
|
1466
|
+
const { rerender } = renderWithIntl(
|
|
1467
|
+
<PreviewModeGroup
|
|
1468
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1469
|
+
onChange={mockOnChange}
|
|
1470
|
+
/>
|
|
1471
|
+
);
|
|
1472
|
+
|
|
1473
|
+
// Multiple re-renders with same props
|
|
1474
|
+
for (let i = 0; i < 5; i++) {
|
|
1475
|
+
rerender(
|
|
1476
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1477
|
+
<PreviewModeGroup
|
|
1478
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1479
|
+
onChange={mockOnChange}
|
|
1480
|
+
/>
|
|
1481
|
+
</IntlProvider>
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
const buttons = screen.getAllByRole('button');
|
|
1486
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
it('handles state transitions correctly', () => {
|
|
1490
|
+
const states = [
|
|
1491
|
+
PREVIEW_MODES.DESKTOP,
|
|
1492
|
+
PREVIEW_MODES.MOBILE,
|
|
1493
|
+
PREVIEW_MODES.DESKTOP,
|
|
1494
|
+
PREVIEW_MODES.MOBILE
|
|
1495
|
+
];
|
|
1496
|
+
|
|
1497
|
+
const { rerender } = renderWithIntl(
|
|
1498
|
+
<PreviewModeGroup
|
|
1499
|
+
value={states[0]}
|
|
1500
|
+
onChange={mockOnChange}
|
|
1501
|
+
/>
|
|
1502
|
+
);
|
|
1503
|
+
|
|
1504
|
+
states.forEach((state, index) => {
|
|
1505
|
+
rerender(
|
|
1506
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1507
|
+
<PreviewModeGroup
|
|
1508
|
+
value={state}
|
|
1509
|
+
onChange={mockOnChange}
|
|
1510
|
+
/>
|
|
1511
|
+
</IntlProvider>
|
|
1512
|
+
);
|
|
1513
|
+
|
|
1514
|
+
const buttons = screen.getAllByRole('button');
|
|
1515
|
+
if (state === PREVIEW_MODES.DESKTOP) {
|
|
1516
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
1517
|
+
} else {
|
|
1518
|
+
expect(buttons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
});
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
describe('Real-world Usage Scenarios', () => {
|
|
1525
|
+
it('supports typical preview mode switching workflow', () => {
|
|
1526
|
+
const { rerender } = renderWithIntl(
|
|
1527
|
+
<PreviewModeGroup
|
|
1528
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1529
|
+
onChange={mockOnChange}
|
|
1530
|
+
/>
|
|
1531
|
+
);
|
|
1532
|
+
|
|
1533
|
+
const buttons = screen.getAllByRole('button');
|
|
1534
|
+
|
|
1535
|
+
// User starts with desktop, switches to mobile
|
|
1536
|
+
expect(buttons[0]).toHaveClass('preview-mode-group__btn--active');
|
|
1537
|
+
|
|
1538
|
+
fireEvent.click(buttons[1]);
|
|
1539
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1540
|
+
|
|
1541
|
+
// Simulate parent updating value to mobile
|
|
1542
|
+
rerender(
|
|
1543
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1544
|
+
<PreviewModeGroup
|
|
1545
|
+
value={PREVIEW_MODES.MOBILE}
|
|
1546
|
+
onChange={mockOnChange}
|
|
1547
|
+
/>
|
|
1548
|
+
</IntlProvider>
|
|
1549
|
+
);
|
|
1550
|
+
|
|
1551
|
+
const updatedButtons = screen.getAllByRole('button');
|
|
1552
|
+
expect(updatedButtons[1]).toHaveClass('preview-mode-group__btn--active');
|
|
1553
|
+
expect(updatedButtons[0]).not.toHaveClass('preview-mode-group__btn--active');
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
it('supports editor toolbar integration', () => {
|
|
1557
|
+
// Simulate being used within a larger toolbar
|
|
1558
|
+
const { container } = render(
|
|
1559
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1560
|
+
<div className="editor-toolbar">
|
|
1561
|
+
<div className="toolbar-section">
|
|
1562
|
+
<PreviewModeGroup
|
|
1563
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1564
|
+
onChange={mockOnChange}
|
|
1565
|
+
/>
|
|
1566
|
+
</div>
|
|
1567
|
+
</div>
|
|
1568
|
+
</IntlProvider>
|
|
1569
|
+
);
|
|
1570
|
+
|
|
1571
|
+
expect(container.querySelector('.editor-toolbar')).toBeInTheDocument();
|
|
1572
|
+
expect(container.querySelector('.preview-mode-group')).toBeInTheDocument();
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
it('supports responsive design scenarios', () => {
|
|
1576
|
+
renderWithIntl(
|
|
1577
|
+
<PreviewModeGroup
|
|
1578
|
+
value={PREVIEW_MODES.DESKTOP}
|
|
1579
|
+
onChange={mockOnChange}
|
|
1580
|
+
/>
|
|
1581
|
+
);
|
|
1582
|
+
|
|
1583
|
+
// Component should maintain functionality regardless of container size
|
|
1584
|
+
const buttons = screen.getAllByRole('button');
|
|
1585
|
+
expect(buttons).toHaveLength(2);
|
|
1586
|
+
|
|
1587
|
+
// Should work in different viewport scenarios
|
|
1588
|
+
fireEvent.click(buttons[1]);
|
|
1589
|
+
expect(mockOnChange).toHaveBeenCalledWith(PREVIEW_MODES.MOBILE);
|
|
1590
|
+
});
|
|
1591
|
+
});
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
|