@wix/interact 1.92.0 → 2.0.0-rc.2

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 (179) hide show
  1. package/dist/cjs/index.js +2 -23
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/react.js +15 -0
  4. package/dist/cjs/react.js.map +1 -0
  5. package/dist/cjs/web.js +2 -0
  6. package/dist/cjs/web.js.map +1 -0
  7. package/dist/es/index.js +8 -0
  8. package/dist/es/index.js.map +1 -0
  9. package/dist/es/react.js +650 -0
  10. package/dist/es/react.js.map +1 -0
  11. package/dist/es/web.js +56 -0
  12. package/dist/es/web.js.map +1 -0
  13. package/dist/index-C8QxOkui.mjs +7940 -0
  14. package/dist/index-C8QxOkui.mjs.map +1 -0
  15. package/dist/index-DEPRHaUt.js +18 -0
  16. package/dist/index-DEPRHaUt.js.map +1 -0
  17. package/dist/tsconfig.build.tsbuildinfo +1 -0
  18. package/dist/types/core/Interact.d.ts +17 -7
  19. package/dist/types/core/Interact.d.ts.map +1 -0
  20. package/dist/types/core/InteractionController.d.ts +19 -0
  21. package/dist/types/core/InteractionController.d.ts.map +1 -0
  22. package/dist/types/core/add.d.ts +4 -3
  23. package/dist/types/core/add.d.ts.map +1 -0
  24. package/dist/types/core/css.d.ts +3 -0
  25. package/dist/types/core/css.d.ts.map +1 -0
  26. package/dist/types/core/remove.d.ts +3 -1
  27. package/dist/types/core/remove.d.ts.map +1 -0
  28. package/dist/types/core/utilities.d.ts +1 -0
  29. package/dist/types/core/utilities.d.ts.map +1 -0
  30. package/dist/types/dom/api.d.ts +3 -0
  31. package/dist/types/dom/api.d.ts.map +1 -0
  32. package/dist/types/handlers/animationEnd.d.ts +3 -2
  33. package/dist/types/handlers/animationEnd.d.ts.map +1 -0
  34. package/dist/types/handlers/click.d.ts +3 -2
  35. package/dist/types/handlers/click.d.ts.map +1 -0
  36. package/dist/types/handlers/hover.d.ts +3 -2
  37. package/dist/types/handlers/hover.d.ts.map +1 -0
  38. package/dist/types/handlers/index.d.ts +1 -0
  39. package/dist/types/handlers/index.d.ts.map +1 -0
  40. package/dist/types/handlers/pointerMove.d.ts +3 -2
  41. package/dist/types/handlers/pointerMove.d.ts.map +1 -0
  42. package/dist/types/handlers/utilities.d.ts +1 -0
  43. package/dist/types/handlers/utilities.d.ts.map +1 -0
  44. package/dist/types/handlers/viewEnter.d.ts +3 -2
  45. package/dist/types/handlers/viewEnter.d.ts.map +1 -0
  46. package/dist/types/handlers/viewProgress.d.ts +4 -3
  47. package/dist/types/handlers/viewProgress.d.ts.map +1 -0
  48. package/dist/types/index.d.ts +3 -2
  49. package/dist/types/index.d.ts.map +1 -0
  50. package/dist/types/react/Interaction.d.ts +10 -0
  51. package/dist/types/react/Interaction.d.ts.map +1 -0
  52. package/dist/types/react/index.d.ts +8 -0
  53. package/dist/types/react/index.d.ts.map +1 -0
  54. package/dist/types/react/interactRef.d.ts +3 -0
  55. package/dist/types/react/interactRef.d.ts.map +1 -0
  56. package/dist/types/types.d.ts +25 -10
  57. package/dist/types/types.d.ts.map +1 -0
  58. package/dist/types/utils.d.ts +4 -2
  59. package/dist/types/utils.d.ts.map +1 -0
  60. package/dist/types/{InteractElement.d.ts → web/InteractElement.d.ts} +115 -77
  61. package/dist/types/web/InteractElement.d.ts.map +1 -0
  62. package/dist/types/web/defineInteractElement.d.ts +2 -0
  63. package/dist/types/web/defineInteractElement.d.ts.map +1 -0
  64. package/dist/types/web/index.d.ts +6 -0
  65. package/dist/types/web/index.d.ts.map +1 -0
  66. package/docs/README.md +211 -0
  67. package/docs/advanced/README.md +164 -0
  68. package/docs/api/README.md +157 -0
  69. package/docs/api/element-selection.md +607 -0
  70. package/docs/api/functions.md +638 -0
  71. package/docs/api/interact-class.md +663 -0
  72. package/docs/api/interact-element.md +565 -0
  73. package/docs/api/interaction-controller.md +450 -0
  74. package/docs/api/types.md +957 -0
  75. package/docs/examples/README.md +212 -0
  76. package/docs/examples/click-interactions.md +977 -0
  77. package/docs/examples/entrance-animations.md +935 -0
  78. package/docs/examples/hover-effects.md +930 -0
  79. package/docs/examples/list-patterns.md +737 -0
  80. package/docs/guides/README.md +49 -0
  81. package/docs/guides/conditions-and-media-queries.md +1068 -0
  82. package/docs/guides/configuration-structure.md +726 -0
  83. package/docs/guides/custom-elements.md +327 -0
  84. package/docs/guides/effects-and-animations.md +634 -0
  85. package/docs/guides/getting-started.md +379 -0
  86. package/docs/guides/lists-and-dynamic-content.md +713 -0
  87. package/docs/guides/state-management.md +747 -0
  88. package/docs/guides/understanding-triggers.md +690 -0
  89. package/docs/integration/README.md +264 -0
  90. package/docs/integration/react.md +605 -0
  91. package/package.json +73 -56
  92. package/rules/Integration.md +255 -0
  93. package/rules/click-rules.md +533 -0
  94. package/rules/full-lean.md +346 -0
  95. package/rules/hover-rules.md +593 -0
  96. package/rules/pointermove-rules.md +1341 -0
  97. package/rules/scroll-list-rules.md +900 -0
  98. package/rules/viewenter-rules.md +1015 -0
  99. package/rules/viewprogress-rules.md +1044 -0
  100. package/dist/cjs/InteractElement.js +0 -162
  101. package/dist/cjs/InteractElement.js.map +0 -1
  102. package/dist/cjs/__tests__/interact.spec.js +0 -1930
  103. package/dist/cjs/__tests__/interact.spec.js.map +0 -1
  104. package/dist/cjs/__tests__/viewEnter.spec.js +0 -207
  105. package/dist/cjs/__tests__/viewEnter.spec.js.map +0 -1
  106. package/dist/cjs/core/Interact.js +0 -257
  107. package/dist/cjs/core/Interact.js.map +0 -1
  108. package/dist/cjs/core/add.js +0 -246
  109. package/dist/cjs/core/add.js.map +0 -1
  110. package/dist/cjs/core/remove.js +0 -35
  111. package/dist/cjs/core/remove.js.map +0 -1
  112. package/dist/cjs/core/utilities.js +0 -16
  113. package/dist/cjs/core/utilities.js.map +0 -1
  114. package/dist/cjs/external-types.d.js +0 -2
  115. package/dist/cjs/external-types.d.js.map +0 -1
  116. package/dist/cjs/handlers/animationEnd.js +0 -33
  117. package/dist/cjs/handlers/animationEnd.js.map +0 -1
  118. package/dist/cjs/handlers/click.js +0 -116
  119. package/dist/cjs/handlers/click.js.map +0 -1
  120. package/dist/cjs/handlers/hover.js +0 -141
  121. package/dist/cjs/handlers/hover.js.map +0 -1
  122. package/dist/cjs/handlers/index.js +0 -32
  123. package/dist/cjs/handlers/index.js.map +0 -1
  124. package/dist/cjs/handlers/pointerMove.js +0 -49
  125. package/dist/cjs/handlers/pointerMove.js.map +0 -1
  126. package/dist/cjs/handlers/utilities.js +0 -49
  127. package/dist/cjs/handlers/utilities.js.map +0 -1
  128. package/dist/cjs/handlers/viewEnter.js +0 -127
  129. package/dist/cjs/handlers/viewEnter.js.map +0 -1
  130. package/dist/cjs/handlers/viewProgress.js +0 -65
  131. package/dist/cjs/handlers/viewProgress.js.map +0 -1
  132. package/dist/cjs/test-types.d.js +0 -2
  133. package/dist/cjs/test-types.d.js.map +0 -1
  134. package/dist/cjs/types.js +0 -2
  135. package/dist/cjs/types.js.map +0 -1
  136. package/dist/cjs/utils.js +0 -68
  137. package/dist/cjs/utils.js.map +0 -1
  138. package/dist/esm/InteractElement.js +0 -156
  139. package/dist/esm/InteractElement.js.map +0 -1
  140. package/dist/esm/__tests__/interact.spec.js +0 -1937
  141. package/dist/esm/__tests__/interact.spec.js.map +0 -1
  142. package/dist/esm/__tests__/viewEnter.spec.js +0 -210
  143. package/dist/esm/__tests__/viewEnter.spec.js.map +0 -1
  144. package/dist/esm/core/Interact.js +0 -251
  145. package/dist/esm/core/Interact.js.map +0 -1
  146. package/dist/esm/core/add.js +0 -241
  147. package/dist/esm/core/add.js.map +0 -1
  148. package/dist/esm/core/remove.js +0 -30
  149. package/dist/esm/core/remove.js.map +0 -1
  150. package/dist/esm/core/utilities.js +0 -14
  151. package/dist/esm/core/utilities.js.map +0 -1
  152. package/dist/esm/external-types.d.js +0 -2
  153. package/dist/esm/external-types.d.js.map +0 -1
  154. package/dist/esm/handlers/animationEnd.js +0 -29
  155. package/dist/esm/handlers/animationEnd.js.map +0 -1
  156. package/dist/esm/handlers/click.js +0 -116
  157. package/dist/esm/handlers/click.js.map +0 -1
  158. package/dist/esm/handlers/hover.js +0 -141
  159. package/dist/esm/handlers/hover.js.map +0 -1
  160. package/dist/esm/handlers/index.js +0 -27
  161. package/dist/esm/handlers/index.js.map +0 -1
  162. package/dist/esm/handlers/pointerMove.js +0 -48
  163. package/dist/esm/handlers/pointerMove.js.map +0 -1
  164. package/dist/esm/handlers/utilities.js +0 -43
  165. package/dist/esm/handlers/utilities.js.map +0 -1
  166. package/dist/esm/handlers/viewEnter.js +0 -129
  167. package/dist/esm/handlers/viewEnter.js.map +0 -1
  168. package/dist/esm/handlers/viewProgress.js +0 -61
  169. package/dist/esm/handlers/viewProgress.js.map +0 -1
  170. package/dist/esm/index.js +0 -5
  171. package/dist/esm/index.js.map +0 -1
  172. package/dist/esm/test-types.d.js +0 -2
  173. package/dist/esm/test-types.d.js.map +0 -1
  174. package/dist/esm/types.js +0 -2
  175. package/dist/esm/types.js.map +0 -1
  176. package/dist/esm/utils.js +0 -63
  177. package/dist/esm/utils.js.map +0 -1
  178. package/dist/types/__tests__/interact.spec.d.ts +0 -1
  179. package/dist/types/__tests__/viewEnter.spec.d.ts +0 -0
