@wix/interact 1.66.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/README.md +258 -0
  2. package/dist/cjs/WixInteractElement.js +103 -0
  3. package/dist/cjs/WixInteractElement.js.map +1 -0
  4. package/dist/cjs/__tests__/interact.spec.js +944 -0
  5. package/dist/cjs/__tests__/interact.spec.js.map +1 -0
  6. package/dist/cjs/external-types.d.js +2 -0
  7. package/dist/cjs/external-types.d.js.map +1 -0
  8. package/dist/cjs/handlers/animationEnd.js +33 -0
  9. package/dist/cjs/handlers/animationEnd.js.map +1 -0
  10. package/dist/cjs/handlers/click.js +82 -0
  11. package/dist/cjs/handlers/click.js.map +1 -0
  12. package/dist/cjs/handlers/hover.js +103 -0
  13. package/dist/cjs/handlers/hover.js.map +1 -0
  14. package/dist/cjs/handlers/index.js +21 -0
  15. package/dist/cjs/handlers/index.js.map +1 -0
  16. package/dist/cjs/handlers/pointerMove.js +40 -0
  17. package/dist/cjs/handlers/pointerMove.js.map +1 -0
  18. package/dist/cjs/handlers/utilities.js +51 -0
  19. package/dist/cjs/handlers/utilities.js.map +1 -0
  20. package/dist/cjs/handlers/viewEnter.js +69 -0
  21. package/dist/cjs/handlers/viewEnter.js.map +1 -0
  22. package/dist/cjs/handlers/viewProgress.js +56 -0
  23. package/dist/cjs/handlers/viewProgress.js.map +1 -0
  24. package/dist/cjs/index.js +21 -0
  25. package/dist/cjs/index.js.map +1 -0
  26. package/dist/cjs/interact.js +310 -0
  27. package/dist/cjs/interact.js.map +1 -0
  28. package/dist/cjs/test-types.d.js +2 -0
  29. package/dist/cjs/test-types.d.js.map +1 -0
  30. package/dist/cjs/types.js +2 -0
  31. package/dist/cjs/types.js.map +1 -0
  32. package/dist/cjs/utils.js +66 -0
  33. package/dist/cjs/utils.js.map +1 -0
  34. package/dist/esm/WixInteractElement.js +97 -0
  35. package/dist/esm/WixInteractElement.js.map +1 -0
  36. package/dist/esm/__tests__/interact.spec.js +952 -0
  37. package/dist/esm/__tests__/interact.spec.js.map +1 -0
  38. package/dist/esm/external-types.d.js +2 -0
  39. package/dist/esm/external-types.d.js.map +1 -0
  40. package/dist/esm/handlers/animationEnd.js +29 -0
  41. package/dist/esm/handlers/animationEnd.js.map +1 -0
  42. package/dist/esm/handlers/click.js +82 -0
  43. package/dist/esm/handlers/click.js.map +1 -0
  44. package/dist/esm/handlers/hover.js +103 -0
  45. package/dist/esm/handlers/hover.js.map +1 -0
  46. package/dist/esm/handlers/index.js +16 -0
  47. package/dist/esm/handlers/index.js.map +1 -0
  48. package/dist/esm/handlers/pointerMove.js +39 -0
  49. package/dist/esm/handlers/pointerMove.js.map +1 -0
  50. package/dist/esm/handlers/utilities.js +45 -0
  51. package/dist/esm/handlers/utilities.js.map +1 -0
  52. package/dist/esm/handlers/viewEnter.js +69 -0
  53. package/dist/esm/handlers/viewEnter.js.map +1 -0
  54. package/dist/esm/handlers/viewProgress.js +52 -0
  55. package/dist/esm/handlers/viewProgress.js.map +1 -0
  56. package/dist/esm/index.js +3 -0
  57. package/dist/esm/index.js.map +1 -0
  58. package/dist/esm/interact.js +303 -0
  59. package/dist/esm/interact.js.map +1 -0
  60. package/dist/esm/test-types.d.js +2 -0
  61. package/dist/esm/test-types.d.js.map +1 -0
  62. package/dist/esm/types.js +2 -0
  63. package/dist/esm/types.js.map +1 -0
  64. package/dist/esm/utils.js +61 -0
  65. package/dist/esm/utils.js.map +1 -0
  66. package/dist/types/WixInteractElement.d.ts +324 -0
  67. package/dist/types/__tests__/interact.spec.d.ts +1 -0
  68. package/dist/types/handlers/animationEnd.d.ts +8 -0
  69. package/dist/types/handlers/click.d.ts +8 -0
  70. package/dist/types/handlers/hover.d.ts +8 -0
  71. package/dist/types/handlers/index.d.ts +3 -0
  72. package/dist/types/handlers/pointerMove.d.ts +8 -0
  73. package/dist/types/handlers/utilities.d.ts +4 -0
  74. package/dist/types/handlers/viewEnter.d.ts +8 -0
  75. package/dist/types/handlers/viewProgress.d.ts +8 -0
  76. package/dist/types/index.d.ts +2 -0
  77. package/dist/types/interact.d.ts +25 -0
  78. package/dist/types/types.d.ts +173 -0
  79. package/dist/types/utils.d.ts +4 -0
  80. package/package.json +71 -0
