@ccheever/exact-renderer 0.1.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/package.json +118 -0
- package/src/__tests__/adapter-window-state.test.tsx +190 -0
- package/src/__tests__/attrs.test.ts +157 -0
- package/src/__tests__/classname.test.ts +332 -0
- package/src/__tests__/color.test.ts +169 -0
- package/src/__tests__/dom-mirror.test.ts +682 -0
- package/src/__tests__/dom-shim.test.ts +274 -0
- package/src/__tests__/fixtures/SvelteCounter.svelte +7 -0
- package/src/__tests__/fixtures/SvelteInput.svelte +8 -0
- package/src/__tests__/host-config.test.ts +51 -0
- package/src/__tests__/host-ops.test.ts +2234 -0
- package/src/__tests__/image-source.test.ts +135 -0
- package/src/__tests__/liquid-glass.test.ts +72 -0
- package/src/__tests__/multi-root.test.ts +118 -0
- package/src/__tests__/native-view-events.test.ts +102 -0
- package/src/__tests__/nodes.test.ts +399 -0
- package/src/__tests__/normalize.test.ts +576 -0
- package/src/__tests__/paragraph-lowering.test.tsx +144 -0
- package/src/__tests__/props.test.ts +518 -0
- package/src/__tests__/protocol-encoder.test.ts +732 -0
- package/src/__tests__/protocol-fixture-bytes.test.ts +41 -0
- package/src/__tests__/reconciler.test.tsx +241 -0
- package/src/__tests__/svelte-adapter.test.ts +166 -0
- package/src/__tests__/svg-source.test.ts +71 -0
- package/src/__tests__/tags.test.ts +354 -0
- package/src/__tests__/toggle.test.ts +441 -0
- package/src/__tests__/transitions.test.ts +106 -0
- package/src/__tests__/web-primitives.test.tsx +454 -0
- package/src/__tests__/window-hooks.test.tsx +447 -0
- package/src/adapter-contract.ts +68 -0
- package/src/attrs.ts +596 -0
- package/src/classname-contract.ts +87 -0
- package/src/classname-resolve.ts +553 -0
- package/src/classname-runtime.ts +29 -0
- package/src/components.ts +214 -0
- package/src/css-variable-context.ts +83 -0
- package/src/dom-hydration.ts +160 -0
- package/src/dom-mirror.ts +1459 -0
- package/src/dom-shim.ts +1736 -0
- package/src/group-context.ts +69 -0
- package/src/host-config.ts +431 -0
- package/src/host-ops.ts +3167 -0
- package/src/image-source.native.ts +703 -0
- package/src/image-source.ts +554 -0
- package/src/index.ts +278 -0
- package/src/inspector-runtime.ts +244 -0
- package/src/inspector.ts +3570 -0
- package/src/jsx-augmentations.ts +54 -0
- package/src/keyboard-avoidance.ts +217 -0
- package/src/native-primitives.ts +43 -0
- package/src/native-view-events.ts +322 -0
- package/src/native-view.ts +60 -0
- package/src/nodes/index.ts +41 -0
- package/src/nodes/node.ts +531 -0
- package/src/peer-context.ts +100 -0
- package/src/primitives.native.ts +8 -0
- package/src/primitives.ts +8 -0
- package/src/props/index.ts +14 -0
- package/src/props/normalize.ts +816 -0
- package/src/protocol/encoder.ts +940 -0
- package/src/protocol/index.ts +33 -0
- package/src/reconciler.ts +581 -0
- package/src/runtime.ts +11 -0
- package/src/safe-area.ts +543 -0
- package/src/solid.ts +490 -0
- package/src/style/color.js +1 -0
- package/src/style/color.ts +15 -0
- package/src/style/index.js +1 -0
- package/src/style/index.ts +22 -0
- package/src/style/normalize.js +1 -0
- package/src/style/normalize.ts +1426 -0
- package/src/svelte.ts +349 -0
- package/src/svg-source.ts +222 -0
- package/src/tags/index.ts +21 -0
- package/src/tags/tag-map.ts +289 -0
- package/src/text/paragraph-lowering.ts +310 -0
- package/src/types.ts +1175 -0
- package/src/vue.ts +535 -0
- package/src/web-host.ts +19 -0
- package/src/web-primitives.ts +1654 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle/Switch Component Tests
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive tests for the Toggle native element implementation.
|
|
5
|
+
* Tests cover: props normalization, tag mapping, controlled/uncontrolled state,
|
|
6
|
+
* event handling, accessibility, and styling.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import {
|
|
11
|
+
getTagConfig,
|
|
12
|
+
isValidTag,
|
|
13
|
+
} from '../tags/tag-map.js';
|
|
14
|
+
import { normalizeProps } from '../props/normalize.js';
|
|
15
|
+
import { NodeType } from '@exact/core';
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Tag Configuration Tests
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
describe('Toggle tag configuration', () => {
|
|
22
|
+
describe('web-style toggle tag', () => {
|
|
23
|
+
it('should have correct config for lowercase toggle', () => {
|
|
24
|
+
const config = getTagConfig('toggle');
|
|
25
|
+
expect(config).not.toBeNull();
|
|
26
|
+
expect(config!.canonicalType).toBe('nativeView');
|
|
27
|
+
expect(config!.nodeType).toBe(NodeType.NativeView);
|
|
28
|
+
expect(config!.nativeView?.moduleName).toBe('exact.toggle');
|
|
29
|
+
expect(config!.isRNStyle).toBe(false);
|
|
30
|
+
expect(config!.defaultFlexDirection).toBe('row');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should support change events', () => {
|
|
34
|
+
const config = getTagConfig('toggle');
|
|
35
|
+
expect(config!.supportsChangeEvents).toBe(true);
|
|
36
|
+
expect(config!.supportsPressEvents).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should not be a text container', () => {
|
|
40
|
+
const config = getTagConfig('toggle');
|
|
41
|
+
expect(config!.isTextContainer).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('RN-style Toggle component', () => {
|
|
46
|
+
it('should have correct config for Toggle', () => {
|
|
47
|
+
const config = getTagConfig('Toggle');
|
|
48
|
+
expect(config).not.toBeNull();
|
|
49
|
+
expect(config!.canonicalType).toBe('nativeView');
|
|
50
|
+
expect(config!.nodeType).toBe(NodeType.NativeView);
|
|
51
|
+
expect(config!.nativeView?.moduleName).toBe('exact.toggle');
|
|
52
|
+
expect(config!.isRNStyle).toBe(true);
|
|
53
|
+
expect(config!.defaultFlexDirection).toBe('column');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should have correct config for Switch alias', () => {
|
|
57
|
+
const config = getTagConfig('Switch');
|
|
58
|
+
expect(config).not.toBeNull();
|
|
59
|
+
expect(config!.canonicalType).toBe('nativeView');
|
|
60
|
+
expect(config!.nodeType).toBe(NodeType.NativeView);
|
|
61
|
+
expect(config!.nativeView?.moduleName).toBe('exact.toggle');
|
|
62
|
+
expect(config!.isRNStyle).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should support change events for Toggle and Switch', () => {
|
|
66
|
+
expect(getTagConfig('Toggle')!.supportsChangeEvents).toBe(true);
|
|
67
|
+
expect(getTagConfig('Switch')!.supportsChangeEvents).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('tag validation', () => {
|
|
72
|
+
it('should validate toggle as a valid tag', () => {
|
|
73
|
+
expect(isValidTag('toggle')).toBe(true);
|
|
74
|
+
expect(isValidTag('Toggle')).toBe(true);
|
|
75
|
+
expect(isValidTag('Switch')).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Props Normalization Tests
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
describe('Toggle props normalization', () => {
|
|
85
|
+
describe('value props', () => {
|
|
86
|
+
it('should normalize boolean value prop', () => {
|
|
87
|
+
const result = normalizeProps('toggle', { value: true }, false);
|
|
88
|
+
expect(result.toggleValue).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should normalize false value prop', () => {
|
|
92
|
+
const result = normalizeProps('toggle', { value: false }, false);
|
|
93
|
+
expect(result.toggleValue).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should normalize defaultValue when value is not present', () => {
|
|
97
|
+
const result = normalizeProps('toggle', { defaultValue: true }, false);
|
|
98
|
+
expect(result.toggleValue).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should prefer value over defaultValue', () => {
|
|
102
|
+
const result = normalizeProps('toggle', { value: false, defaultValue: true }, false);
|
|
103
|
+
expect(result.toggleValue).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle undefined value gracefully', () => {
|
|
107
|
+
const result = normalizeProps('toggle', {}, false);
|
|
108
|
+
expect(result.toggleValue).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('styling props', () => {
|
|
113
|
+
it('should normalize glassEffect prop', () => {
|
|
114
|
+
const result = normalizeProps('toggle', { glassEffect: true }, false);
|
|
115
|
+
expect(result.glassEffect).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should normalize glassEffect false', () => {
|
|
119
|
+
const result = normalizeProps('toggle', { glassEffect: false }, false);
|
|
120
|
+
// The explicit off is the "false" spec string (not boolean false /
|
|
121
|
+
// omitted): the protocol has no prop-removal op, so the off must hit
|
|
122
|
+
// the wire to tear down a previously glassy node (ENG-22484). The
|
|
123
|
+
// scalar encoder stringifies booleans, so the wire value is identical.
|
|
124
|
+
expect(result.glassEffect).toBe('false');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should normalize tintColor prop', () => {
|
|
128
|
+
const result = normalizeProps('toggle', { tintColor: '#34d399' }, false);
|
|
129
|
+
expect(result.tintColor).toBe('#34d399');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should handle hex colors with alpha', () => {
|
|
133
|
+
const result = normalizeProps('toggle', { tintColor: '#34d39980' }, false);
|
|
134
|
+
expect(result.tintColor).toBe('#34d39980');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle rgb colors', () => {
|
|
138
|
+
const result = normalizeProps('toggle', { tintColor: 'rgb(52, 211, 153)' }, false);
|
|
139
|
+
expect(result.tintColor).toBe('rgb(52, 211, 153)');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('disabled prop', () => {
|
|
144
|
+
it('should normalize disabled prop true', () => {
|
|
145
|
+
const result = normalizeProps('toggle', { disabled: true }, false);
|
|
146
|
+
expect(result.disabled).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should normalize disabled prop false', () => {
|
|
150
|
+
const result = normalizeProps('toggle', { disabled: false }, false);
|
|
151
|
+
expect(result.disabled).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should not set disabled when undefined', () => {
|
|
155
|
+
const result = normalizeProps('toggle', {}, false);
|
|
156
|
+
expect(result.disabled).toBeUndefined();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('label props', () => {
|
|
161
|
+
it('should extract label from label prop', () => {
|
|
162
|
+
const result = normalizeProps('toggle', { label: 'Enable notifications' }, false);
|
|
163
|
+
expect(result.textContent).toBe('Enable notifications');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should extract label from string children', () => {
|
|
167
|
+
const result = normalizeProps('toggle', { children: 'Dark mode' }, false);
|
|
168
|
+
expect(result.textContent).toBe('Dark mode');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should prefer label prop over children', () => {
|
|
172
|
+
const result = normalizeProps('toggle', { label: 'From label', children: 'From children' }, false);
|
|
173
|
+
expect(result.textContent).toBe('From label');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should handle empty label', () => {
|
|
177
|
+
const result = normalizeProps('toggle', { label: '' }, false);
|
|
178
|
+
// Empty string is still a valid string, so it gets set
|
|
179
|
+
expect(result.textContent).toBe('');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('RN style props', () => {
|
|
184
|
+
it('should normalize props with RN style flag', () => {
|
|
185
|
+
const result = normalizeProps('toggle', {
|
|
186
|
+
value: true,
|
|
187
|
+
disabled: false,
|
|
188
|
+
tintColor: '#10b981'
|
|
189
|
+
}, true);
|
|
190
|
+
|
|
191
|
+
expect(result.toggleValue).toBe(true);
|
|
192
|
+
expect(result.disabled).toBe(false);
|
|
193
|
+
expect(result.tintColor).toBe('#10b981');
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('combined props', () => {
|
|
198
|
+
it('should normalize all props together', () => {
|
|
199
|
+
const result = normalizeProps('toggle', {
|
|
200
|
+
value: true,
|
|
201
|
+
disabled: false,
|
|
202
|
+
glassEffect: true,
|
|
203
|
+
tintColor: '#34d399',
|
|
204
|
+
label: 'Notifications',
|
|
205
|
+
}, false);
|
|
206
|
+
|
|
207
|
+
expect(result.toggleValue).toBe(true);
|
|
208
|
+
expect(result.disabled).toBe(false);
|
|
209
|
+
expect(result.glassEffect).toBe(true);
|
|
210
|
+
expect(result.tintColor).toBe('#34d399');
|
|
211
|
+
expect(result.textContent).toBe('Notifications');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// =============================================================================
|
|
217
|
+
// NodeType Tests
|
|
218
|
+
// =============================================================================
|
|
219
|
+
|
|
220
|
+
describe('Toggle NativeView routing', () => {
|
|
221
|
+
it('should route through NativeView instead of a dedicated NodeType', () => {
|
|
222
|
+
expect(getTagConfig('Toggle')!.nodeType).toBe(NodeType.NativeView);
|
|
223
|
+
expect(getTagConfig('Switch')!.nodeType).toBe(NodeType.NativeView);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should preserve the exact.toggle module contract', () => {
|
|
227
|
+
const metadata = getTagConfig('Toggle')!.nativeView;
|
|
228
|
+
expect(metadata?.moduleName).toBe('exact.toggle');
|
|
229
|
+
expect(metadata?.propKeys).toEqual([
|
|
230
|
+
'value',
|
|
231
|
+
'tintColor',
|
|
232
|
+
'disabled',
|
|
233
|
+
'glassEffect',
|
|
234
|
+
'accessibilityLabel',
|
|
235
|
+
]);
|
|
236
|
+
expect(metadata?.selection).toEqual({
|
|
237
|
+
tier: 'delegated',
|
|
238
|
+
gesturePolicy: 'internal',
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// =============================================================================
|
|
244
|
+
// Controlled vs Uncontrolled State Tests
|
|
245
|
+
// =============================================================================
|
|
246
|
+
|
|
247
|
+
describe('Toggle controlled/uncontrolled behavior', () => {
|
|
248
|
+
describe('controlled mode (value prop)', () => {
|
|
249
|
+
it('should use value when provided', () => {
|
|
250
|
+
const result = normalizeProps('toggle', { value: true }, false);
|
|
251
|
+
expect(result.toggleValue).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should ignore defaultValue when value is provided', () => {
|
|
255
|
+
const result = normalizeProps('toggle', { value: false, defaultValue: true }, false);
|
|
256
|
+
expect(result.toggleValue).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should update when value changes to false', () => {
|
|
260
|
+
const resultTrue = normalizeProps('toggle', { value: true }, false);
|
|
261
|
+
const resultFalse = normalizeProps('toggle', { value: false }, false);
|
|
262
|
+
|
|
263
|
+
expect(resultTrue.toggleValue).toBe(true);
|
|
264
|
+
expect(resultFalse.toggleValue).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('uncontrolled mode (defaultValue prop)', () => {
|
|
269
|
+
it('should use defaultValue when value is not provided', () => {
|
|
270
|
+
const result = normalizeProps('toggle', { defaultValue: true }, false);
|
|
271
|
+
expect(result.toggleValue).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should have undefined toggleValue when neither value nor defaultValue provided', () => {
|
|
275
|
+
const result = normalizeProps('toggle', {}, false);
|
|
276
|
+
expect(result.toggleValue).toBeUndefined();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// =============================================================================
|
|
282
|
+
// Accessibility Props Tests
|
|
283
|
+
// =============================================================================
|
|
284
|
+
|
|
285
|
+
describe('Toggle accessibility', () => {
|
|
286
|
+
it('should extract accessibilityLabel', () => {
|
|
287
|
+
const result = normalizeProps('toggle', {
|
|
288
|
+
accessibilityLabel: 'Toggle dark mode'
|
|
289
|
+
}, false);
|
|
290
|
+
expect(result.accessibilityLabel).toBe('Toggle dark mode');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should not set accessibilityLabel when not provided', () => {
|
|
294
|
+
const result = normalizeProps('toggle', { value: true }, false);
|
|
295
|
+
expect(result.accessibilityLabel).toBeUndefined();
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// =============================================================================
|
|
300
|
+
// Track Color Tests (RN compatibility)
|
|
301
|
+
// =============================================================================
|
|
302
|
+
|
|
303
|
+
describe('Toggle track colors', () => {
|
|
304
|
+
it('should handle trackColor object format', () => {
|
|
305
|
+
// Note: The normalizeProps currently doesn't process trackColor,
|
|
306
|
+
// but we test the prop extraction pattern
|
|
307
|
+
const props = {
|
|
308
|
+
trackColor: { false: '#767577', true: '#81b0ff' }
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// Verify the structure is correct
|
|
312
|
+
expect(props.trackColor.false).toBe('#767577');
|
|
313
|
+
expect(props.trackColor.true).toBe('#81b0ff');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should handle simple tintColor', () => {
|
|
317
|
+
const result = normalizeProps('toggle', { tintColor: '#10b981' }, false);
|
|
318
|
+
expect(result.tintColor).toBe('#10b981');
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// =============================================================================
|
|
323
|
+
// Event Callback Tests
|
|
324
|
+
// =============================================================================
|
|
325
|
+
|
|
326
|
+
describe('Toggle event callbacks', () => {
|
|
327
|
+
it('should accept onValueChange callback in props', () => {
|
|
328
|
+
const callback = (value: boolean) => console.log(value);
|
|
329
|
+
const props = { value: true, onValueChange: callback };
|
|
330
|
+
|
|
331
|
+
// The props should pass through correctly
|
|
332
|
+
expect(typeof props.onValueChange).toBe('function');
|
|
333
|
+
expect(props.value).toBe(true);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should accept onFocus callback', () => {
|
|
337
|
+
const callback = () => console.log('focused');
|
|
338
|
+
const props = { onFocus: callback };
|
|
339
|
+
expect(typeof props.onFocus).toBe('function');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should accept onBlur callback', () => {
|
|
343
|
+
const callback = () => console.log('blurred');
|
|
344
|
+
const props = { onBlur: callback };
|
|
345
|
+
expect(typeof props.onBlur).toBe('function');
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// =============================================================================
|
|
350
|
+
// Integration Tests - Full Component Props
|
|
351
|
+
// =============================================================================
|
|
352
|
+
|
|
353
|
+
describe('Toggle full integration', () => {
|
|
354
|
+
it('should normalize a complete Toggle configuration', () => {
|
|
355
|
+
const result = normalizeProps('toggle', {
|
|
356
|
+
value: true,
|
|
357
|
+
defaultValue: false, // Should be ignored
|
|
358
|
+
disabled: false,
|
|
359
|
+
glassEffect: true,
|
|
360
|
+
tintColor: '#34d399',
|
|
361
|
+
label: 'Enable feature',
|
|
362
|
+
accessibilityLabel: 'Enable feature toggle',
|
|
363
|
+
}, false);
|
|
364
|
+
|
|
365
|
+
expect(result.toggleValue).toBe(true);
|
|
366
|
+
expect(result.disabled).toBe(false);
|
|
367
|
+
expect(result.glassEffect).toBe(true);
|
|
368
|
+
expect(result.tintColor).toBe('#34d399');
|
|
369
|
+
expect(result.textContent).toBe('Enable feature');
|
|
370
|
+
expect(result.accessibilityLabel).toBe('Enable feature toggle');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should normalize minimal Toggle props', () => {
|
|
374
|
+
const result = normalizeProps('toggle', {}, false);
|
|
375
|
+
|
|
376
|
+
// All optional props should be undefined
|
|
377
|
+
expect(result.toggleValue).toBeUndefined();
|
|
378
|
+
expect(result.disabled).toBeUndefined();
|
|
379
|
+
expect(result.glassEffect).toBeUndefined();
|
|
380
|
+
expect(result.tintColor).toBeUndefined();
|
|
381
|
+
expect(result.textContent).toBeUndefined();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should handle web-style toggle with iOS glass effect', () => {
|
|
385
|
+
const result = normalizeProps('toggle', {
|
|
386
|
+
value: false,
|
|
387
|
+
glassEffect: true,
|
|
388
|
+
tintColor: '#6366f1',
|
|
389
|
+
}, false);
|
|
390
|
+
|
|
391
|
+
expect(result.toggleValue).toBe(false);
|
|
392
|
+
expect(result.glassEffect).toBe(true);
|
|
393
|
+
expect(result.tintColor).toBe('#6366f1');
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('should handle RN-style Switch props', () => {
|
|
397
|
+
const result = normalizeProps('toggle', {
|
|
398
|
+
value: true,
|
|
399
|
+
disabled: true,
|
|
400
|
+
tintColor: '#ef4444',
|
|
401
|
+
}, true);
|
|
402
|
+
|
|
403
|
+
expect(result.toggleValue).toBe(true);
|
|
404
|
+
expect(result.disabled).toBe(true);
|
|
405
|
+
expect(result.tintColor).toBe('#ef4444');
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// =============================================================================
|
|
410
|
+
// Edge Cases
|
|
411
|
+
// =============================================================================
|
|
412
|
+
|
|
413
|
+
describe('Toggle edge cases', () => {
|
|
414
|
+
it('should handle non-boolean value gracefully', () => {
|
|
415
|
+
// @ts-expect-error - Testing invalid prop type
|
|
416
|
+
const result = normalizeProps('toggle', { value: 'true' }, false);
|
|
417
|
+
expect(result.toggleValue).toBeUndefined();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('should handle non-string tintColor gracefully', () => {
|
|
421
|
+
// @ts-expect-error - Testing invalid prop type
|
|
422
|
+
const result = normalizeProps('toggle', { tintColor: 123 }, false);
|
|
423
|
+
expect(result.tintColor).toBeUndefined();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should handle non-boolean disabled gracefully', () => {
|
|
427
|
+
// @ts-expect-error - Testing invalid prop type
|
|
428
|
+
const result = normalizeProps('toggle', { disabled: 'true' }, false);
|
|
429
|
+
expect(result.disabled).toBeUndefined();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should handle numeric children gracefully', () => {
|
|
433
|
+
const result = normalizeProps('toggle', { children: 42 }, false);
|
|
434
|
+
expect(result.textContent).toBe('42');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should handle array children', () => {
|
|
438
|
+
const result = normalizeProps('toggle', { children: ['Enable ', 'feature'] }, false);
|
|
439
|
+
expect(result.textContent).toBe('Enable feature');
|
|
440
|
+
});
|
|
441
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { EventType } from '@exact/core/protocol/opcodes';
|
|
3
|
+
import { createInstance, getTagConfig, processEventProps, updateInstanceProps } from '../host-ops.js';
|
|
4
|
+
import { _resetNodeIdCounter } from '../nodes/index.js';
|
|
5
|
+
import { _clearHandlers, getHandler } from '../host-config.js';
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
_resetNodeIdCounter();
|
|
9
|
+
_clearHandlers();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('renderer transition integration', () => {
|
|
13
|
+
it('normalizes transitions during instance creation', () => {
|
|
14
|
+
const onTransitionEnd = vi.fn();
|
|
15
|
+
const instance = createInstance('View', {
|
|
16
|
+
style: {
|
|
17
|
+
opacity: 1,
|
|
18
|
+
transition: 'opacity 180ms ease-out, transform 220ms spring(damping: 16, stiffness: 220)',
|
|
19
|
+
},
|
|
20
|
+
onTransitionEnd,
|
|
21
|
+
});
|
|
22
|
+
// Event registration is deferred to commit (LLP 0159); simulate it here.
|
|
23
|
+
processEventProps(instance, instance.originalProps, getTagConfig('View')!);
|
|
24
|
+
|
|
25
|
+
expect(instance.transitions).toEqual({
|
|
26
|
+
opacity: {
|
|
27
|
+
type: 'timing',
|
|
28
|
+
duration: 180,
|
|
29
|
+
delay: 0,
|
|
30
|
+
easing: 'easeOut',
|
|
31
|
+
respectsReducedMotion: true,
|
|
32
|
+
},
|
|
33
|
+
transform: {
|
|
34
|
+
type: 'spring',
|
|
35
|
+
duration: 220,
|
|
36
|
+
delay: 0,
|
|
37
|
+
damping: 16,
|
|
38
|
+
stiffness: 220,
|
|
39
|
+
mass: 1,
|
|
40
|
+
velocity: 0,
|
|
41
|
+
respectsReducedMotion: true,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const binding = instance.events.get(EventType.TransitionEnd);
|
|
46
|
+
expect(binding).toBeDefined();
|
|
47
|
+
expect(getHandler(binding!.handlerId)).toBe(onTransitionEnd);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('updates transition metadata and preserves transition-end binding identity', () => {
|
|
51
|
+
const handler = vi.fn();
|
|
52
|
+
const instance = createInstance('View', {
|
|
53
|
+
style: {
|
|
54
|
+
opacity: 1,
|
|
55
|
+
transition: 'opacity 180ms ease-out',
|
|
56
|
+
},
|
|
57
|
+
onTransitionEnd: handler,
|
|
58
|
+
});
|
|
59
|
+
// Event registration is deferred to commit (LLP 0159); simulate it here.
|
|
60
|
+
processEventProps(instance, instance.originalProps, getTagConfig('View')!);
|
|
61
|
+
|
|
62
|
+
const binding = instance.events.get(EventType.TransitionEnd);
|
|
63
|
+
expect(binding).toBeDefined();
|
|
64
|
+
|
|
65
|
+
updateInstanceProps(
|
|
66
|
+
instance,
|
|
67
|
+
{
|
|
68
|
+
style: {
|
|
69
|
+
opacity: 1,
|
|
70
|
+
transition: 'opacity 180ms ease-out',
|
|
71
|
+
},
|
|
72
|
+
onTransitionEnd: handler,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
style: {
|
|
76
|
+
opacity: 0.5,
|
|
77
|
+
transition: {
|
|
78
|
+
opacity: { type: 'timing', duration: 200, easing: 'easeIn' },
|
|
79
|
+
backgroundColor: { type: 'timing', duration: 150, easing: 'linear' },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
onTransitionEnd: handler,
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(instance.transitions).toEqual({
|
|
87
|
+
opacity: {
|
|
88
|
+
type: 'timing',
|
|
89
|
+
duration: 200,
|
|
90
|
+
delay: 0,
|
|
91
|
+
easing: 'easeIn',
|
|
92
|
+
respectsReducedMotion: true,
|
|
93
|
+
},
|
|
94
|
+
backgroundColor: {
|
|
95
|
+
type: 'timing',
|
|
96
|
+
duration: 150,
|
|
97
|
+
delay: 0,
|
|
98
|
+
easing: 'linear',
|
|
99
|
+
respectsReducedMotion: true,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const updatedBinding = instance.events.get(EventType.TransitionEnd);
|
|
104
|
+
expect(updatedBinding?.handlerId).toBe(binding?.handlerId);
|
|
105
|
+
});
|
|
106
|
+
});
|