@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.
Files changed (80) hide show
  1. package/package.json +118 -0
  2. package/src/__tests__/adapter-window-state.test.tsx +190 -0
  3. package/src/__tests__/attrs.test.ts +157 -0
  4. package/src/__tests__/classname.test.ts +332 -0
  5. package/src/__tests__/color.test.ts +169 -0
  6. package/src/__tests__/dom-mirror.test.ts +682 -0
  7. package/src/__tests__/dom-shim.test.ts +274 -0
  8. package/src/__tests__/fixtures/SvelteCounter.svelte +7 -0
  9. package/src/__tests__/fixtures/SvelteInput.svelte +8 -0
  10. package/src/__tests__/host-config.test.ts +51 -0
  11. package/src/__tests__/host-ops.test.ts +2234 -0
  12. package/src/__tests__/image-source.test.ts +135 -0
  13. package/src/__tests__/liquid-glass.test.ts +72 -0
  14. package/src/__tests__/multi-root.test.ts +118 -0
  15. package/src/__tests__/native-view-events.test.ts +102 -0
  16. package/src/__tests__/nodes.test.ts +399 -0
  17. package/src/__tests__/normalize.test.ts +576 -0
  18. package/src/__tests__/paragraph-lowering.test.tsx +144 -0
  19. package/src/__tests__/props.test.ts +518 -0
  20. package/src/__tests__/protocol-encoder.test.ts +732 -0
  21. package/src/__tests__/protocol-fixture-bytes.test.ts +41 -0
  22. package/src/__tests__/reconciler.test.tsx +241 -0
  23. package/src/__tests__/svelte-adapter.test.ts +166 -0
  24. package/src/__tests__/svg-source.test.ts +71 -0
  25. package/src/__tests__/tags.test.ts +354 -0
  26. package/src/__tests__/toggle.test.ts +441 -0
  27. package/src/__tests__/transitions.test.ts +106 -0
  28. package/src/__tests__/web-primitives.test.tsx +454 -0
  29. package/src/__tests__/window-hooks.test.tsx +447 -0
  30. package/src/adapter-contract.ts +68 -0
  31. package/src/attrs.ts +596 -0
  32. package/src/classname-contract.ts +87 -0
  33. package/src/classname-resolve.ts +553 -0
  34. package/src/classname-runtime.ts +29 -0
  35. package/src/components.ts +214 -0
  36. package/src/css-variable-context.ts +83 -0
  37. package/src/dom-hydration.ts +160 -0
  38. package/src/dom-mirror.ts +1459 -0
  39. package/src/dom-shim.ts +1736 -0
  40. package/src/group-context.ts +69 -0
  41. package/src/host-config.ts +431 -0
  42. package/src/host-ops.ts +3167 -0
  43. package/src/image-source.native.ts +703 -0
  44. package/src/image-source.ts +554 -0
  45. package/src/index.ts +278 -0
  46. package/src/inspector-runtime.ts +244 -0
  47. package/src/inspector.ts +3570 -0
  48. package/src/jsx-augmentations.ts +54 -0
  49. package/src/keyboard-avoidance.ts +217 -0
  50. package/src/native-primitives.ts +43 -0
  51. package/src/native-view-events.ts +322 -0
  52. package/src/native-view.ts +60 -0
  53. package/src/nodes/index.ts +41 -0
  54. package/src/nodes/node.ts +531 -0
  55. package/src/peer-context.ts +100 -0
  56. package/src/primitives.native.ts +8 -0
  57. package/src/primitives.ts +8 -0
  58. package/src/props/index.ts +14 -0
  59. package/src/props/normalize.ts +816 -0
  60. package/src/protocol/encoder.ts +940 -0
  61. package/src/protocol/index.ts +33 -0
  62. package/src/reconciler.ts +581 -0
  63. package/src/runtime.ts +11 -0
  64. package/src/safe-area.ts +543 -0
  65. package/src/solid.ts +490 -0
  66. package/src/style/color.js +1 -0
  67. package/src/style/color.ts +15 -0
  68. package/src/style/index.js +1 -0
  69. package/src/style/index.ts +22 -0
  70. package/src/style/normalize.js +1 -0
  71. package/src/style/normalize.ts +1426 -0
  72. package/src/svelte.ts +349 -0
  73. package/src/svg-source.ts +222 -0
  74. package/src/tags/index.ts +21 -0
  75. package/src/tags/tag-map.ts +289 -0
  76. package/src/text/paragraph-lowering.ts +310 -0
  77. package/src/types.ts +1175 -0
  78. package/src/vue.ts +535 -0
  79. package/src/web-host.ts +19 -0
  80. 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
+ });