@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,580 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLayoutState Hook Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the useLayoutState custom hook that manages editor layout state.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { render, screen, fireEvent, act } from '@testing-library/react';
|
|
9
|
+
import '@testing-library/jest-dom';
|
|
10
|
+
import { useLayoutState } from '../useLayoutState';
|
|
11
|
+
import { PREVIEW_MODES, LAYOUT } from '../../constants';
|
|
12
|
+
|
|
13
|
+
// Test wrapper component
|
|
14
|
+
const TestComponent = ({ initialLayout, onStateChange }) => {
|
|
15
|
+
const layoutState = useLayoutState(initialLayout);
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (onStateChange) {
|
|
19
|
+
onStateChange(layoutState);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div>
|
|
25
|
+
<div data-testid="split-size">{layoutState.splitSize}</div>
|
|
26
|
+
<div data-testid="view-mode">{layoutState.viewMode}</div>
|
|
27
|
+
<div data-testid="mobile-width">{layoutState.mobileWidth}</div>
|
|
28
|
+
<div data-testid="is-fullscreen">{String(layoutState.isFullscreen)}</div>
|
|
29
|
+
<div data-testid="is-resizing">{String(layoutState.isResizing)}</div>
|
|
30
|
+
<div data-testid="is-mobile-view">{String(layoutState.isMobileView)}</div>
|
|
31
|
+
<div data-testid="is-desktop-view">{String(layoutState.isDesktopView)}</div>
|
|
32
|
+
|
|
33
|
+
<button onClick={() => layoutState.setSplitSize(60)} data-testid="set-split">
|
|
34
|
+
Set Split
|
|
35
|
+
</button>
|
|
36
|
+
<button onClick={() => layoutState.setViewMode(PREVIEW_MODES.MOBILE)} data-testid="set-mobile">
|
|
37
|
+
Set Mobile
|
|
38
|
+
</button>
|
|
39
|
+
<button onClick={() => layoutState.toggleViewMode()} data-testid="toggle-view">
|
|
40
|
+
Toggle View
|
|
41
|
+
</button>
|
|
42
|
+
<button onClick={() => layoutState.setMobileWidth(400)} data-testid="set-width">
|
|
43
|
+
Set Width
|
|
44
|
+
</button>
|
|
45
|
+
<button onClick={() => layoutState.toggleFullscreen()} data-testid="toggle-fullscreen">
|
|
46
|
+
Toggle Fullscreen
|
|
47
|
+
</button>
|
|
48
|
+
<button onClick={() => layoutState.resetLayout()} data-testid="reset">
|
|
49
|
+
Reset
|
|
50
|
+
</button>
|
|
51
|
+
<button onClick={() => layoutState.setResizingState(true)} data-testid="start-resize">
|
|
52
|
+
Start Resize
|
|
53
|
+
</button>
|
|
54
|
+
<button onClick={() => layoutState.updateSplitSizes([40, 60])} data-testid="update-sizes">
|
|
55
|
+
Update Sizes
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
describe('useLayoutState', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
// Clear any existing event listeners
|
|
64
|
+
jest.clearAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
// Cleanup
|
|
69
|
+
jest.restoreAllMocks();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Initial State', () => {
|
|
73
|
+
it('initializes with default layout values', () => {
|
|
74
|
+
render(<TestComponent />);
|
|
75
|
+
|
|
76
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('50');
|
|
77
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.DESKTOP);
|
|
78
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent(String(LAYOUT.MOBILE_WIDTH_DEFAULT));
|
|
79
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('false');
|
|
80
|
+
expect(screen.getByTestId('is-resizing')).toHaveTextContent('false');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('accepts custom initial layout', () => {
|
|
84
|
+
const customLayout = {
|
|
85
|
+
splitSize: 30,
|
|
86
|
+
viewMode: PREVIEW_MODES.MOBILE,
|
|
87
|
+
mobileWidth: 400,
|
|
88
|
+
isFullscreen: true
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
render(<TestComponent initialLayout={customLayout} />);
|
|
92
|
+
|
|
93
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('30');
|
|
94
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
95
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent('400');
|
|
96
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('true');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('computes isMobileView correctly', () => {
|
|
100
|
+
render(<TestComponent initialLayout={{ viewMode: PREVIEW_MODES.MOBILE }} />);
|
|
101
|
+
|
|
102
|
+
expect(screen.getByTestId('is-mobile-view')).toHaveTextContent('true');
|
|
103
|
+
expect(screen.getByTestId('is-desktop-view')).toHaveTextContent('false');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('computes isDesktopView correctly', () => {
|
|
107
|
+
render(<TestComponent initialLayout={{ viewMode: PREVIEW_MODES.DESKTOP }} />);
|
|
108
|
+
|
|
109
|
+
expect(screen.getByTestId('is-mobile-view')).toHaveTextContent('false');
|
|
110
|
+
expect(screen.getByTestId('is-desktop-view')).toHaveTextContent('true');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('setSplitSize', () => {
|
|
115
|
+
it('updates split size', () => {
|
|
116
|
+
render(<TestComponent />);
|
|
117
|
+
|
|
118
|
+
act(() => {
|
|
119
|
+
fireEvent.click(screen.getByTestId('set-split'));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('60');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('rejects split size below 10', () => {
|
|
126
|
+
let layoutState;
|
|
127
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
128
|
+
|
|
129
|
+
act(() => {
|
|
130
|
+
layoutState.setSplitSize(5);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('50'); // Should remain unchanged
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('rejects split size above 90', () => {
|
|
137
|
+
let layoutState;
|
|
138
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
139
|
+
|
|
140
|
+
act(() => {
|
|
141
|
+
layoutState.setSplitSize(95);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('50'); // Should remain unchanged
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('updates splitSizes array when split size changes', () => {
|
|
148
|
+
let layoutState;
|
|
149
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
150
|
+
|
|
151
|
+
act(() => {
|
|
152
|
+
layoutState.setSplitSize(70);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(layoutState.splitSizes).toEqual([70, 30]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('setViewMode', () => {
|
|
160
|
+
it('changes view mode to mobile', () => {
|
|
161
|
+
render(<TestComponent />);
|
|
162
|
+
|
|
163
|
+
act(() => {
|
|
164
|
+
fireEvent.click(screen.getByTestId('set-mobile'));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('ignores invalid view modes', () => {
|
|
171
|
+
let layoutState;
|
|
172
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
173
|
+
|
|
174
|
+
act(() => {
|
|
175
|
+
layoutState.setViewMode('invalid-mode');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.DESKTOP); // Should remain unchanged
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('accepts all valid preview modes', () => {
|
|
182
|
+
let layoutState;
|
|
183
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
184
|
+
|
|
185
|
+
Object.values(PREVIEW_MODES).forEach(mode => {
|
|
186
|
+
act(() => {
|
|
187
|
+
layoutState.setViewMode(mode);
|
|
188
|
+
});
|
|
189
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(mode);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('toggleViewMode', () => {
|
|
195
|
+
it('toggles from desktop to mobile', () => {
|
|
196
|
+
render(<TestComponent initialLayout={{ viewMode: PREVIEW_MODES.DESKTOP }} />);
|
|
197
|
+
|
|
198
|
+
act(() => {
|
|
199
|
+
fireEvent.click(screen.getByTestId('toggle-view'));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('toggles from mobile to desktop', () => {
|
|
206
|
+
render(<TestComponent initialLayout={{ viewMode: PREVIEW_MODES.MOBILE }} />);
|
|
207
|
+
|
|
208
|
+
act(() => {
|
|
209
|
+
fireEvent.click(screen.getByTestId('toggle-view'));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.DESKTOP);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('toggles multiple times correctly', () => {
|
|
216
|
+
render(<TestComponent />);
|
|
217
|
+
|
|
218
|
+
act(() => {
|
|
219
|
+
fireEvent.click(screen.getByTestId('toggle-view'));
|
|
220
|
+
});
|
|
221
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
222
|
+
|
|
223
|
+
act(() => {
|
|
224
|
+
fireEvent.click(screen.getByTestId('toggle-view'));
|
|
225
|
+
});
|
|
226
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.DESKTOP);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('setMobileWidth', () => {
|
|
231
|
+
it('updates mobile width', () => {
|
|
232
|
+
render(<TestComponent />);
|
|
233
|
+
|
|
234
|
+
act(() => {
|
|
235
|
+
fireEvent.click(screen.getByTestId('set-width'));
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent('400');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('constrains width to minimum 320', () => {
|
|
242
|
+
let layoutState;
|
|
243
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
244
|
+
|
|
245
|
+
act(() => {
|
|
246
|
+
layoutState.setMobileWidth(200);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent('320');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('constrains width to maximum 768', () => {
|
|
253
|
+
let layoutState;
|
|
254
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
255
|
+
|
|
256
|
+
act(() => {
|
|
257
|
+
layoutState.setMobileWidth(1000);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent('768');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('accepts width within valid range', () => {
|
|
264
|
+
let layoutState;
|
|
265
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
266
|
+
|
|
267
|
+
const validWidths = [320, 375, 400, 500, 600, 768];
|
|
268
|
+
validWidths.forEach(width => {
|
|
269
|
+
act(() => {
|
|
270
|
+
layoutState.setMobileWidth(width);
|
|
271
|
+
});
|
|
272
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent(String(width));
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('toggleFullscreen', () => {
|
|
278
|
+
it('toggles fullscreen on', () => {
|
|
279
|
+
render(<TestComponent />);
|
|
280
|
+
|
|
281
|
+
act(() => {
|
|
282
|
+
fireEvent.click(screen.getByTestId('toggle-fullscreen'));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('true');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('toggles fullscreen off', () => {
|
|
289
|
+
render(<TestComponent initialLayout={{ isFullscreen: true }} />);
|
|
290
|
+
|
|
291
|
+
act(() => {
|
|
292
|
+
fireEvent.click(screen.getByTestId('toggle-fullscreen'));
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('false');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('toggles multiple times', () => {
|
|
299
|
+
render(<TestComponent />);
|
|
300
|
+
|
|
301
|
+
act(() => {
|
|
302
|
+
fireEvent.click(screen.getByTestId('toggle-fullscreen'));
|
|
303
|
+
});
|
|
304
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('true');
|
|
305
|
+
|
|
306
|
+
act(() => {
|
|
307
|
+
fireEvent.click(screen.getByTestId('toggle-fullscreen'));
|
|
308
|
+
});
|
|
309
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('false');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('resetLayout', () => {
|
|
314
|
+
it('resets layout to initial defaults', () => {
|
|
315
|
+
// Note: resetLayout resets to the initial layout provided to the hook
|
|
316
|
+
// If custom initial layout was provided, it resets to that
|
|
317
|
+
const initialLayout = {
|
|
318
|
+
splitSize: 70,
|
|
319
|
+
viewMode: PREVIEW_MODES.MOBILE,
|
|
320
|
+
mobileWidth: 600,
|
|
321
|
+
isFullscreen: true
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
let layoutState;
|
|
325
|
+
render(<TestComponent
|
|
326
|
+
initialLayout={initialLayout}
|
|
327
|
+
onStateChange={(state) => { layoutState = state; }}
|
|
328
|
+
/>);
|
|
329
|
+
|
|
330
|
+
// Change some values
|
|
331
|
+
act(() => {
|
|
332
|
+
layoutState.setSplitSize(30);
|
|
333
|
+
layoutState.setViewMode(PREVIEW_MODES.DESKTOP);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Verify values changed
|
|
337
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('30');
|
|
338
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.DESKTOP);
|
|
339
|
+
|
|
340
|
+
// Reset should go back to initial values
|
|
341
|
+
act(() => {
|
|
342
|
+
fireEvent.click(screen.getByTestId('reset'));
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('70');
|
|
346
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
347
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent('600');
|
|
348
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('true');
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('setResizingState', () => {
|
|
353
|
+
it('sets resizing to true', () => {
|
|
354
|
+
render(<TestComponent />);
|
|
355
|
+
|
|
356
|
+
act(() => {
|
|
357
|
+
fireEvent.click(screen.getByTestId('start-resize'));
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
expect(screen.getByTestId('is-resizing')).toHaveTextContent('true');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('sets resizing to false', () => {
|
|
364
|
+
let layoutState;
|
|
365
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
366
|
+
|
|
367
|
+
act(() => {
|
|
368
|
+
layoutState.setResizingState(true);
|
|
369
|
+
});
|
|
370
|
+
expect(screen.getByTestId('is-resizing')).toHaveTextContent('true');
|
|
371
|
+
|
|
372
|
+
act(() => {
|
|
373
|
+
layoutState.setResizingState(false);
|
|
374
|
+
});
|
|
375
|
+
expect(screen.getByTestId('is-resizing')).toHaveTextContent('false');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('updateSplitSizes', () => {
|
|
380
|
+
it('updates split sizes array', () => {
|
|
381
|
+
let layoutState;
|
|
382
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
383
|
+
|
|
384
|
+
act(() => {
|
|
385
|
+
fireEvent.click(screen.getByTestId('update-sizes'));
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
expect(layoutState.splitSizes).toEqual([40, 60]);
|
|
389
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('40');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('rejects sizes below minimum', () => {
|
|
393
|
+
let layoutState;
|
|
394
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
395
|
+
|
|
396
|
+
act(() => {
|
|
397
|
+
layoutState.updateSplitSizes([10, 90]);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('50'); // Should remain unchanged
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('rejects sizes above maximum', () => {
|
|
404
|
+
let layoutState;
|
|
405
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
406
|
+
|
|
407
|
+
act(() => {
|
|
408
|
+
layoutState.updateSplitSizes([85, 15]);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('50'); // Should remain unchanged
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('rejects sizes that do not add up to 100', () => {
|
|
415
|
+
let layoutState;
|
|
416
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
417
|
+
|
|
418
|
+
act(() => {
|
|
419
|
+
layoutState.updateSplitSizes([50, 45]);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('50'); // Should remain unchanged
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('handleResize', () => {
|
|
427
|
+
it('calculates new sizes based on delta', () => {
|
|
428
|
+
let layoutState;
|
|
429
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
430
|
+
|
|
431
|
+
act(() => {
|
|
432
|
+
layoutState.handleResize(100, 1000); // 10% increase
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
expect(layoutState.splitSizes[0]).toBeCloseTo(60, 0);
|
|
436
|
+
expect(layoutState.splitSizes[1]).toBeCloseTo(40, 0);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('respects minimum pane size constraints', () => {
|
|
440
|
+
let layoutState;
|
|
441
|
+
render(<TestComponent
|
|
442
|
+
initialLayout={{ splitSizes: [25, 75], splitSize: 25 }}
|
|
443
|
+
onStateChange={(state) => { layoutState = state; }}
|
|
444
|
+
/>);
|
|
445
|
+
|
|
446
|
+
act(() => {
|
|
447
|
+
layoutState.handleResize(-100, 1000); // Try to shrink below min
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
expect(layoutState.splitSizes[0]).toBeGreaterThanOrEqual(LAYOUT.MIN_PANE_SIZE);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('ignores zero container size', () => {
|
|
454
|
+
let layoutState;
|
|
455
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
456
|
+
|
|
457
|
+
const initialSizes = [...layoutState.splitSizes];
|
|
458
|
+
|
|
459
|
+
act(() => {
|
|
460
|
+
layoutState.handleResize(100, 0);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
expect(layoutState.splitSizes).toEqual(initialSizes); // Should remain unchanged
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe('Computed Properties', () => {
|
|
468
|
+
it('exposes layout constraints', () => {
|
|
469
|
+
let layoutState;
|
|
470
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
471
|
+
|
|
472
|
+
expect(layoutState.minPaneSize).toBe(LAYOUT.MIN_PANE_SIZE);
|
|
473
|
+
expect(layoutState.maxPaneSize).toBe(LAYOUT.MAX_PANE_SIZE);
|
|
474
|
+
expect(layoutState.gutterSize).toBe(LAYOUT.GUTTER_SIZE);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
describe('Keyboard Shortcuts', () => {
|
|
479
|
+
it('handles F11 for fullscreen', () => {
|
|
480
|
+
render(<TestComponent />);
|
|
481
|
+
|
|
482
|
+
act(() => {
|
|
483
|
+
const event = new KeyboardEvent('keydown', { key: 'F11' });
|
|
484
|
+
document.dispatchEvent(event);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
expect(screen.getByTestId('is-fullscreen')).toHaveTextContent('true');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('handles Ctrl+P for view toggle', () => {
|
|
491
|
+
render(<TestComponent />);
|
|
492
|
+
|
|
493
|
+
act(() => {
|
|
494
|
+
const event = new KeyboardEvent('keydown', { key: 'p', ctrlKey: true });
|
|
495
|
+
document.dispatchEvent(event);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('handles Cmd+P for view toggle on Mac', () => {
|
|
502
|
+
render(<TestComponent />);
|
|
503
|
+
|
|
504
|
+
act(() => {
|
|
505
|
+
const event = new KeyboardEvent('keydown', { key: 'p', metaKey: true });
|
|
506
|
+
document.dispatchEvent(event);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
describe('Responsive Behavior', () => {
|
|
514
|
+
it('switches to mobile view on small screens', () => {
|
|
515
|
+
// Mock window.innerWidth
|
|
516
|
+
Object.defineProperty(window, 'innerWidth', {
|
|
517
|
+
writable: true,
|
|
518
|
+
configurable: true,
|
|
519
|
+
value: 500 // Below MOBILE_BREAKPOINT
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
render(<TestComponent initialLayout={{ viewMode: PREVIEW_MODES.DESKTOP }} />);
|
|
523
|
+
|
|
524
|
+
act(() => {
|
|
525
|
+
window.dispatchEvent(new Event('resize'));
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('does not auto-switch when already in mobile view', () => {
|
|
532
|
+
Object.defineProperty(window, 'innerWidth', {
|
|
533
|
+
writable: true,
|
|
534
|
+
configurable: true,
|
|
535
|
+
value: 500
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
render(<TestComponent initialLayout={{ viewMode: PREVIEW_MODES.MOBILE }} />);
|
|
539
|
+
|
|
540
|
+
act(() => {
|
|
541
|
+
window.dispatchEvent(new Event('resize'));
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
expect(screen.getByTestId('view-mode')).toHaveTextContent(PREVIEW_MODES.MOBILE);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
describe('Edge Cases', () => {
|
|
549
|
+
it('handles boundary split sizes', () => {
|
|
550
|
+
let layoutState;
|
|
551
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
552
|
+
|
|
553
|
+
act(() => {
|
|
554
|
+
layoutState.setSplitSize(10); // Minimum
|
|
555
|
+
});
|
|
556
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('10');
|
|
557
|
+
|
|
558
|
+
act(() => {
|
|
559
|
+
layoutState.setSplitSize(90); // Maximum
|
|
560
|
+
});
|
|
561
|
+
expect(screen.getByTestId('split-size')).toHaveTextContent('90');
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('handles exact constraint boundaries for mobile width', () => {
|
|
565
|
+
let layoutState;
|
|
566
|
+
render(<TestComponent onStateChange={(state) => { layoutState = state; }} />);
|
|
567
|
+
|
|
568
|
+
act(() => {
|
|
569
|
+
layoutState.setMobileWidth(320); // Min
|
|
570
|
+
});
|
|
571
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent('320');
|
|
572
|
+
|
|
573
|
+
act(() => {
|
|
574
|
+
layoutState.setMobileWidth(768); // Max
|
|
575
|
+
});
|
|
576
|
+
expect(screen.getByTestId('mobile-width')).toHaveTextContent('768');
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
|