@@ -1,1937 +0,0 @@
1
- import { Interact } from '../core/Interact';
2
- import { add, addListItems } from '../core/add';
3
- import { remove } from '../core/remove';
4
- import { effectToAnimationOptions } from '../handlers/utilities';
5
- import TRIGGER_TO_HANDLER_MODULE_MAP from '../handlers';
6
-
7
- // Mock @wix/motion module
8
- jest.mock('@wix/motion', () => {
9
- const mock = {
10
- getWebAnimation: jest.fn().mockReturnValue({
11
- play: jest.fn(),
12
- cancel: jest.fn(),
13
- onFinish: jest.fn()
14
- }),
15
- getScrubScene: jest.fn().mockReturnValue({}),
16
- getEasing: jest.fn().mockImplementation(v => v),
17
- getAnimation: jest.fn().mockImplementation((target, options, trigger, reducedMotion) => {
18
- return mock.getWebAnimation(target, options, trigger, {
19
- reducedMotion
20
- });
21
- })
22
- };
23
- return mock;
24
- });
25
-
26
- // Mock kuliso module
27
- jest.mock('kuliso', () => ({
28
- Pointer: jest.fn().mockImplementation(() => ({
29
- start: jest.fn(),
30
- destroy: jest.fn()
31
- }))
32
- }));
33
-
34
- // Mock fizban module
35
- jest.mock('fizban', () => ({
36
- Scroll: jest.fn().mockImplementation(() => ({
37
- start: jest.fn(),
38
- end: jest.fn()
39
- }))
40
- }));
41
- describe('interact', () => {
42
- let element;
43
- let mockConfig;
44
- const getMockConfig = () => ({
45
- interactions: [{
46
- trigger: 'viewEnter',
47
- key: 'logo-entrance',
48
- params: {
49
- threshold: 0.2
50
- },
51
- effects: [{
52
- key: 'logo-entrance',
53
- effectId: 'logo-arc-in'
54
- }, {
55
- key: 'logo-click',
56
- effectId: 'logo-bounce'
57
- }]
58
- }, {
59
- trigger: 'pageVisible',
60
- key: 'logo-loop',
61
- effects: [{
62
- key: 'logo-loop',
63
- effectId: 'logo-poke'
64
- }]
65
- }, {
66
- trigger: 'animationEnd',
67
- key: 'logo-animation-end',
68
- params: {
69
- effectId: 'logo-arc-in'
70
- },
71
- effects: [{
72
- key: 'logo-animation-end',
73
- effectId: 'logo-poke'
74
- }]
75
- }, {
76
- trigger: 'pointerMove',
77
- key: 'logo-mouse',
78
- params: {
79
- hitArea: 'root'
80
- },
81
- effects: [{
82
- key: 'logo-mouse',
83
- effectId: 'logo-track-mouse'
84
- }]
85
- }, {
86
- trigger: 'click',
87
- key: 'logo-click',
88
- params: {
89
- type: 'alternate'
90
- },
91
- effects: [{
92
- key: 'logo-click',
93
- effectId: 'logo-bounce'
94
- }]
95
- }, {
96
- trigger: 'click',
97
- key: 'logo-click',
98
- params: {
99
- method: 'toggle'
100
- },
101
- effects: [{
102
- key: 'logo-click',
103
- effectId: 'logo-transition-hover'
104
- }]
105
- }, {
106
- trigger: 'hover',
107
- key: 'logo-hover',
108
- params: {
109
- type: 'alternate'
110
- },
111
- effects: [{
112
- key: 'logo-hover',
113
- effectId: 'logo-arc-in'
114
- }, {
115
- key: 'logo-hover',
116
- effectId: 'logo-arc-in',
117
- namedEffect: {
118
- type: 'ArcIn',
119
- direction: 'left',
120
- power: 'hard'
121
- }
122
- }]
123
- }, {
124
- trigger: 'hover',
125
- key: 'logo-hover',
126
- params: {
127
- method: 'toggle'
128
- },
129
- effects: [{
130
- key: 'logo-hover',
131
- effectId: 'logo-transition-hover'
132
- }]
133
- }, {
134
- trigger: 'viewProgress',
135
- key: 'logo-scroll',
136
- effects: [{
137
- key: 'logo-scroll',
138
- effectId: 'logo-fade-scroll'
139
- }]
140
- }, {
141
- trigger: 'click',
142
- key: 'logo-click-container',
143
- listContainer: '#logo-list',
144
- effects: [{
145
- effectId: 'logo-bounce'
146
- }]
147
- }, {
148
- trigger: 'viewEnter',
149
- key: 'logo-view-container',
150
- effects: [{
151
- listContainer: '#logo-list',
152
- effectId: 'logo-bounce'
153
- }]
154
- }, {
155
- trigger: 'viewProgress',
156
- key: 'logo-scroll-container',
157
- listContainer: '#logo-scroll-list',
158
- effects: [{
159
- listContainer: '#logo-scroll-list',
160
- effectId: 'logo-fade-scroll'
161
- }]
162
- }, {
163
- trigger: 'viewProgress',
164
- key: 'logo-scroll-container',
165
- effects: [{
166
- key: 'logo-scroll-items',
167
- listContainer: '#logo-scroll-list',
168
- effectId: 'logo-fade-scroll'
169
- }]
170
- }],
171
- effects: {
172
- 'logo-arc-in': {
173
- namedEffect: {
174
- type: 'ArcIn',
175
- direction: 'right',
176
- power: 'medium'
177
- },
178
- duration: 1200
179
- },
180
- 'logo-arc-in-with-target': {
181
- key: 'logo-hover',
182
- namedEffect: {
183
- type: 'ArcIn',
184
- direction: 'right',
185
- power: 'medium'
186
- },
187
- duration: 1200
188
- },
189
- 'logo-track-mouse': {
190
- namedEffect: {
191
- type: 'TrackMouse',
192
- distance: {
193
- value: 20,
194
- type: 'px'
195
- },
196
- axis: 'both',
197
- power: 'medium'
198
- },
199
- transitionDuration: 300,
200
- transitionEasing: 'easeOut',
201
- centeredToTarget: true
202
- },
203
- 'logo-bounce': {
204
- namedEffect: {
205
- type: 'BounceIn',
206
- power: 'hard',
207
- direction: 'center',
208
- distanceFactor: 1.2
209
- },
210
- duration: 500
211
- },
212
- 'logo-fade-scroll': {
213
- namedEffect: {
214
- type: 'FadeScroll',
215
- range: 'in',
216
- opacity: 0
217
- },
218
- rangeStart: {
219
- name: 'contain',
220
- offset: {
221
- value: -10,
222
- type: 'percentage'
223
- }
224
- },
225
- rangeEnd: {
226
- name: 'contain',
227
- offset: {
228
- value: 110,
229
- type: 'percentage'
230
- }
231
- }
232
- },
233
- 'logo-transition-hover': {
234
- transition: {
235
- duration: 300,
236
- styleProperties: [{
237
- name: 'opacity',
238
- value: '0'
239
- }]
240
- }
241
- },
242
- 'logo-poke': {
243
- namedEffect: {
244
- type: 'Poke',
245
- direction: 'left',
246
- power: 'medium'
247
- },
248
- duration: 500
249
- }
250
- }
251
- });
252
- beforeEach(() => {
253
- element = document.createElement('interact-element');
254
- const div = document.createElement('div');
255
- element.append(div);
256
-
257
- // Mock Web Animations API
258
- window.KeyframeEffect = class KeyframeEffect {
259
- // eslint-disable-next-line @typescript-eslint/no-shadow
260
- constructor(element, keyframes, options) {
261
- return {
262
- element,
263
- keyframes,
264
- options
265
- };
266
- }
267
- };
268
-
269
- // Mock ViewTimeline
270
- window.ViewTimeline = class ViewTimeline {
271
- constructor(options) {
272
- return {
273
- ...options
274
- };
275
- }
276
- };
277
-
278
- // Mock Animation
279
- window.Animation = class Animation {
280
- constructor(effect, timeline) {
281
- return {
282
- effect,
283
- timeline,
284
- play: jest.fn()
285
- };
286
- }
287
- };
288
-
289
- // Mock IntersectionObserver
290
- window.IntersectionObserver = class IntersectionObserver {
291
- constructor(callback, options) {
292
- return {
293
- callback,
294
- options,
295
- observe: jest.fn(),
296
- unobserve: jest.fn()
297
- };
298
- }
299
- };
300
-
301
- // Mock CSSStyleSheet
302
- window.CSSStyleSheet = class CSSStyleSheet {
303
- constructor(_) {
304
- return {
305
- replace: jest.fn(),
306
- insertRule: jest.fn()
307
- };
308
- }
309
- };
310
-
311
- // Mock adoptedStyleSheets
312
- if (!document.adoptedStyleSheets) {
313
- document.adoptedStyleSheets = [];
314
- }
315
-
316
- // Mock matchMedia for condition testing
317
- mockMatchMedia();
318
- });
319
- function mockMatchMedia(matchingQueries) {
320
- if (matchingQueries === void 0) {
321
- matchingQueries = [];
322
- }
323
- const queryRule = `(${matchingQueries.join(') and (')})`;
324
- const mockMQL = query => {
325
- return {
326
- matches: queryRule === query,
327
- media: query,
328
- onchange: null,
329
- addEventListener: jest.fn(),
330
- removeEventListener: jest.fn(),
331
- dispatchEvent: jest.fn()
332
- };
333
- };
334
- Object.defineProperty(window, 'matchMedia', {
335
- writable: true,
336
- value: jest.fn().mockImplementation(mockMQL)
337
- });
338
- }
339
- function createCascadingTestConfig(conditions, matchingConditions) {
340
- if (conditions === void 0) {
341
- conditions = {};
342
- }
343
- if (matchingConditions === void 0) {
344
- matchingConditions = [];
345
- }
346
- mockMatchMedia(matchingConditions);
347
- return {
348
- conditions: {
349
- desktop: {
350
- type: 'media',
351
- predicate: 'min-width: 1024px'
352
- },
353
- mobile: {
354
- type: 'media',
355
- predicate: 'max-width: 767px'
356
- },
357
- tablet: {
358
- type: 'media',
359
- predicate: '(min-width: 768px) and (max-width: 1023px)'
360
- },
361
- ...conditions
362
- },
363
- interactions: [{
364
- trigger: 'click',
365
- key: 'cascade-source',
366
- effects: [{
367
- key: 'cascade-target',
368
- effectId: 'default-effect'
369
- }, {
370
- key: 'cascade-target',
371
- effectId: 'default-effect',
372
- conditions: ['desktop'],
373
- namedEffect: {
374
- type: 'SlideIn',
375
- direction: 'right',
376
- power: 'medium'
377
- },
378
- duration: 800
379
- }, {
380
- key: 'cascade-target',
381
- effectId: 'default-effect',
382
- conditions: ['mobile'],
383
- namedEffect: {
384
- type: 'BounceIn',
385
- direction: 'center',
386
- power: 'hard'
387
- },
388
- duration: 600
389
- }]
390
- }],
391
- effects: {
392
- 'default-effect': {
393
- namedEffect: {
394
- type: 'FadeIn',
395
- power: 'medium'
396
- },
397
- duration: 500
398
- }
399
- }
400
- };
401
- }
402
- afterEach(() => {
403
- jest.clearAllMocks();
404
- // Clear Interact instances to ensure test isolation
405
- Interact.destroy();
406
- // Reset static flags to default
407
- Interact.forceReducedMotion = false;
408
- Interact.allowA11yTriggers = false;
409
- });
410
- describe('init Interact instance', () => {
411
- it('should initialize with valid config', () => {
412
- Interact.create({});
413
- expect(customElements.get('interact-element')).toBeDefined();
414
- });
415
- });
416
- describe('destroy Interact instance', () => {
417
- it('should clear an instance entire cache', () => {
418
- const instance = Interact.create(getMockConfig());
419
- element = document.createElement('interact-element');
420
- element.dataset.interactKey = 'logo-entrance';
421
- const div = document.createElement('div');
422
- element.append(div);
423
- add(element, 'logo-entrance');
424
- expect(Object.keys(instance.dataCache.interactions).length).toBe(11);
425
- expect(instance.elements.size).toBe(1);
426
- expect(Interact.instances.length).toBe(1);
427
- instance.destroy();
428
- expect(Object.keys(instance.dataCache.interactions).length).toBe(0);
429
- expect(instance.elements.size).toBe(0);
430
- expect(Interact.instances.length).toBe(0);
431
- expect(Interact.elementCache.size).toBe(0);
432
- });
433
- });
434
- describe('destroy Interact', () => {
435
- it('should clear all instances', () => {
436
- Interact.create(getMockConfig());
437
- Interact.create(getMockConfig());
438
- expect(Interact.instances.length).toBe(2);
439
- Interact.destroy();
440
- expect(Interact.instances.length).toBe(0);
441
- });
442
- it('should clear all elements from cache', () => {
443
- Interact.create(getMockConfig());
444
- element = document.createElement('interact-element');
445
- const div = document.createElement('div');
446
- element.append(div);
447
- add(element, 'logo-hover');
448
- expect(Interact.elementCache.size).toBeGreaterThan(0);
449
- Interact.destroy();
450
- expect(Interact.elementCache.size).toBe(0);
451
- });
452
- it('should call disconnect on all cached elements', () => {
453
- Interact.create(getMockConfig());
454
- const element1 = document.createElement('interact-element');
455
- const div1 = document.createElement('div');
456
- element1.append(div1);
457
- const element2 = document.createElement('interact-element');
458
- const div2 = document.createElement('div');
459
- element2.append(div2);
460
- add(element1, 'logo-hover');
461
- add(element2, 'logo-click');
462
- const disconnectSpy1 = jest.spyOn(element1, 'disconnect');
463
- const disconnectSpy2 = jest.spyOn(element2, 'disconnect');
464
- Interact.destroy();
465
- expect(disconnectSpy1).toHaveBeenCalled();
466
- expect(disconnectSpy2).toHaveBeenCalled();
467
- });
468
- it('should clean up interactions after destroy', () => {
469
- Interact.create(getMockConfig());
470
- element = document.createElement('interact-element');
471
- const div = document.createElement('div');
472
- element.append(div);
473
- add(element, 'logo-click');
474
- Interact.destroy();
475
-
476
- // After destroy, getInstance should return undefined
477
- expect(Interact.getInstance('logo-click')).toBeUndefined();
478
-
479
- // Re-create instance and verify it works independently
480
- Interact.create(getMockConfig());
481
- const newElement = document.createElement('interact-element');
482
- const newDiv = document.createElement('div');
483
- newElement.append(newDiv);
484
- const newAddEventListenerSpy = jest.spyOn(newDiv, 'addEventListener');
485
- add(newElement, 'logo-click');
486
- expect(newAddEventListenerSpy).toHaveBeenCalled();
487
- expect(Interact.getInstance('logo-click')).toBeDefined();
488
- });
489
-
490
- // TODO: fix this test - when adding this test it causes 2 other tests to fail
491
- // it('should remove event listeners and styles when disconnect is called', () => {
492
- // Interact.create(mockConfig);
493
-
494
- // element = document.createElement(
495
- // 'interact-element',
496
- // ) as IInteractElement;
497
- // element.dataset.interactKey = 'logo-click';
498
- // const div = document.createElement('div');
499
- // element.append(div);
500
-
501
- // add(element, 'logo-click');
502
-
503
- // const removeEventListenerSpy = jest.spyOn(div, 'removeEventListener');
504
-
505
- // Interact.destroy();
506
-
507
- // expect(removeEventListenerSpy).toHaveBeenCalled();
508
- // expect(removeEventListenerSpy).toHaveBeenCalledWith(
509
- // 'click',
510
- // expect.any(Function),
511
- // );
512
- // });
513
- });
514
- describe('reduced motion', () => {
515
- it('should pass reducedMotion=true to getWebAnimation when forceReducedMotion is true', () => {
516
- const {
517
- getWebAnimation
518
- } = require('@wix/motion');
519
- Interact.forceReducedMotion = true;
520
- Interact.create({
521
- interactions: [{
522
- trigger: 'hover',
523
- key: 'logo-hover',
524
- effects: [{
525
- key: 'logo-hover',
526
- effectId: 'logo-arc-in'
527
- }]
528
- }],
529
- effects: {
530
- 'logo-arc-in': {
531
- namedEffect: {
532
- type: 'ArcIn',
533
- direction: 'right',
534
- power: 'medium'
535
- },
536
- duration: 1200
537
- }
538
- }
539
- });
540
- element = document.createElement('interact-element');
541
- const div = document.createElement('div');
542
- element.append(div);
543
- add(element, 'logo-hover');
544
- expect(getWebAnimation).toHaveBeenCalledWith(div, expect.any(Object), undefined, {
545
- reducedMotion: true
546
- });
547
- Interact.forceReducedMotion = false;
548
- Interact.destroy();
549
- });
550
- });
551
- describe('add interaction', () => {
552
- beforeEach(() => {
553
- mockConfig = getMockConfig();
554
- Interact.create(mockConfig);
555
- });
556
- afterEach(() => {
557
- Interact.destroy();
558
- jest.clearAllMocks();
559
- });
560
- describe('hover', () => {
561
- it('should add handler for hover trigger with alternate type', () => {
562
- const {
563
- getWebAnimation
564
- } = require('@wix/motion');
565
- element = document.createElement('interact-element');
566
- const div = document.createElement('div');
567
- element.append(div);
568
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
569
- expect(getWebAnimation).toHaveBeenCalledTimes(0);
570
- add(element, 'logo-hover');
571
- expect(addEventListenerSpy).toHaveBeenCalledTimes(4);
572
- expect(addEventListenerSpy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.objectContaining({
573
- passive: true
574
- }));
575
- expect(addEventListenerSpy).toHaveBeenCalledWith('mouseleave', expect.any(Function), expect.objectContaining({
576
- passive: true
577
- }));
578
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
579
- expect(getWebAnimation).toHaveBeenCalledWith(div, expect.objectContaining({
580
- namedEffect: expect.objectContaining({
581
- type: 'ArcIn',
582
- direction: 'left',
583
- power: 'hard'
584
- })
585
- }), undefined, {
586
- reducedMotion: false
587
- });
588
- });
589
- });
590
- describe('click', () => {
591
- it('should add handler for click trigger', () => {
592
- element = document.createElement('interact-element');
593
- const div = document.createElement('div');
594
- element.append(div);
595
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
596
- add(element, 'logo-click');
597
- expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
598
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
599
- passive: true
600
- }));
601
- });
602
- });
603
- describe('viewEnter', () => {
604
- it('should add handler for viewEnter trigger', () => {
605
- const {
606
- getWebAnimation
607
- } = require('@wix/motion');
608
- element = document.createElement('interact-element');
609
- const div = document.createElement('div');
610
- element.append(div);
611
- add(element, 'logo-entrance');
612
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
613
- expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object), undefined, {
614
- reducedMotion: false
615
- });
616
- });
617
- it('should add handler for viewEnter trigger when target is added before source', () => {
618
- const {
619
- getWebAnimation
620
- } = require('@wix/motion');
621
- element = document.createElement('interact-element');
622
- const div = document.createElement('div');
623
- div.id = 'logo-entrance';
624
- element.dataset.interactKey = 'logo-entrance';
625
- element.append(div);
626
- const elementClick = document.createElement('interact-element');
627
- const divClick = document.createElement('div');
628
- divClick.id = 'logo-click';
629
- elementClick.dataset.interactKey = 'logo-click';
630
- elementClick.append(divClick);
631
- add(elementClick, 'logo-click');
632
- add(element, 'logo-entrance');
633
- expect(getWebAnimation).toHaveBeenCalledTimes(3);
634
- expect(getWebAnimation.mock.calls[0][0]).toBe(divClick);
635
- expect(getWebAnimation.mock.calls[1][0]).toBe(divClick);
636
- expect(getWebAnimation.mock.calls[2][0]).toBe(div);
637
- expect(getWebAnimation.mock.calls[0][3]).toMatchObject({
638
- reducedMotion: false
639
- });
640
- expect(getWebAnimation.mock.calls[1][3]).toMatchObject({
641
- reducedMotion: false
642
- });
643
- expect(getWebAnimation.mock.calls[2][3]).toMatchObject({
644
- reducedMotion: false
645
- });
646
- });
647
- });
648
- describe('pageVisible', () => {
649
- it('should add handler for pageVisible trigger', () => {
650
- const {
651
- getWebAnimation
652
- } = require('@wix/motion');
653
- element = document.createElement('interact-element');
654
- const div = document.createElement('div');
655
- element.append(div);
656
- add(element, 'logo-loop');
657
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
658
- expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object), undefined, {
659
- reducedMotion: false
660
- });
661
- });
662
- });
663
- describe('animationEnd', () => {
664
- it('should add handler for animationEnd trigger', () => {
665
- const {
666
- getWebAnimation
667
- } = require('@wix/motion');
668
- element = document.createElement('interact-element');
669
- const div = document.createElement('div');
670
- element.append(div);
671
- add(element, 'logo-animation-end');
672
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
673
- expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object), undefined, {
674
- reducedMotion: false
675
- });
676
- });
677
- });
678
- describe('pointerMove', () => {
679
- it('should add handler for pointerMove trigger', () => {
680
- const {
681
- getScrubScene
682
- } = require('@wix/motion');
683
- const {
684
- Pointer
685
- } = require('kuliso');
686
- const pointerInstance = {
687
- start: jest.fn(),
688
- destroy: jest.fn()
689
- };
690
- Pointer.mockImplementation(() => pointerInstance);
691
- element = document.createElement('interact-element');
692
- const div = document.createElement('div');
693
- element.append(div);
694
- add(element, 'logo-mouse');
695
- expect(getScrubScene).toHaveBeenCalledTimes(1);
696
- expect(getScrubScene).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining(effectToAnimationOptions(getMockConfig().effects['logo-track-mouse'])), expect.objectContaining({
697
- trigger: 'pointer-move'
698
- }));
699
- expect(pointerInstance.start).toHaveBeenCalled();
700
- });
701
- });
702
- describe('viewProgress', () => {
703
- it('should add handler for viewProgress trigger with native ViewTimeline support', () => {
704
- const {
705
- getWebAnimation
706
- } = require('@wix/motion');
707
- element = document.createElement('interact-element');
708
- const div = document.createElement('div');
709
- element.append(div);
710
- add(element, 'logo-scroll');
711
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
712
- expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining(effectToAnimationOptions(getMockConfig().effects['logo-fade-scroll'])), expect.objectContaining({
713
- trigger: 'view-progress'
714
- }));
715
- });
716
- it('should add handler for viewProgress trigger with fizban polyfill', () => {
717
- // Remove ViewTimeline support
718
- delete window.ViewTimeline;
719
- const {
720
- getScrubScene
721
- } = require('@wix/motion');
722
- const {
723
- Scroll
724
- } = require('fizban');
725
- const scrollInstance = {
726
- start: jest.fn(),
727
- destroy: jest.fn()
728
- };
729
- Scroll.mockImplementation(() => scrollInstance);
730
- element = document.createElement('interact-element');
731
- const div = document.createElement('div');
732
- element.append(div);
733
- add(element, 'logo-scroll');
734
- expect(getScrubScene).toHaveBeenCalledTimes(1);
735
- expect(getScrubScene).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining(effectToAnimationOptions(getMockConfig().effects['logo-fade-scroll'])), expect.objectContaining({
736
- trigger: 'view-progress'
737
- }));
738
- setTimeout(() => {
739
- expect(scrollInstance.start).toHaveBeenCalled();
740
- }, 0);
741
- });
742
- });
743
- describe('listContainer', () => {
744
- it('should add a handler per list item for click trigger with listContainer', () => {
745
- element = document.createElement('interact-element');
746
- const div = document.createElement('div');
747
- const ul = document.createElement('ul');
748
- ul.id = 'logo-list';
749
- div.append(ul);
750
- const li = document.createElement('li');
751
- const li2 = li.cloneNode(true);
752
- ul.append(li);
753
- ul.append(li2);
754
- element.append(div);
755
- const addEventListenerSpy = jest.spyOn(li, 'addEventListener');
756
- const addEventListenerSpy2 = jest.spyOn(li2, 'addEventListener');
757
- add(element, 'logo-click-container');
758
- expect(addEventListenerSpy).toHaveBeenCalledTimes(1);
759
- expect(addEventListenerSpy2).toHaveBeenCalledTimes(1);
760
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
761
- passive: true
762
- }));
763
- expect(addEventListenerSpy2).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
764
- passive: true
765
- }));
766
- });
767
- it('should add a handler per list item for viewEnter trigger with listContainer in effect', () => {
768
- const {
769
- getWebAnimation
770
- } = require('@wix/motion');
771
- element = document.createElement('interact-element');
772
- const div = document.createElement('div');
773
- const ul = document.createElement('ul');
774
- ul.id = 'logo-list';
775
- div.append(ul);
776
- const li = document.createElement('li');
777
- const li2 = li.cloneNode(true);
778
- ul.append(li);
779
- ul.append(li2);
780
- element.append(div);
781
- add(element, 'logo-view-container');
782
- expect(getWebAnimation).toHaveBeenCalledTimes(2);
783
- expect(getWebAnimation.mock.calls[0][0]).toBe(li);
784
- expect(getWebAnimation.mock.calls[1][0]).toBe(li2);
785
- });
786
- it('should add a handler per newly added list item for click trigger with listContainer', () => {
787
- element = document.createElement('interact-element');
788
- const div = document.createElement('div');
789
- const ul = document.createElement('ul');
790
- ul.id = 'logo-list';
791
- div.append(ul);
792
- const li = document.createElement('li');
793
- const li2 = li.cloneNode(true);
794
- ul.append(li);
795
- ul.append(li2);
796
- element.append(div);
797
- const addEventListenerSpy = jest.spyOn(li, 'addEventListener');
798
- const addEventListenerSpy2 = jest.spyOn(li2, 'addEventListener');
799
- add(element, 'logo-click-container');
800
- expect(addEventListenerSpy).toHaveBeenCalledTimes(1);
801
- expect(addEventListenerSpy2).toHaveBeenCalledTimes(1);
802
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
803
- passive: true
804
- }));
805
- expect(addEventListenerSpy2).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
806
- passive: true
807
- }));
808
- const li3 = document.createElement('li');
809
- ul.append(li3);
810
- const addEventListenerSpy3 = jest.spyOn(li3, 'addEventListener');
811
- addListItems(element, 'logo-click-container', '#logo-list', [li3]);
812
- expect(addEventListenerSpy3).toHaveBeenCalledTimes(1);
813
- expect(addEventListenerSpy3).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
814
- passive: true
815
- }));
816
- });
817
- it('should add a handler per newly added list item for viewProgress trigger but not add same interaction twice', () => {
818
- const {
819
- getWebAnimation
820
- } = require('@wix/motion');
821
- element = document.createElement('interact-element');
822
- const div = document.createElement('div');
823
- const targetElement = document.createElement('interact-element');
824
- const divTarget = document.createElement('div');
825
- const ul = document.createElement('ul');
826
- ul.id = 'logo-scroll-list';
827
- divTarget.append(ul);
828
- const li = document.createElement('li');
829
- const li2 = li.cloneNode(true);
830
- ul.append(li);
831
- ul.append(li2);
832
- element.append(div);
833
- targetElement.append(divTarget);
834
- add(element, 'logo-scroll-container');
835
- expect(getWebAnimation).toHaveBeenCalledTimes(0);
836
- add(targetElement, 'logo-scroll-items');
837
- expect(getWebAnimation).toHaveBeenCalledTimes(2);
838
- expect(getWebAnimation).toHaveBeenCalledWith(li, expect.objectContaining(effectToAnimationOptions(getMockConfig().effects['logo-fade-scroll'])), expect.objectContaining({
839
- trigger: 'view-progress'
840
- }));
841
- expect(getWebAnimation).toHaveBeenCalledWith(li2, expect.objectContaining(effectToAnimationOptions(getMockConfig().effects['logo-fade-scroll'])), expect.objectContaining({
842
- trigger: 'view-progress'
843
- }));
844
- const li3 = document.createElement('li');
845
- ul.append(li3);
846
- addListItems(targetElement, 'logo-scroll-items', '#logo-scroll-list', [li3]);
847
- expect(getWebAnimation).toHaveBeenCalledTimes(3);
848
- expect(getWebAnimation).toHaveBeenCalledWith(li3, expect.objectContaining(effectToAnimationOptions(getMockConfig().effects['logo-fade-scroll'])), expect.objectContaining({
849
- trigger: 'view-progress'
850
- }));
851
- });
852
- });
853
- });
854
- describe('remove interaction', () => {
855
- beforeEach(() => {
856
- Interact.create(getMockConfig());
857
- });
858
- afterEach(() => {
859
- Interact.destroy();
860
- });
861
- it('should remove event listeners', () => {
862
- element = document.createElement('interact-element');
863
- const div = document.createElement('div');
864
- element.append(div);
865
- const removeEventListenerSpy = jest.spyOn(div, 'removeEventListener');
866
- add(element, 'logo-click');
867
- remove('logo-click');
868
- expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
869
- expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
870
- });
871
- it('should do nothing if key does not exist', () => {
872
- expect(() => remove('non-existent-key')).not.toThrow();
873
- });
874
- it('should cleanup pointer effects', () => {
875
- const {
876
- Pointer
877
- } = require('kuliso');
878
- const pointerInstance = {
879
- start: jest.fn(),
880
- destroy: jest.fn()
881
- };
882
- Pointer.mockImplementation(() => pointerInstance);
883
- element = document.createElement('interact-element');
884
- const div = document.createElement('div');
885
- element.append(div);
886
- add(element, 'logo-mouse');
887
- remove('logo-mouse');
888
- expect(pointerInstance.destroy).toHaveBeenCalledTimes(1);
889
- });
890
- });
891
- describe('effect cascading logic', () => {
892
- describe('basic cascading behavior', () => {
893
- it('should apply only first matching effect for same target', () => {
894
- const {
895
- getWebAnimation
896
- } = require('@wix/motion');
897
- const config = createCascadingTestConfig({}, ['min-width: 1024px']);
898
- Interact.create(config);
899
- const sourceElement = document.createElement('interact-element');
900
- const sourceDiv = document.createElement('div');
901
- sourceElement.append(sourceDiv);
902
- const targetElement = document.createElement('interact-element');
903
- const targetDiv = document.createElement('div');
904
- targetElement.append(targetDiv);
905
- const addEventListenerSpy = jest.spyOn(sourceDiv, 'addEventListener');
906
- add(sourceElement, 'cascade-source');
907
- add(targetElement, 'cascade-target');
908
- expect(addEventListenerSpy).toHaveBeenCalledTimes(1);
909
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
910
- passive: true
911
- }));
912
-
913
- // Should create animation with desktop effect (first matching condition)
914
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
915
- expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
916
- namedEffect: expect.objectContaining({
917
- type: 'SlideIn',
918
- direction: 'right'
919
- })
920
- }), undefined, {
921
- reducedMotion: false
922
- });
923
- });
924
- it('should apply default effect when no conditions match', () => {
925
- const {
926
- getWebAnimation
927
- } = require('@wix/motion');
928
- const config = createCascadingTestConfig({}, []); // No matching conditions
929
-
930
- Interact.create(config);
931
- const sourceElement = document.createElement('interact-element');
932
- const sourceDiv = document.createElement('div');
933
- sourceElement.append(sourceDiv);
934
- const targetElement = document.createElement('interact-element');
935
- const targetDiv = document.createElement('div');
936
- targetElement.append(targetDiv);
937
- add(sourceElement, 'cascade-source');
938
- add(targetElement, 'cascade-target');
939
-
940
- // Should create animation with default effect
941
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
942
- expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
943
- namedEffect: expect.objectContaining({
944
- type: 'FadeIn',
945
- power: 'medium'
946
- })
947
- }), undefined, {
948
- reducedMotion: false
949
- });
950
- });
951
- it('should apply mobile effect when mobile condition matches', () => {
952
- const {
953
- getWebAnimation
954
- } = require('@wix/motion');
955
- const config = createCascadingTestConfig({}, ['max-width: 767px']);
956
- Interact.create(config);
957
- const sourceElement = document.createElement('interact-element');
958
- const sourceDiv = document.createElement('div');
959
- sourceElement.append(sourceDiv);
960
- const targetElement = document.createElement('interact-element');
961
- const targetDiv = document.createElement('div');
962
- targetElement.append(targetDiv);
963
- add(sourceElement, 'cascade-source');
964
- add(targetElement, 'cascade-target');
965
-
966
- // Should create animation with mobile effect
967
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
968
- expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
969
- namedEffect: expect.objectContaining({
970
- type: 'BounceIn',
971
- direction: 'center'
972
- })
973
- }), undefined, {
974
- reducedMotion: false
975
- });
976
- });
977
- });
978
- describe('element addition order', () => {
979
- it('should work when source is added before target', () => {
980
- const {
981
- getWebAnimation
982
- } = require('@wix/motion');
983
- const config = createCascadingTestConfig({}, ['min-width: 1024px']);
984
- Interact.create(config);
985
- const sourceElement = document.createElement('interact-element');
986
- const sourceDiv = document.createElement('div');
987
- sourceElement.append(sourceDiv);
988
- const targetElement = document.createElement('interact-element');
989
- const targetDiv = document.createElement('div');
990
- targetElement.append(targetDiv);
991
-
992
- // Add source first
993
- add(sourceElement, 'cascade-source');
994
- // Add target second
995
- add(targetElement, 'cascade-target');
996
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
997
- expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
998
- namedEffect: expect.objectContaining({
999
- type: 'SlideIn'
1000
- })
1001
- }), undefined, {
1002
- reducedMotion: false
1003
- });
1004
- });
1005
- it('should work when target is added before source', () => {
1006
- const {
1007
- getWebAnimation
1008
- } = require('@wix/motion');
1009
- const config = createCascadingTestConfig({}, ['min-width: 1024px']);
1010
- Interact.create(config);
1011
- const sourceElement = document.createElement('interact-element');
1012
- const sourceDiv = document.createElement('div');
1013
- sourceElement.append(sourceDiv);
1014
- const targetElement = document.createElement('interact-element');
1015
- const targetDiv = document.createElement('div');
1016
- targetElement.append(targetDiv);
1017
-
1018
- // Add target first
1019
- add(targetElement, 'cascade-target');
1020
- // Add source second
1021
- add(sourceElement, 'cascade-source');
1022
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
1023
- expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
1024
- namedEffect: expect.objectContaining({
1025
- type: 'SlideIn'
1026
- })
1027
- }), undefined, {
1028
- reducedMotion: false
1029
- });
1030
- });
1031
- });
1032
- describe('complex cascading scenarios', () => {
1033
- it('should handle multiple targets with different conditions', () => {
1034
- const {
1035
- getWebAnimation
1036
- } = require('@wix/motion');
1037
- const complexConfig = {
1038
- conditions: {
1039
- desktop: {
1040
- type: 'media',
1041
- predicate: 'min-width: 1024px'
1042
- },
1043
- mobile: {
1044
- type: 'media',
1045
- predicate: 'max-width: 767px'
1046
- }
1047
- },
1048
- interactions: [{
1049
- trigger: 'click',
1050
- key: 'multi-source-1',
1051
- effects: [{
1052
- key: 'cascade-target-1',
1053
- effectId: 'desktop-effect',
1054
- conditions: ['desktop']
1055
- }, {
1056
- key: 'cascade-target-2',
1057
- effectId: 'mobile-effect',
1058
- conditions: ['mobile']
1059
- }]
1060
- }],
1061
- effects: {
1062
- 'desktop-effect': {
1063
- namedEffect: {
1064
- type: 'SlideIn',
1065
- direction: 'right',
1066
- power: 'medium'
1067
- },
1068
- duration: 800
1069
- },
1070
- 'mobile-effect': {
1071
- namedEffect: {
1072
- type: 'BounceIn',
1073
- direction: 'center',
1074
- power: 'hard'
1075
- },
1076
- duration: 600
1077
- }
1078
- }
1079
- };
1080
- mockMatchMedia(['min-width: 1024px']); // Only desktop matches
1081
- Interact.create(complexConfig);
1082
- const sourceElement = document.createElement('interact-element');
1083
- const sourceDiv = document.createElement('div');
1084
- sourceElement.append(sourceDiv);
1085
- const target1Element = document.createElement('interact-element');
1086
- const target1Div = document.createElement('div');
1087
- target1Element.append(target1Div);
1088
- const target2Element = document.createElement('interact-element');
1089
- const target2Div = document.createElement('div');
1090
- target2Element.append(target2Div);
1091
- add(sourceElement, 'multi-source-1');
1092
- add(target1Element, 'cascade-target-1');
1093
- add(target2Element, 'cascade-target-2');
1094
-
1095
- // Only desktop effect should be applied (mobile condition doesn't match)
1096
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
1097
- expect(getWebAnimation).toHaveBeenCalledWith(target1Element.firstElementChild, expect.objectContaining({
1098
- namedEffect: expect.objectContaining({
1099
- type: 'SlideIn'
1100
- })
1101
- }), undefined, {
1102
- reducedMotion: false
1103
- });
1104
-
1105
- // Should not be called for mobile effect since condition doesn't match
1106
- expect(getWebAnimation).not.toHaveBeenCalledWith(target2Element.firstElementChild, expect.objectContaining({
1107
- namedEffect: expect.objectContaining({
1108
- type: 'BounceIn'
1109
- })
1110
- }), undefined, {
1111
- reducedMotion: false
1112
- });
1113
- });
1114
- it('should handle effects with multiple conditions', () => {
1115
- const {
1116
- getWebAnimation
1117
- } = require('@wix/motion');
1118
- const multiConditionConfig = {
1119
- conditions: {
1120
- desktop: {
1121
- type: 'media',
1122
- predicate: 'min-width: 1024px'
1123
- },
1124
- 'high-res': {
1125
- type: 'media',
1126
- predicate: 'min-resolution: 2dppx'
1127
- }
1128
- },
1129
- interactions: [{
1130
- trigger: 'click',
1131
- key: 'cascade-source',
1132
- effects: [{
1133
- key: 'cascade-target',
1134
- effectId: 'premium-effect'
1135
- }, {
1136
- key: 'cascade-target',
1137
- effectId: 'premium-effect',
1138
- conditions: ['desktop', 'high-res']
1139
- }]
1140
- }],
1141
- effects: {
1142
- 'premium-effect': {
1143
- namedEffect: {
1144
- type: 'Poke',
1145
- direction: 'left',
1146
- power: 'hard'
1147
- },
1148
- duration: 1000
1149
- }
1150
- }
1151
- };
1152
-
1153
- // Both conditions match
1154
- mockMatchMedia(['min-width: 1024px', 'min-resolution: 2dppx']);
1155
- Interact.create(multiConditionConfig);
1156
- const sourceElement = document.createElement('interact-element');
1157
- const sourceDiv = document.createElement('div');
1158
- sourceElement.append(sourceDiv);
1159
- const targetElement = document.createElement('interact-element');
1160
- const targetDiv = document.createElement('div');
1161
- targetElement.append(targetDiv);
1162
- add(sourceElement, 'cascade-source');
1163
- add(targetElement, 'cascade-target');
1164
-
1165
- // Premium effect should be applied since both conditions match
1166
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
1167
- expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
1168
- namedEffect: expect.objectContaining({
1169
- type: 'Poke'
1170
- })
1171
- }), undefined, {
1172
- reducedMotion: false
1173
- });
1174
- });
1175
- });
1176
- describe('condition matching edge cases', () => {
1177
- it('should handle missing conditions gracefully', () => {
1178
- const {
1179
- getWebAnimation
1180
- } = require('@wix/motion');
1181
- const configWithMissingCondition = {
1182
- conditions: {
1183
- desktop: {
1184
- type: 'media',
1185
- predicate: 'min-width: 1024px'
1186
- }
1187
- },
1188
- interactions: [{
1189
- trigger: 'click',
1190
- key: 'cascade-source',
1191
- effects: [{
1192
- key: 'cascade-target',
1193
- effectId: 'default-effect'
1194
- }, {
1195
- key: 'cascade-target',
1196
- effectId: 'default-effect',
1197
- conditions: ['nonexistent-condition']
1198
- }]
1199
- }],
1200
- effects: {
1201
- 'default-effect': {
1202
- namedEffect: {
1203
- type: 'FadeIn',
1204
- power: 'medium'
1205
- },
1206
- duration: 500
1207
- }
1208
- }
1209
- };
1210
- mockMatchMedia(['min-width: 1024px']);
1211
- Interact.create(configWithMissingCondition);
1212
- const sourceElement = document.createElement('interact-element');
1213
- const sourceDiv = document.createElement('div');
1214
- sourceElement.append(sourceDiv);
1215
- const targetElement = document.createElement('interact-element');
1216
- const targetDiv = document.createElement('div');
1217
- targetElement.append(targetDiv);
1218
- add(sourceElement, 'cascade-source');
1219
- add(targetElement, 'cascade-target');
1220
-
1221
- // Should fall back to default effect since condition doesn't exist
1222
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
1223
- expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
1224
- namedEffect: expect.objectContaining({
1225
- type: 'FadeIn'
1226
- })
1227
- }), undefined, {
1228
- reducedMotion: false
1229
- });
1230
- });
1231
- it('should handle empty conditions array', () => {
1232
- const {
1233
- getWebAnimation
1234
- } = require('@wix/motion');
1235
- const configWithEmptyConditions = {
1236
- conditions: {},
1237
- interactions: [{
1238
- trigger: 'click',
1239
- key: 'cascade-source',
1240
- effects: [{
1241
- key: 'cascade-target',
1242
- effectId: 'always-applied-effect',
1243
- conditions: []
1244
- }]
1245
- }],
1246
- effects: {
1247
- 'always-applied-effect': {
1248
- namedEffect: {
1249
- type: 'Spin',
1250
- direction: 'clockwise',
1251
- power: 'medium'
1252
- },
1253
- duration: 1000
1254
- }
1255
- }
1256
- };
1257
- mockMatchMedia([]);
1258
- Interact.create(configWithEmptyConditions);
1259
- const sourceElement = document.createElement('interact-element');
1260
- const sourceDiv = document.createElement('div');
1261
- sourceElement.append(sourceDiv);
1262
- const targetElement = document.createElement('interact-element');
1263
- const targetDiv = document.createElement('div');
1264
- targetElement.append(targetDiv);
1265
- add(sourceElement, 'cascade-source');
1266
- add(targetElement, 'cascade-target');
1267
-
1268
- // Effect should be applied since empty conditions array should always match
1269
- expect(getWebAnimation).toHaveBeenCalledTimes(1);
1270
- expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining({
1271
- namedEffect: expect.objectContaining({
1272
- type: 'Spin'
1273
- })
1274
- }), undefined, {
1275
- reducedMotion: false
1276
- });
1277
- });
1278
- });
1279
- });
1280
- describe('selector functionality', () => {
1281
- let sourceElement;
1282
- let targetElement;
1283
- beforeEach(() => {
1284
- // Create source element with multiple child elements
1285
- sourceElement = document.createElement('interact-element');
1286
- sourceElement.innerHTML = `
1287
- <div class="first-child">First Child</div>
1288
- <button class="trigger-button">Click Me</button>
1289
- <div class="other-element">Other</div>
1290
- <div class="list-container">
1291
- <div class="list-item">Item 1</div>
1292
- <div class="list-item">Item 2</div>
1293
- <div class="list-item">Item 3</div>
1294
- </div>
1295
- `;
1296
-
1297
- // Create target element with multiple child elements
1298
- targetElement = document.createElement('interact-element');
1299
- targetElement.innerHTML = `
1300
- <div class="first-child">Target First</div>
1301
- <div class="animation-target">Animation Target</div>
1302
- <div class="overlay">Overlay</div>
1303
- <div class="nested">
1304
- <span class="deep-target">Deep Target</span>
1305
- </div>
1306
- `;
1307
- });
1308
- describe('basic selector functionality', () => {
1309
- it('should use selector instead of firstElementChild for source', () => {
1310
- const config = {
1311
- effects: {
1312
- 'test-effect': {
1313
- keyframeEffect: {
1314
- name: 'test',
1315
- keyframes: [{
1316
- opacity: '0'
1317
- }, {
1318
- opacity: '1'
1319
- }]
1320
- },
1321
- duration: 300
1322
- }
1323
- },
1324
- interactions: [{
1325
- key: 'selector-source',
1326
- selector: '.trigger-button',
1327
- trigger: 'click',
1328
- effects: [{
1329
- key: 'selector-target',
1330
- effectId: 'test-effect'
1331
- }]
1332
- }]
1333
- };
1334
- Interact.create(config);
1335
- const triggerButton = sourceElement.querySelector('.trigger-button');
1336
- const firstChild = sourceElement.querySelector('.first-child');
1337
- const triggerSpy = jest.spyOn(triggerButton, 'addEventListener');
1338
- const firstChildSpy = jest.spyOn(firstChild, 'addEventListener');
1339
- add(sourceElement, 'selector-source');
1340
- add(targetElement, 'selector-target');
1341
-
1342
- // Should add event listener to the selected element, not firstElementChild
1343
- expect(triggerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1344
- expect(firstChildSpy).not.toHaveBeenCalled();
1345
- });
1346
- it('should use selector instead of firstElementChild for target', () => {
1347
- const {
1348
- getWebAnimation
1349
- } = require('@wix/motion');
1350
- const config = {
1351
- effects: {
1352
- 'test-effect': {
1353
- keyframeEffect: {
1354
- name: 'test',
1355
- keyframes: [{
1356
- opacity: '0'
1357
- }, {
1358
- opacity: '1'
1359
- }]
1360
- },
1361
- duration: 300
1362
- }
1363
- },
1364
- interactions: [{
1365
- key: 'selector-source',
1366
- trigger: 'click',
1367
- effects: [{
1368
- key: 'selector-target',
1369
- selector: '.animation-target',
1370
- effectId: 'test-effect'
1371
- }]
1372
- }]
1373
- };
1374
- Interact.create(config);
1375
- add(sourceElement, 'selector-source');
1376
- add(targetElement, 'selector-target');
1377
- const animationTarget = targetElement.querySelector('.animation-target');
1378
-
1379
- // Should create animation on the selected element, not firstElementChild
1380
- expect(getWebAnimation).toHaveBeenCalledWith(animationTarget, expect.any(Object), undefined, {
1381
- reducedMotion: false
1382
- });
1383
- });
1384
- it('should fall back to firstElementChild when no selector is provided', () => {
1385
- const config = {
1386
- effects: {
1387
- 'test-effect': {
1388
- keyframeEffect: {
1389
- name: 'test',
1390
- keyframes: [{
1391
- opacity: '0'
1392
- }, {
1393
- opacity: '1'
1394
- }]
1395
- },
1396
- duration: 300
1397
- }
1398
- },
1399
- interactions: [{
1400
- key: 'fallback-source',
1401
- trigger: 'click',
1402
- effects: [{
1403
- key: 'fallback-target',
1404
- effectId: 'test-effect'
1405
- }]
1406
- }]
1407
- };
1408
- Interact.create(config);
1409
- const firstChild = sourceElement.querySelector('.first-child');
1410
- const firstChildSpy = jest.spyOn(firstChild, 'addEventListener');
1411
- add(sourceElement, 'fallback-source');
1412
- add(targetElement, 'fallback-target');
1413
-
1414
- // Should fall back to firstElementChild
1415
- expect(firstChildSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1416
- });
1417
- });
1418
- describe('selector with listContainer', () => {
1419
- it('should use selector within listContainer items', () => {
1420
- const config = {
1421
- effects: {
1422
- 'list-effect': {
1423
- keyframeEffect: {
1424
- name: 'list-test',
1425
- keyframes: [{
1426
- transform: 'scale(1)'
1427
- }, {
1428
- transform: 'scale(1.1)'
1429
- }]
1430
- },
1431
- duration: 200
1432
- }
1433
- },
1434
- interactions: [{
1435
- key: 'list-source',
1436
- listContainer: '.list-container',
1437
- selector: '.list-item',
1438
- trigger: 'hover',
1439
- effects: [{
1440
- listContainer: '.list-container',
1441
- selector: '.list-item',
1442
- effectId: 'list-effect'
1443
- }]
1444
- }]
1445
- };
1446
- Interact.create(config);
1447
-
1448
- // Set up spies before adding interactions
1449
- const listItems = Array.from(sourceElement.querySelectorAll('.list-item'));
1450
- const spies = listItems.map(item => jest.spyOn(item, 'addEventListener'));
1451
- add(sourceElement, 'list-source');
1452
-
1453
- // Should add event listeners to each list item
1454
- spies.forEach(spy => {
1455
- expect(spy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.any(Object));
1456
- });
1457
- });
1458
- it('should handle listContainer without selector (all children)', () => {
1459
- var _sourceElement$queryS;
1460
- const config = {
1461
- effects: {
1462
- 'container-effect': {
1463
- keyframeEffect: {
1464
- name: 'container-test',
1465
- keyframes: [{
1466
- opacity: '0.5'
1467
- }, {
1468
- opacity: '1'
1469
- }]
1470
- },
1471
- duration: 150
1472
- }
1473
- },
1474
- interactions: [{
1475
- key: 'container-source',
1476
- listContainer: '.list-container',
1477
- trigger: 'click',
1478
- effects: [{
1479
- listContainer: '.list-container',
1480
- effectId: 'container-effect'
1481
- }]
1482
- }]
1483
- };
1484
- Interact.create(config);
1485
-
1486
- // Set up spies before adding interactions
1487
- const containerChildren = Array.from(((_sourceElement$queryS = sourceElement.querySelector('.list-container')) == null ? void 0 : _sourceElement$queryS.children) || []);
1488
- const spies = containerChildren.map(child => jest.spyOn(child, 'addEventListener'));
1489
- add(sourceElement, 'container-source');
1490
-
1491
- // Should add event listeners to all children of the container
1492
- spies.forEach(spy => {
1493
- expect(spy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1494
- });
1495
- });
1496
- });
1497
- describe('selector error handling', () => {
1498
- let consoleSpy;
1499
- beforeEach(() => {
1500
- consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
1501
- });
1502
- afterEach(() => {
1503
- consoleSpy.mockRestore();
1504
- });
1505
- it('should warn when selector does not match any elements', () => {
1506
- const config = {
1507
- effects: {
1508
- 'test-effect': {
1509
- keyframeEffect: {
1510
- name: 'test',
1511
- keyframes: [{
1512
- opacity: '0'
1513
- }, {
1514
- opacity: '1'
1515
- }]
1516
- },
1517
- duration: 300
1518
- }
1519
- },
1520
- interactions: [{
1521
- key: 'invalid-source',
1522
- selector: '.non-existent-element',
1523
- trigger: 'click',
1524
- effects: [{
1525
- key: 'invalid-target',
1526
- effectId: 'test-effect'
1527
- }]
1528
- }]
1529
- };
1530
- Interact.create(config);
1531
- add(sourceElement, 'invalid-source');
1532
- add(targetElement, 'invalid-target');
1533
- expect(consoleSpy).toHaveBeenCalledWith('Interact: No element found for selector ".non-existent-element"');
1534
- });
1535
- it('should warn when listContainer selector does not match', () => {
1536
- const config = {
1537
- effects: {
1538
- 'test-effect': {
1539
- keyframeEffect: {
1540
- name: 'test',
1541
- keyframes: [{
1542
- opacity: '0'
1543
- }, {
1544
- opacity: '1'
1545
- }]
1546
- },
1547
- duration: 300
1548
- }
1549
- },
1550
- interactions: [{
1551
- key: 'invalid-container-source',
1552
- listContainer: '.non-existent-container',
1553
- trigger: 'click',
1554
- effects: [{
1555
- key: 'invalid-container-target',
1556
- effectId: 'test-effect'
1557
- }]
1558
- }]
1559
- };
1560
- Interact.create(config);
1561
- add(sourceElement, 'invalid-container-source');
1562
- add(targetElement, 'invalid-container-target');
1563
- expect(consoleSpy).toHaveBeenCalledWith('Interact: No container found for list container ".non-existent-container"');
1564
- });
1565
- it('should gracefully handle invalid selectors without breaking interactions', () => {
1566
- const config = {
1567
- effects: {
1568
- 'valid-effect': {
1569
- keyframeEffect: {
1570
- name: 'valid',
1571
- keyframes: [{
1572
- opacity: '0'
1573
- }, {
1574
- opacity: '1'
1575
- }]
1576
- },
1577
- duration: 300
1578
- }
1579
- },
1580
- interactions: [{
1581
- key: 'mixed-source',
1582
- selector: '.non-existent',
1583
- trigger: 'click',
1584
- effects: [{
1585
- key: 'mixed-target',
1586
- effectId: 'valid-effect'
1587
- }]
1588
- }, {
1589
- key: 'valid-source',
1590
- selector: '.trigger-button',
1591
- trigger: 'click',
1592
- effects: [{
1593
- key: 'valid-target',
1594
- effectId: 'valid-effect'
1595
- }]
1596
- }]
1597
- };
1598
- Interact.create(config);
1599
-
1600
- // Set up spy before adding interactions
1601
- const triggerButton = sourceElement.querySelector('.trigger-button');
1602
- const spy = jest.spyOn(triggerButton, 'addEventListener');
1603
-
1604
- // This should not throw and should allow other interactions to work
1605
- expect(() => {
1606
- add(sourceElement, 'mixed-source');
1607
- add(sourceElement, 'valid-source');
1608
- add(targetElement, 'mixed-target');
1609
- add(targetElement, 'valid-target');
1610
- }).not.toThrow();
1611
-
1612
- // Valid interaction should still work
1613
- expect(spy).toHaveBeenCalled();
1614
- });
1615
- });
1616
- describe('complex selector scenarios', () => {
1617
- it('should handle nested selectors', () => {
1618
- const {
1619
- getWebAnimation
1620
- } = require('@wix/motion');
1621
- const config = {
1622
- effects: {
1623
- 'nested-effect': {
1624
- keyframeEffect: {
1625
- name: 'nested',
1626
- keyframes: [{
1627
- color: 'black'
1628
- }, {
1629
- color: 'blue'
1630
- }]
1631
- },
1632
- duration: 200
1633
- }
1634
- },
1635
- interactions: [{
1636
- key: 'nested-source',
1637
- selector: '.other-element',
1638
- trigger: 'click',
1639
- effects: [{
1640
- key: 'nested-target',
1641
- selector: '.nested .deep-target',
1642
- effectId: 'nested-effect'
1643
- }]
1644
- }]
1645
- };
1646
- Interact.create(config);
1647
- const addEventListenerSpy = jest.spyOn(sourceElement.querySelector('.other-element'), 'addEventListener');
1648
- add(sourceElement, 'nested-source');
1649
- add(targetElement, 'nested-target');
1650
- const deepTarget = targetElement.querySelector('.nested .deep-target');
1651
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1652
- expect(getWebAnimation).toHaveBeenCalledWith(deepTarget, expect.any(Object), undefined, {
1653
- reducedMotion: false
1654
- });
1655
- });
1656
- it('should handle attribute selectors', () => {
1657
- // Add data attributes to test elements
1658
- const buttonWithData = sourceElement.querySelector('.trigger-button');
1659
- buttonWithData.setAttribute('data-interactive', 'true');
1660
- buttonWithData.setAttribute('data-category', 'primary');
1661
- const config = {
1662
- effects: {
1663
- 'attr-effect': {
1664
- keyframeEffect: {
1665
- name: 'attr',
1666
- keyframes: [{
1667
- backgroundColor: 'white'
1668
- }, {
1669
- backgroundColor: 'lightblue'
1670
- }]
1671
- },
1672
- duration: 300
1673
- }
1674
- },
1675
- interactions: [{
1676
- key: 'attr-source',
1677
- selector: '[data-interactive="true"][data-category="primary"]',
1678
- trigger: 'click',
1679
- effects: [{
1680
- key: 'attr-target',
1681
- selector: '[class*="animation"]',
1682
- effectId: 'attr-effect'
1683
- }]
1684
- }]
1685
- };
1686
- Interact.create(config);
1687
-
1688
- // Set up spy before adding interactions
1689
- const spy = jest.spyOn(buttonWithData, 'addEventListener');
1690
- add(sourceElement, 'attr-source');
1691
- add(targetElement, 'attr-target');
1692
- expect(spy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1693
- });
1694
- });
1695
- describe('selector inheritance and priority', () => {
1696
- it('should not inherit selector from interaction to effect', () => {
1697
- const {
1698
- getWebAnimation
1699
- } = require('@wix/motion');
1700
- const config = {
1701
- effects: {
1702
- 'inherit-effect': {
1703
- keyframeEffect: {
1704
- name: 'inherit',
1705
- keyframes: [{
1706
- opacity: '0'
1707
- }, {
1708
- opacity: '1'
1709
- }]
1710
- },
1711
- duration: 300
1712
- }
1713
- },
1714
- interactions: [{
1715
- key: 'inherit-source',
1716
- selector: '.trigger-button',
1717
- // Interaction has selector
1718
- trigger: 'click',
1719
- effects: [{
1720
- key: 'inherit-target',
1721
- // Effect has no selector - should use firstElementChild, not inherit
1722
- effectId: 'inherit-effect'
1723
- }]
1724
- }]
1725
- };
1726
- Interact.create(config);
1727
- add(sourceElement, 'inherit-source');
1728
- add(targetElement, 'inherit-target');
1729
-
1730
- // Effect should target firstElementChild, not the interaction's selector
1731
- const targetFirstChild = targetElement.firstElementChild;
1732
- expect(getWebAnimation).toHaveBeenCalledWith(targetFirstChild, expect.any(Object), undefined, {
1733
- reducedMotion: false
1734
- });
1735
- });
1736
- });
1737
- describe('selector cleanup', () => {
1738
- it('should clean up selector-based interactions on remove', () => {
1739
- const config = {
1740
- effects: {
1741
- 'cleanup-effect': {
1742
- keyframeEffect: {
1743
- name: 'cleanup',
1744
- keyframes: [{
1745
- opacity: '0'
1746
- }, {
1747
- opacity: '1'
1748
- }]
1749
- },
1750
- duration: 300
1751
- }
1752
- },
1753
- interactions: [{
1754
- key: 'cleanup-source',
1755
- selector: '.trigger-button',
1756
- trigger: 'click',
1757
- effects: [{
1758
- key: 'cleanup-target',
1759
- selector: '.animation-target',
1760
- effectId: 'cleanup-effect'
1761
- }]
1762
- }]
1763
- };
1764
- Interact.create(config);
1765
- const triggerButton = sourceElement.querySelector('.trigger-button');
1766
- const removeEventListenerSpy = jest.spyOn(triggerButton, 'removeEventListener');
1767
- add(sourceElement, 'cleanup-source');
1768
- add(targetElement, 'cleanup-target');
1769
- remove('cleanup-source');
1770
-
1771
- // Should remove event listeners from the selected element
1772
- expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
1773
- });
1774
- });
1775
- });
1776
- describe('element caching', () => {
1777
- it('should cache element in elementCache even when no instance was found for it', () => {
1778
- const keyWithoutInstance = 'key-without-instance';
1779
- add(element, keyWithoutInstance);
1780
- expect(Interact.elementCache.has(keyWithoutInstance)).toBe(true);
1781
- expect(Interact.elementCache.get(keyWithoutInstance)).toBe(element);
1782
- });
1783
- });
1784
- describe('setup', () => {
1785
- afterEach(() => {
1786
- Interact.setup({
1787
- scrollOptionsGetter: () => ({}),
1788
- pointerOptionsGetter: () => ({})
1789
- });
1790
- });
1791
- it('should register scroll options getter', () => {
1792
- const scrollOptionsGetter = jest.fn().mockReturnValue({});
1793
- const spy = jest.spyOn(TRIGGER_TO_HANDLER_MODULE_MAP.viewProgress, 'registerOptionsGetter');
1794
- Interact.setup({
1795
- scrollOptionsGetter
1796
- });
1797
- expect(spy).toHaveBeenCalledWith(scrollOptionsGetter);
1798
- });
1799
- it('should register pointer options getter', () => {
1800
- const pointerOptionsGetter = jest.fn().mockReturnValue({});
1801
- const spy = jest.spyOn(TRIGGER_TO_HANDLER_MODULE_MAP.pointerMove, 'registerOptionsGetter');
1802
- Interact.setup({
1803
- pointerOptionsGetter
1804
- });
1805
- expect(spy).toHaveBeenCalledWith(pointerOptionsGetter);
1806
- });
1807
- });
1808
- describe('a11y - accessible triggers', () => {
1809
- let a11yElement;
1810
- function getA11yConfig(trigger, key) {
1811
- return {
1812
- interactions: [{
1813
- trigger,
1814
- key,
1815
- effects: [{
1816
- effectId: 'test-effect'
1817
- }]
1818
- }],
1819
- effects: {
1820
- 'test-effect': {
1821
- namedEffect: {
1822
- type: 'BounceIn',
1823
- power: 'medium'
1824
- },
1825
- duration: 500
1826
- }
1827
- }
1828
- };
1829
- }
1830
- afterEach(() => {
1831
- Interact.destroy();
1832
- });
1833
- describe('activate trigger', () => {
1834
- it('should add both click and keydown listeners', () => {
1835
- Interact.create(getA11yConfig('activate', 'activate-div'));
1836
- a11yElement = document.createElement('interact-element');
1837
- const div = document.createElement('div');
1838
- a11yElement.append(div);
1839
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
1840
- add(a11yElement, 'activate-div');
1841
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1842
- expect(addEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function), expect.any(Object));
1843
- });
1844
- it('should not double-invoke handler when Enter triggers both keydown and click', () => {
1845
- const {
1846
- getWebAnimation
1847
- } = require('@wix/motion');
1848
- const mockPlay = getWebAnimation().play;
1849
- mockPlay.mockClear();
1850
- Interact.create(getA11yConfig('activate', 'activate-handler-test'));
1851
- a11yElement = document.createElement('interact-element');
1852
- const button = document.createElement('button');
1853
- a11yElement.append(button);
1854
- add(a11yElement, 'activate-handler-test');
1855
-
1856
- // Simulate browser behavior: Enter key triggers keydown AND synthesized click with no pointerType
1857
- button.dispatchEvent(new KeyboardEvent('keydown', {
1858
- code: 'Enter',
1859
- bubbles: true
1860
- }));
1861
- button.dispatchEvent(new MouseEvent('click', {
1862
- bubbles: true
1863
- }));
1864
- expect(mockPlay).toHaveBeenCalledTimes(1);
1865
- });
1866
- });
1867
- describe('interest trigger', () => {
1868
- it('should add focusin listener alongside mouseenter', () => {
1869
- Interact.create(getA11yConfig('interest', 'interest-test'));
1870
- a11yElement = document.createElement('interact-element');
1871
- const div = document.createElement('div');
1872
- a11yElement.append(div);
1873
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
1874
- add(a11yElement, 'interest-test');
1875
- expect(addEventListenerSpy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.any(Object));
1876
- expect(addEventListenerSpy).toHaveBeenCalledWith('focusin', expect.any(Function), expect.any(Object));
1877
- });
1878
- });
1879
- describe('click trigger with allowA11yTriggers flag', () => {
1880
- it('should NOT add keydown listener when flag is false', () => {
1881
- Interact.create(getA11yConfig('click', 'click-no-flag'));
1882
- Interact.setup({
1883
- allowA11yTriggers: false
1884
- });
1885
- a11yElement = document.createElement('interact-element');
1886
- const div = document.createElement('div');
1887
- a11yElement.append(div);
1888
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
1889
- add(a11yElement, 'click-no-flag');
1890
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1891
- expect(addEventListenerSpy).not.toHaveBeenCalledWith('keydown', expect.any(Function), expect.any(Object));
1892
- });
1893
- it('should add keydown listener when flag is true', () => {
1894
- Interact.setup({
1895
- allowA11yTriggers: true
1896
- });
1897
- Interact.create(getA11yConfig('click', 'click-with-flag'));
1898
- a11yElement = document.createElement('interact-element');
1899
- const div = document.createElement('div');
1900
- a11yElement.append(div);
1901
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
1902
- add(a11yElement, 'click-with-flag');
1903
- expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
1904
- expect(addEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function), expect.any(Object));
1905
- });
1906
- });
1907
- describe('hover trigger with allowA11yTriggers flag', () => {
1908
- it('should NOT add focusin listener when flag is false', () => {
1909
- Interact.setup({
1910
- allowA11yTriggers: false
1911
- });
1912
- Interact.create(getA11yConfig('hover', 'hover-no-flag'));
1913
- a11yElement = document.createElement('interact-element');
1914
- const div = document.createElement('div');
1915
- a11yElement.append(div);
1916
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
1917
- add(a11yElement, 'hover-no-flag');
1918
- expect(addEventListenerSpy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.any(Object));
1919
- expect(addEventListenerSpy).not.toHaveBeenCalledWith('focusin', expect.any(Function), expect.any(Object));
1920
- });
1921
- it('should add focusin listener when flag is true', () => {
1922
- Interact.setup({
1923
- allowA11yTriggers: true
1924
- });
1925
- Interact.create(getA11yConfig('hover', 'hover-with-flag'));
1926
- a11yElement = document.createElement('interact-element');
1927
- const div = document.createElement('div');
1928
- a11yElement.append(div);
1929
- const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
1930
- add(a11yElement, 'hover-with-flag');
1931
- expect(addEventListenerSpy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.any(Object));
1932
- expect(addEventListenerSpy).toHaveBeenCalledWith('focusin', expect.any(Function), expect.any(Object));
1933
- });
1934
- });
1935
- });
1936
- });
1937
- //# sourceMappingURL=interact.spec.js.map