@@ -0,0 +1,952 @@
1
+ import { Interact, add, remove } from '../interact';
2
+ import { effectToAnimationOptions } from '../handlers/utilities';
3
+
4
+ // Mock @wix/motion module
5
+ jest.mock('@wix/motion', () => ({
6
+ getWebAnimation: jest.fn().mockReturnValue({
7
+ play: jest.fn(),
8
+ cancel: jest.fn()
9
+ }),
10
+ getScrubScene: jest.fn().mockReturnValue({}),
11
+ getEasing: jest.fn().mockImplementation(v => v)
12
+ }));
13
+
14
+ // Mock kuliso module
15
+ jest.mock('kuliso', () => ({
16
+ Pointer: jest.fn().mockImplementation(() => ({
17
+ start: jest.fn(),
18
+ destroy: jest.fn()
19
+ }))
20
+ }));
21
+
22
+ // Mock fizban module
23
+ jest.mock('fizban', () => ({
24
+ Scroll: jest.fn().mockImplementation(() => ({
25
+ start: jest.fn(),
26
+ end: jest.fn()
27
+ }))
28
+ }));
29
+ describe('interact', () => {
30
+ let element;
31
+ const mockConfig = {
32
+ interactions: [{
33
+ trigger: 'viewEnter',
34
+ source: '#logo-entrance',
35
+ params: {
36
+ threshold: 0.2
37
+ },
38
+ effects: [{
39
+ target: '#logo-entrance',
40
+ effectId: 'logo-arc-in'
41
+ }, {
42
+ target: '#logo-click',
43
+ effectId: 'logo-bounce'
44
+ }]
45
+ }, {
46
+ trigger: 'pageVisible',
47
+ source: '#logo-loop',
48
+ effects: [{
49
+ target: '#logo-loop',
50
+ effectId: 'logo-poke'
51
+ }]
52
+ }, {
53
+ trigger: 'animationEnd',
54
+ source: '#logo-animation-end',
55
+ params: {
56
+ effectId: 'logo-arc-in'
57
+ },
58
+ effects: [{
59
+ target: '#logo-animation-end',
60
+ effectId: 'logo-poke'
61
+ }]
62
+ }, {
63
+ trigger: 'pointerMove',
64
+ source: '#logo-mouse',
65
+ params: {
66
+ hitArea: 'root'
67
+ },
68
+ effects: [{
69
+ target: '#logo-mouse',
70
+ effectId: 'logo-track-mouse'
71
+ }]
72
+ }, {
73
+ trigger: 'click',
74
+ source: '#logo-click',
75
+ params: {
76
+ type: 'alternate'
77
+ },
78
+ effects: [{
79
+ target: '#logo-click',
80
+ effectId: 'logo-bounce'
81
+ }]
82
+ }, {
83
+ trigger: 'click',
84
+ source: '#logo-click',
85
+ params: {
86
+ method: 'toggle'
87
+ },
88
+ effects: [{
89
+ target: '#logo-click',
90
+ effectId: 'logo-transition-hover'
91
+ }]
92
+ }, {
93
+ trigger: 'hover',
94
+ source: '#logo-hover',
95
+ params: {
96
+ type: 'alternate'
97
+ },
98
+ effects: [{
99
+ target: '#logo-hover',
100
+ effectId: 'logo-arc-in'
101
+ }, {
102
+ target: '#logo-hover',
103
+ effectId: 'logo-arc-in',
104
+ namedEffect: {
105
+ type: 'ArcIn',
106
+ direction: 'left',
107
+ power: 'hard'
108
+ }
109
+ }]
110
+ }, {
111
+ trigger: 'hover',
112
+ source: '#logo-hover',
113
+ params: {
114
+ method: 'toggle'
115
+ },
116
+ effects: [{
117
+ target: '#logo-hover',
118
+ effectId: 'logo-transition-hover'
119
+ }]
120
+ }, {
121
+ trigger: 'viewProgress',
122
+ source: '#logo-scroll',
123
+ effects: [{
124
+ target: '#logo-scroll',
125
+ effectId: 'logo-fade-scroll'
126
+ }]
127
+ }],
128
+ effects: {
129
+ 'logo-arc-in': {
130
+ namedEffect: {
131
+ type: 'ArcIn',
132
+ direction: 'right',
133
+ power: 'medium'
134
+ },
135
+ duration: 1200
136
+ },
137
+ 'logo-arc-in-with-target': {
138
+ target: '#logo-hover',
139
+ namedEffect: {
140
+ type: 'ArcIn',
141
+ direction: 'right',
142
+ power: 'medium'
143
+ },
144
+ duration: 1200
145
+ },
146
+ 'logo-track-mouse': {
147
+ namedEffect: {
148
+ type: 'TrackMouse',
149
+ distance: {
150
+ value: 20,
151
+ type: 'px'
152
+ },
153
+ axis: 'both',
154
+ power: 'medium'
155
+ },
156
+ transitionDuration: 300,
157
+ transitionEasing: 'easeOut',
158
+ centeredToTarget: true
159
+ },
160
+ 'logo-bounce': {
161
+ namedEffect: {
162
+ type: 'BounceIn',
163
+ power: 'hard',
164
+ direction: 'center',
165
+ distanceFactor: 1.2
166
+ },
167
+ duration: 500
168
+ },
169
+ 'logo-fade-scroll': {
170
+ namedEffect: {
171
+ type: 'FadeScroll',
172
+ range: 'in',
173
+ opacity: 0
174
+ },
175
+ rangeStart: {
176
+ name: 'contain',
177
+ offset: {
178
+ value: -10,
179
+ type: 'percentage'
180
+ }
181
+ },
182
+ rangeEnd: {
183
+ name: 'contain',
184
+ offset: {
185
+ value: 110,
186
+ type: 'percentage'
187
+ }
188
+ }
189
+ },
190
+ 'logo-transition-hover': {
191
+ transition: {
192
+ duration: 300,
193
+ styleProperties: [{
194
+ name: 'opacity',
195
+ value: '0'
196
+ }]
197
+ }
198
+ },
199
+ 'logo-poke': {
200
+ namedEffect: {
201
+ type: 'Poke',
202
+ direction: 'left',
203
+ power: 'medium'
204
+ },
205
+ duration: 500
206
+ }
207
+ }
208
+ };
209
+ beforeEach(() => {
210
+ element = document.createElement('wix-interact-element');
211
+ const div = document.createElement('div');
212
+ element.append(div);
213
+
214
+ // Mock Web Animations API
215
+ window.KeyframeEffect = class KeyframeEffect {
216
+ // eslint-disable-next-line @typescript-eslint/no-shadow
217
+ constructor(element, keyframes, options) {
218
+ return {
219
+ element,
220
+ keyframes,
221
+ options
222
+ };
223
+ }
224
+ };
225
+
226
+ // Mock ViewTimeline
227
+ window.ViewTimeline = class ViewTimeline {
228
+ constructor(options) {
229
+ return {
230
+ ...options
231
+ };
232
+ }
233
+ };
234
+
235
+ // Mock Animation
236
+ window.Animation = class Animation {
237
+ constructor(effect, timeline) {
238
+ return {
239
+ effect,
240
+ timeline,
241
+ play: jest.fn()
242
+ };
243
+ }
244
+ };
245
+
246
+ // Mock IntersectionObserver
247
+ window.IntersectionObserver = class IntersectionObserver {
248
+ constructor(callback, options) {
249
+ return {
250
+ callback,
251
+ options,
252
+ observe: jest.fn(),
253
+ unobserve: jest.fn()
254
+ };
255
+ }
256
+ };
257
+
258
+ // Mock CSSStyleSheet
259
+ window.CSSStyleSheet = class CSSStyleSheet {
260
+ constructor(_) {
261
+ return {
262
+ replace: jest.fn()
263
+ };
264
+ }
265
+ };
266
+
267
+ // Mock adoptedStyleSheets
268
+ if (!document.adoptedStyleSheets) {
269
+ document.adoptedStyleSheets = [];
270
+ }
271
+
272
+ // Mock matchMedia for condition testing
273
+ mockMatchMedia();
274
+ });
275
+ function mockMatchMedia(matchingQueries) {
276
+ if (matchingQueries === void 0) {
277
+ matchingQueries = [];
278
+ }
279
+ const queryRule = `(${matchingQueries.join(') and (')})`;
280
+ const mockMQL = query => {
281
+ return {
282
+ matches: queryRule === query,
283
+ media: query,
284
+ onchange: null,
285
+ addEventListener: jest.fn(),
286
+ removeEventListener: jest.fn(),
287
+ dispatchEvent: jest.fn()
288
+ };
289
+ };
290
+ Object.defineProperty(window, 'matchMedia', {
291
+ writable: true,
292
+ value: jest.fn().mockImplementation(mockMQL)
293
+ });
294
+ }
295
+ function createCascadingTestConfig(conditions, matchingConditions) {
296
+ if (conditions === void 0) {
297
+ conditions = {};
298
+ }
299
+ if (matchingConditions === void 0) {
300
+ matchingConditions = [];
301
+ }
302
+ mockMatchMedia(matchingConditions);
303
+ return {
304
+ conditions: {
305
+ desktop: {
306
+ type: 'media',
307
+ predicate: 'min-width: 1024px'
308
+ },
309
+ mobile: {
310
+ type: 'media',
311
+ predicate: 'max-width: 767px'
312
+ },
313
+ tablet: {
314
+ type: 'media',
315
+ predicate: '(min-width: 768px) and (max-width: 1023px)'
316
+ },
317
+ ...conditions
318
+ },
319
+ interactions: [{
320
+ trigger: 'click',
321
+ source: '#cascade-source',
322
+ effects: [{
323
+ target: '#cascade-target',
324
+ effectId: 'default-effect'
325
+ }, {
326
+ target: '#cascade-target',
327
+ effectId: 'default-effect',
328
+ conditions: ['desktop'],
329
+ namedEffect: {
330
+ type: 'SlideIn',
331
+ direction: 'right',
332
+ power: 'medium'
333
+ },
334
+ duration: 800
335
+ }, {
336
+ target: '#cascade-target',
337
+ effectId: 'default-effect',
338
+ conditions: ['mobile'],
339
+ namedEffect: {
340
+ type: 'BounceIn',
341
+ direction: 'center',
342
+ power: 'hard'
343
+ },
344
+ duration: 600
345
+ }]
346
+ }],
347
+ effects: {
348
+ 'default-effect': {
349
+ namedEffect: {
350
+ type: 'FadeIn',
351
+ power: 'medium'
352
+ },
353
+ duration: 500
354
+ }
355
+ }
356
+ };
357
+ }
358
+ afterEach(() => {
359
+ jest.clearAllMocks();
360
+ remove('#logo-entrance');
361
+ remove('#logo-loop');
362
+ remove('#logo-animation-end');
363
+ remove('#logo-mouse');
364
+ remove('#logo-click');
365
+ remove('#logo-hover');
366
+ remove('#logo-scroll');
367
+ // Clean up cascading test elements
368
+ remove('#cascade-source');
369
+ remove('#cascade-target');
370
+ remove('#cascade-target-1');
371
+ remove('#cascade-target-2');
372
+ remove('#multi-source-1');
373
+ remove('#multi-source-2');
374
+ // Clear Interact instances to ensure test isolation
375
+ Interact.instances.length = 0;
376
+ Interact.elementCache.clear();
377
+ });
378
+ describe('init Interact', () => {
379
+ it('should initialize with valid config', () => {
380
+ Interact.create({});
381
+ expect(customElements.get('wix-interact-element')).toBeDefined();
382
+ });
383
+ });
384
+ describe('add interaction', () => {
385
+ beforeEach(() => {
386
+ Interact.create(mockConfig);
387
+ });
388
+ describe('hover', () => {
389
+ it('should add handler for hover trigger with alternate type', () => {
390
+ const {
391
+ getWebAnimation
392
+ } = require('@wix/motion');
393
+ element = document.createElement('wix-interact-element');
394
+ const div = document.createElement('div');
395
+ element.append(div);
396
+ const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
397
+ add(element, '#logo-hover');
398
+ expect(addEventListenerSpy).toHaveBeenCalledTimes(4);
399
+ expect(addEventListenerSpy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.objectContaining({
400
+ passive: true
401
+ }));
402
+ expect(addEventListenerSpy).toHaveBeenCalledWith('mouseleave', expect.any(Function), expect.objectContaining({
403
+ passive: true
404
+ }));
405
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
406
+ expect(getWebAnimation).toHaveBeenCalledWith(div, expect.objectContaining({
407
+ namedEffect: expect.objectContaining({
408
+ type: 'ArcIn',
409
+ direction: 'left',
410
+ power: 'hard'
411
+ })
412
+ }));
413
+ });
414
+ });
415
+ describe('click', () => {
416
+ it('should add handler for click trigger', () => {
417
+ element = document.createElement('wix-interact-element');
418
+ const div = document.createElement('div');
419
+ element.append(div);
420
+ const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
421
+ add(element, '#logo-click');
422
+ expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
423
+ expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
424
+ passive: true
425
+ }));
426
+ });
427
+ });
428
+ describe('viewEnter', () => {
429
+ it('should add handler for viewEnter trigger', () => {
430
+ const {
431
+ getWebAnimation
432
+ } = require('@wix/motion');
433
+ element = document.createElement('wix-interact-element');
434
+ const div = document.createElement('div');
435
+ element.append(div);
436
+ add(element, '#logo-entrance');
437
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
438
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
439
+ });
440
+ it('should add handler for viewEnter trigger when target is added before source', () => {
441
+ const {
442
+ getWebAnimation
443
+ } = require('@wix/motion');
444
+ element = document.createElement('wix-interact-element');
445
+ const div = document.createElement('div');
446
+ element.append(div);
447
+ const elementClick = document.createElement('wix-interact-element');
448
+ const divClick = document.createElement('div');
449
+ element.append(divClick);
450
+ add(elementClick, '#logo-click');
451
+ add(element, '#logo-entrance');
452
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
453
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
454
+ });
455
+ });
456
+ describe('pageVisible', () => {
457
+ it('should add handler for pageVisible trigger', () => {
458
+ const {
459
+ getWebAnimation
460
+ } = require('@wix/motion');
461
+ element = document.createElement('wix-interact-element');
462
+ const div = document.createElement('div');
463
+ element.append(div);
464
+ add(element, '#logo-loop');
465
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
466
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
467
+ });
468
+ });
469
+ describe('animationEnd', () => {
470
+ it('should add handler for animationEnd trigger', () => {
471
+ const {
472
+ getWebAnimation
473
+ } = require('@wix/motion');
474
+ element = document.createElement('wix-interact-element');
475
+ const div = document.createElement('div');
476
+ element.append(div);
477
+ add(element, '#logo-animation-end');
478
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
479
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
480
+ });
481
+ });
482
+ describe('pointerMove', () => {
483
+ it('should add handler for pointerMove trigger', () => {
484
+ const {
485
+ getScrubScene
486
+ } = require('@wix/motion');
487
+ const {
488
+ Pointer
489
+ } = require('kuliso');
490
+ const pointerInstance = {
491
+ start: jest.fn(),
492
+ destroy: jest.fn()
493
+ };
494
+ Pointer.mockImplementation(() => pointerInstance);
495
+ element = document.createElement('wix-interact-element');
496
+ const div = document.createElement('div');
497
+ element.append(div);
498
+ add(element, '#logo-mouse');
499
+ expect(getScrubScene).toHaveBeenCalledTimes(1);
500
+ expect(getScrubScene).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining(effectToAnimationOptions(mockConfig.effects['logo-track-mouse'])), expect.objectContaining({
501
+ trigger: 'pointer-move'
502
+ }));
503
+ expect(pointerInstance.start).toHaveBeenCalled();
504
+ });
505
+ });
506
+ describe('viewProgress', () => {
507
+ it('should add handler for viewProgress trigger with native ViewTimeline support', () => {
508
+ const {
509
+ getWebAnimation
510
+ } = require('@wix/motion');
511
+ element = document.createElement('wix-interact-element');
512
+ const div = document.createElement('div');
513
+ element.append(div);
514
+ add(element, '#logo-scroll');
515
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
516
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining(effectToAnimationOptions(mockConfig.effects['logo-fade-scroll'])), expect.objectContaining({
517
+ trigger: 'view-progress'
518
+ }));
519
+ });
520
+ it('should add handler for viewProgress trigger with fizban polyfill', () => {
521
+ // Remove ViewTimeline support
522
+ delete window.ViewTimeline;
523
+ const {
524
+ getScrubScene
525
+ } = require('@wix/motion');
526
+ const {
527
+ Scroll
528
+ } = require('fizban');
529
+ const scrollInstance = {
530
+ start: jest.fn(),
531
+ destroy: jest.fn()
532
+ };
533
+ Scroll.mockImplementation(() => scrollInstance);
534
+ element = document.createElement('wix-interact-element');
535
+ const div = document.createElement('div');
536
+ element.append(div);
537
+ add(element, '#logo-scroll');
538
+ expect(getScrubScene).toHaveBeenCalledTimes(1);
539
+ expect(getScrubScene).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining(effectToAnimationOptions(mockConfig.effects['logo-fade-scroll'])), expect.objectContaining({
540
+ trigger: 'view-progress'
541
+ }));
542
+ setTimeout(() => {
543
+ expect(scrollInstance.start).toHaveBeenCalled();
544
+ }, 0);
545
+ });
546
+ });
547
+ });
548
+ describe('remove interaction', () => {
549
+ beforeEach(() => {
550
+ Interact.create(mockConfig);
551
+ });
552
+ it('should remove event listeners', () => {
553
+ element = document.createElement('wix-interact-element');
554
+ const div = document.createElement('div');
555
+ element.append(div);
556
+ const removeEventListenerSpy = jest.spyOn(div, 'removeEventListener');
557
+ add(element, '#logo-click');
558
+ remove('#logo-click');
559
+ expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
560
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
561
+ });
562
+ it('should do nothing if key does not exist', () => {
563
+ expect(() => remove('non-existent-key')).not.toThrow();
564
+ });
565
+ it('should cleanup pointer effects', () => {
566
+ const {
567
+ Pointer
568
+ } = require('kuliso');
569
+ const pointerInstance = {
570
+ start: jest.fn(),
571
+ destroy: jest.fn()
572
+ };
573
+ Pointer.mockImplementation(() => pointerInstance);
574
+ element = document.createElement('wix-interact-element');
575
+ const div = document.createElement('div');
576
+ element.append(div);
577
+ add(element, '#logo-mouse');
578
+ remove('#logo-mouse');
579
+ expect(pointerInstance.destroy).toHaveBeenCalledTimes(1);
580
+ });
581
+ });
582
+ describe('effect cascading logic', () => {
583
+ describe('basic cascading behavior', () => {
584
+ it('should apply only first matching effect for same target', () => {
585
+ const {
586
+ getWebAnimation
587
+ } = require('@wix/motion');
588
+ const config = createCascadingTestConfig({}, ['min-width: 1024px']);
589
+ Interact.create(config);
590
+ const sourceElement = document.createElement('wix-interact-element');
591
+ const sourceDiv = document.createElement('div');
592
+ sourceElement.append(sourceDiv);
593
+ const targetElement = document.createElement('wix-interact-element');
594
+ const targetDiv = document.createElement('div');
595
+ targetElement.append(targetDiv);
596
+ const addEventListenerSpy = jest.spyOn(sourceDiv, 'addEventListener');
597
+ add(sourceElement, '#cascade-source');
598
+ add(targetElement, '#cascade-target');
599
+ expect(addEventListenerSpy).toHaveBeenCalledTimes(1);
600
+ expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
601
+ passive: true
602
+ }));
603
+
604
+ // Should create animation with desktop effect (first matching condition)
605
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
606
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
607
+ namedEffect: expect.objectContaining({
608
+ type: 'SlideIn',
609
+ direction: 'right'
610
+ })
611
+ }));
612
+ });
613
+ it('should apply default effect when no conditions match', () => {
614
+ const {
615
+ getWebAnimation
616
+ } = require('@wix/motion');
617
+ const config = createCascadingTestConfig({}, []); // No matching conditions
618
+
619
+ Interact.create(config);
620
+ const sourceElement = document.createElement('wix-interact-element');
621
+ const sourceDiv = document.createElement('div');
622
+ sourceElement.append(sourceDiv);
623
+ const targetElement = document.createElement('wix-interact-element');
624
+ const targetDiv = document.createElement('div');
625
+ targetElement.append(targetDiv);
626
+ add(sourceElement, '#cascade-source');
627
+ add(targetElement, '#cascade-target');
628
+
629
+ // Should create animation with default effect
630
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
631
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
632
+ namedEffect: expect.objectContaining({
633
+ type: 'FadeIn',
634
+ power: 'medium'
635
+ })
636
+ }));
637
+ });
638
+ it('should apply mobile effect when mobile condition matches', () => {
639
+ const {
640
+ getWebAnimation
641
+ } = require('@wix/motion');
642
+ const config = createCascadingTestConfig({}, ['max-width: 767px']);
643
+ Interact.create(config);
644
+ const sourceElement = document.createElement('wix-interact-element');
645
+ const sourceDiv = document.createElement('div');
646
+ sourceElement.append(sourceDiv);
647
+ const targetElement = document.createElement('wix-interact-element');
648
+ const targetDiv = document.createElement('div');
649
+ targetElement.append(targetDiv);
650
+ add(sourceElement, '#cascade-source');
651
+ add(targetElement, '#cascade-target');
652
+
653
+ // Should create animation with mobile effect
654
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
655
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
656
+ namedEffect: expect.objectContaining({
657
+ type: 'BounceIn',
658
+ direction: 'center'
659
+ })
660
+ }));
661
+ });
662
+ });
663
+ describe('element addition order', () => {
664
+ it('should work when source is added before target', () => {
665
+ const {
666
+ getWebAnimation
667
+ } = require('@wix/motion');
668
+ const config = createCascadingTestConfig({}, ['min-width: 1024px']);
669
+ Interact.create(config);
670
+ const sourceElement = document.createElement('wix-interact-element');
671
+ const sourceDiv = document.createElement('div');
672
+ sourceElement.append(sourceDiv);
673
+ const targetElement = document.createElement('wix-interact-element');
674
+ const targetDiv = document.createElement('div');
675
+ targetElement.append(targetDiv);
676
+
677
+ // Add source first
678
+ add(sourceElement, '#cascade-source');
679
+ // Add target second
680
+ add(targetElement, '#cascade-target');
681
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
682
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
683
+ namedEffect: expect.objectContaining({
684
+ type: 'SlideIn'
685
+ })
686
+ }));
687
+ });
688
+ it('should work when target is added before source', () => {
689
+ const {
690
+ getWebAnimation
691
+ } = require('@wix/motion');
692
+ const config = createCascadingTestConfig({}, ['min-width: 1024px']);
693
+ Interact.create(config);
694
+ const sourceElement = document.createElement('wix-interact-element');
695
+ const sourceDiv = document.createElement('div');
696
+ sourceElement.append(sourceDiv);
697
+ const targetElement = document.createElement('wix-interact-element');
698
+ const targetDiv = document.createElement('div');
699
+ targetElement.append(targetDiv);
700
+
701
+ // Add target first
702
+ add(targetElement, '#cascade-target');
703
+ // Add source second
704
+ add(sourceElement, '#cascade-source');
705
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
706
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
707
+ namedEffect: expect.objectContaining({
708
+ type: 'SlideIn'
709
+ })
710
+ }));
711
+ });
712
+ });
713
+ describe('complex cascading scenarios', () => {
714
+ it('should handle multiple targets with different conditions', () => {
715
+ const {
716
+ getWebAnimation
717
+ } = require('@wix/motion');
718
+ const complexConfig = {
719
+ conditions: {
720
+ desktop: {
721
+ type: 'media',
722
+ predicate: 'min-width: 1024px'
723
+ },
724
+ mobile: {
725
+ type: 'media',
726
+ predicate: 'max-width: 767px'
727
+ }
728
+ },
729
+ interactions: [{
730
+ trigger: 'click',
731
+ source: '#multi-source-1',
732
+ effects: [{
733
+ target: '#cascade-target-1',
734
+ effectId: 'desktop-effect',
735
+ conditions: ['desktop']
736
+ }, {
737
+ target: '#cascade-target-2',
738
+ effectId: 'mobile-effect',
739
+ conditions: ['mobile']
740
+ }]
741
+ }],
742
+ effects: {
743
+ 'desktop-effect': {
744
+ namedEffect: {
745
+ type: 'SlideIn',
746
+ direction: 'right',
747
+ power: 'medium'
748
+ },
749
+ duration: 800
750
+ },
751
+ 'mobile-effect': {
752
+ namedEffect: {
753
+ type: 'BounceIn',
754
+ direction: 'center',
755
+ power: 'hard'
756
+ },
757
+ duration: 600
758
+ }
759
+ }
760
+ };
761
+ mockMatchMedia(['min-width: 1024px']); // Only desktop matches
762
+ Interact.create(complexConfig);
763
+ const sourceElement = document.createElement('wix-interact-element');
764
+ const sourceDiv = document.createElement('div');
765
+ sourceElement.append(sourceDiv);
766
+ const target1Element = document.createElement('wix-interact-element');
767
+ const target1Div = document.createElement('div');
768
+ target1Element.append(target1Div);
769
+ const target2Element = document.createElement('wix-interact-element');
770
+ const target2Div = document.createElement('div');
771
+ target2Element.append(target2Div);
772
+ add(sourceElement, '#multi-source-1');
773
+ add(target1Element, '#cascade-target-1');
774
+ add(target2Element, '#cascade-target-2');
775
+
776
+ // Only desktop effect should be applied (mobile condition doesn't match)
777
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
778
+ expect(getWebAnimation).toHaveBeenCalledWith(target1Element.firstElementChild, expect.objectContaining({
779
+ namedEffect: expect.objectContaining({
780
+ type: 'SlideIn'
781
+ })
782
+ }));
783
+
784
+ // Should not be called for mobile effect since condition doesn't match
785
+ expect(getWebAnimation).not.toHaveBeenCalledWith(target2Element.firstElementChild, expect.objectContaining({
786
+ namedEffect: expect.objectContaining({
787
+ type: 'BounceIn'
788
+ })
789
+ }));
790
+ });
791
+ it('should handle effects with multiple conditions', () => {
792
+ const {
793
+ getWebAnimation
794
+ } = require('@wix/motion');
795
+ const multiConditionConfig = {
796
+ conditions: {
797
+ desktop: {
798
+ type: 'media',
799
+ predicate: 'min-width: 1024px'
800
+ },
801
+ 'high-res': {
802
+ type: 'media',
803
+ predicate: 'min-resolution: 2dppx'
804
+ }
805
+ },
806
+ interactions: [{
807
+ trigger: 'click',
808
+ source: '#cascade-source',
809
+ effects: [{
810
+ target: '#cascade-target',
811
+ effectId: 'premium-effect'
812
+ }, {
813
+ target: '#cascade-target',
814
+ effectId: 'premium-effect',
815
+ conditions: ['desktop', 'high-res']
816
+ }]
817
+ }],
818
+ effects: {
819
+ 'premium-effect': {
820
+ namedEffect: {
821
+ type: 'Poke',
822
+ direction: 'left',
823
+ power: 'hard'
824
+ },
825
+ duration: 1000
826
+ }
827
+ }
828
+ };
829
+
830
+ // Both conditions match
831
+ mockMatchMedia(['min-width: 1024px', 'min-resolution: 2dppx']);
832
+ Interact.create(multiConditionConfig);
833
+ const sourceElement = document.createElement('wix-interact-element');
834
+ const sourceDiv = document.createElement('div');
835
+ sourceElement.append(sourceDiv);
836
+ const targetElement = document.createElement('wix-interact-element');
837
+ const targetDiv = document.createElement('div');
838
+ targetElement.append(targetDiv);
839
+ add(sourceElement, '#cascade-source');
840
+ add(targetElement, '#cascade-target');
841
+
842
+ // Premium effect should be applied since both conditions match
843
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
844
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
845
+ namedEffect: expect.objectContaining({
846
+ type: 'Poke'
847
+ })
848
+ }));
849
+ });
850
+ });
851
+ describe('condition matching edge cases', () => {
852
+ it('should handle missing conditions gracefully', () => {
853
+ const {
854
+ getWebAnimation
855
+ } = require('@wix/motion');
856
+ const configWithMissingCondition = {
857
+ conditions: {
858
+ desktop: {
859
+ type: 'media',
860
+ predicate: 'min-width: 1024px'
861
+ }
862
+ },
863
+ interactions: [{
864
+ trigger: 'click',
865
+ source: '#cascade-source',
866
+ effects: [{
867
+ target: '#cascade-target',
868
+ effectId: 'default-effect'
869
+ }, {
870
+ target: '#cascade-target',
871
+ effectId: 'default-effect',
872
+ conditions: ['nonexistent-condition']
873
+ }]
874
+ }],
875
+ effects: {
876
+ 'default-effect': {
877
+ namedEffect: {
878
+ type: 'FadeIn',
879
+ power: 'medium'
880
+ },
881
+ duration: 500
882
+ }
883
+ }
884
+ };
885
+ mockMatchMedia(['min-width: 1024px']);
886
+ Interact.create(configWithMissingCondition);
887
+ const sourceElement = document.createElement('wix-interact-element');
888
+ const sourceDiv = document.createElement('div');
889
+ sourceElement.append(sourceDiv);
890
+ const targetElement = document.createElement('wix-interact-element');
891
+ const targetDiv = document.createElement('div');
892
+ targetElement.append(targetDiv);
893
+ add(sourceElement, '#cascade-source');
894
+ add(targetElement, '#cascade-target');
895
+
896
+ // Should fall back to default effect since condition doesn't exist
897
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
898
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
899
+ namedEffect: expect.objectContaining({
900
+ type: 'FadeIn'
901
+ })
902
+ }));
903
+ });
904
+ it('should handle empty conditions array', () => {
905
+ const {
906
+ getWebAnimation
907
+ } = require('@wix/motion');
908
+ const configWithEmptyConditions = {
909
+ conditions: {},
910
+ interactions: [{
911
+ trigger: 'click',
912
+ source: '#cascade-source',
913
+ effects: [{
914
+ target: '#cascade-target',
915
+ effectId: 'always-applied-effect',
916
+ conditions: []
917
+ }]
918
+ }],
919
+ effects: {
920
+ 'always-applied-effect': {
921
+ namedEffect: {
922
+ type: 'Spin',
923
+ direction: 'clockwise',
924
+ power: 'medium'
925
+ },
926
+ duration: 1000
927
+ }
928
+ }
929
+ };
930
+ mockMatchMedia([]);
931
+ Interact.create(configWithEmptyConditions);
932
+ const sourceElement = document.createElement('wix-interact-element');
933
+ const sourceDiv = document.createElement('div');
934
+ sourceElement.append(sourceDiv);
935
+ const targetElement = document.createElement('wix-interact-element');
936
+ const targetDiv = document.createElement('div');
937
+ targetElement.append(targetDiv);
938
+ add(sourceElement, '#cascade-source');
939
+ add(targetElement, '#cascade-target');
940
+
941
+ // Effect should be applied since empty conditions array should always match
942
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
943
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining({
944
+ namedEffect: expect.objectContaining({
945
+ type: 'Spin'
946
+ })
947
+ }));
948
+ });
949
+ });
950
+ });
951
+ });
952
+ //# sourceMappingURL=interact.spec.js.map