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