@wix/interact 1.103.0 → 1.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,6 +42,7 @@ jest.mock('fizban', () => ({
42
42
  end: jest.fn()
43
43
  }))
44
44
  }));
45
+ let mockMQLs;
45
46
  describe('interact', () => {
46
47
  let element;
47
48
  let mockConfig;
@@ -327,12 +328,16 @@ describe('interact', () => {
327
328
  }
328
329
 
329
330
  // Mock matchMedia for condition testing
331
+ mockMQLs = new Map();
330
332
  mockMatchMedia();
331
333
  });
332
334
  function mockMatchMedia(matchingQueries = []) {
333
335
  const queryRule = `(${matchingQueries.join(') and (')})`;
334
336
  const mockMQL = query => {
335
- return {
337
+ if (mockMQLs.has(query)) {
338
+ return mockMQLs.get(query);
339
+ }
340
+ const mql = {
336
341
  matches: queryRule === query,
337
342
  media: query,
338
343
  onchange: null,
@@ -340,9 +345,12 @@ describe('interact', () => {
340
345
  removeEventListener: jest.fn(),
341
346
  dispatchEvent: jest.fn()
342
347
  };
348
+ mockMQLs.set(query, mql);
349
+ return mql;
343
350
  };
344
351
  Object.defineProperty(window, 'matchMedia', {
345
352
  writable: true,
353
+ configurable: true,
346
354
  value: jest.fn().mockImplementation(mockMQL)
347
355
  });
348
356
  }
@@ -2725,5 +2733,211 @@ describe('interact', () => {
2725
2733
  });
2726
2734
  });
2727
2735
  });
2736
+ describe('media query listeners', () => {
2737
+ let instance;
2738
+ beforeEach(() => {
2739
+ mockMQLs.clear();
2740
+ mockMatchMedia();
2741
+ });
2742
+ afterEach(() => {
2743
+ _Interact.Interact.destroy();
2744
+ });
2745
+ function createMockInteractElement(key) {
2746
+ const el = document.createElement('interact-element');
2747
+ const div = document.createElement('div');
2748
+ el.append(div);
2749
+ el.dataset.interactKey = key;
2750
+ el.connected = false;
2751
+ el._internals = null;
2752
+ el._observers = new WeakMap();
2753
+ el.sheet = null;
2754
+ el.disconnect = jest.fn(function () {
2755
+ const k = el.dataset.interactKey;
2756
+ if (k) {
2757
+ (0, _remove.remove)(k);
2758
+ }
2759
+ el.sheet = null;
2760
+ el.connected = false;
2761
+ });
2762
+ el.connect = jest.fn(function () {
2763
+ if (el.connected) {
2764
+ return;
2765
+ }
2766
+ const k = el.dataset.interactKey;
2767
+ if (k) {
2768
+ el.connected = (0, _add.add)(el, k);
2769
+ }
2770
+ });
2771
+ el.renderStyle = jest.fn();
2772
+ el.toggleEffect = jest.fn();
2773
+ el.watchChildList = jest.fn();
2774
+ return el;
2775
+ }
2776
+ const createResponsiveConfig = () => ({
2777
+ conditions: {
2778
+ desktop: {
2779
+ type: 'media',
2780
+ predicate: 'min-width: 768px'
2781
+ }
2782
+ },
2783
+ interactions: [{
2784
+ trigger: 'click',
2785
+ key: 'responsive-button',
2786
+ conditions: ['desktop'],
2787
+ effects: [{
2788
+ key: 'responsive-button',
2789
+ effectId: 'test-effect'
2790
+ }]
2791
+ }],
2792
+ effects: {
2793
+ 'test-effect': {
2794
+ namedEffect: {
2795
+ type: 'FadeIn',
2796
+ power: 'medium'
2797
+ },
2798
+ duration: 500
2799
+ }
2800
+ }
2801
+ });
2802
+ it('should set up a media query listener when interaction has conditions', () => {
2803
+ const config = createResponsiveConfig();
2804
+ instance = _Interact.Interact.create(config);
2805
+ const testElement = createMockInteractElement('responsive-button');
2806
+ (0, _add.add)(testElement, 'responsive-button');
2807
+ expect(window.matchMedia).toHaveBeenCalledWith('(min-width: 768px)');
2808
+ expect(instance.mediaQueryListeners.size).toBe(1);
2809
+ expect(instance.mediaQueryListeners.has('responsive-button::trigger::0')).toBe(true);
2810
+ const mql = mockMQLs.get('(min-width: 768px)');
2811
+ expect(mql == null ? void 0 : mql.addEventListener).toHaveBeenCalledWith('change', expect.any(Function));
2812
+ });
2813
+ it('should reconcile when media query changes', () => {
2814
+ const config = createResponsiveConfig();
2815
+ instance = _Interact.Interact.create(config);
2816
+ const testElement = createMockInteractElement('responsive-button');
2817
+ (0, _add.add)(testElement, 'responsive-button');
2818
+ const listenerEntry = instance.mediaQueryListeners.get('responsive-button::trigger::0');
2819
+ expect(listenerEntry).toBeDefined();
2820
+ const clearStateSpy = jest.spyOn(instance, 'clearInteractionStateForKey');
2821
+ listenerEntry.handler({
2822
+ matches: true,
2823
+ media: '(min-width: 768px)'
2824
+ });
2825
+
2826
+ // disconnect triggers remove which calls clearInteractionStateForKey
2827
+ expect(clearStateSpy).toHaveBeenCalledWith('responsive-button');
2828
+ });
2829
+ it('should properly remove event listeners when instance is destroyed', () => {
2830
+ const config = createResponsiveConfig();
2831
+ instance = _Interact.Interact.create(config);
2832
+ const testElement = createMockInteractElement('responsive-button');
2833
+ (0, _add.add)(testElement, 'responsive-button');
2834
+ const mql = mockMQLs.get('(min-width: 768px)');
2835
+ expect(mql).toBeDefined();
2836
+ expect(mql.addEventListener).toHaveBeenCalled();
2837
+ instance.destroy();
2838
+ expect(mql.removeEventListener).toHaveBeenCalledWith('change', expect.any(Function));
2839
+ expect(instance.mediaQueryListeners.size).toBe(0);
2840
+ });
2841
+ it('should not create duplicate listeners when add() is called twice', () => {
2842
+ const config = createResponsiveConfig();
2843
+ instance = _Interact.Interact.create(config);
2844
+ const testElement = createMockInteractElement('responsive-button');
2845
+ (0, _add.add)(testElement, 'responsive-button');
2846
+ // Reset addedInteractions so add() processes interactions again
2847
+ instance.addedInteractions = {};
2848
+ (0, _add.add)(testElement, 'responsive-button');
2849
+ expect(instance.mediaQueryListeners.size).toBe(1);
2850
+ const mql = mockMQLs.get('(min-width: 768px)');
2851
+ expect(mql == null ? void 0 : mql.addEventListener).toHaveBeenCalledTimes(1);
2852
+ });
2853
+ it('should remove listeners when element is deleted', () => {
2854
+ const config = createResponsiveConfig();
2855
+ instance = _Interact.Interact.create(config);
2856
+ const testElement = createMockInteractElement('responsive-button');
2857
+ (0, _add.add)(testElement, 'responsive-button');
2858
+ const mql = mockMQLs.get('(min-width: 768px)');
2859
+ expect(instance.mediaQueryListeners.size).toBe(1);
2860
+ instance.deleteElement('responsive-button');
2861
+ expect(mql.removeEventListener).toHaveBeenCalledWith('change', expect.any(Function));
2862
+ expect(instance.mediaQueryListeners.size).toBe(0);
2863
+ });
2864
+ it('should switch interactions when media query changes', () => {
2865
+ const config = {
2866
+ conditions: {
2867
+ desktop: {
2868
+ type: 'media',
2869
+ predicate: 'min-width: 1024px'
2870
+ },
2871
+ mobile: {
2872
+ type: 'media',
2873
+ predicate: 'max-width: 767px'
2874
+ }
2875
+ },
2876
+ interactions: [{
2877
+ trigger: 'click',
2878
+ key: 'responsive-element',
2879
+ conditions: ['desktop'],
2880
+ effects: [{
2881
+ key: 'responsive-element',
2882
+ effectId: 'desktop-effect'
2883
+ }]
2884
+ }, {
2885
+ trigger: 'hover',
2886
+ key: 'responsive-element',
2887
+ conditions: ['mobile'],
2888
+ effects: [{
2889
+ key: 'responsive-element',
2890
+ effectId: 'mobile-effect'
2891
+ }]
2892
+ }],
2893
+ effects: {
2894
+ 'desktop-effect': {
2895
+ namedEffect: {
2896
+ type: 'FadeIn',
2897
+ power: 'medium'
2898
+ },
2899
+ duration: 500
2900
+ },
2901
+ 'mobile-effect': {
2902
+ namedEffect: {
2903
+ type: 'BounceIn',
2904
+ direction: 'center',
2905
+ power: 'hard'
2906
+ },
2907
+ duration: 300
2908
+ }
2909
+ }
2910
+ };
2911
+ mockMatchMedia(['min-width: 1024px']);
2912
+ instance = _Interact.Interact.create(config);
2913
+ const testElement = createMockInteractElement('responsive-element');
2914
+ // Handlers are added to the child div (firstElementChild), not the root
2915
+ const childDiv = testElement.firstElementChild;
2916
+ const addEventListenerSpy = jest.spyOn(childDiv, 'addEventListener');
2917
+ (0, _add.add)(testElement, 'responsive-element');
2918
+
2919
+ // Desktop interaction (click) should be added
2920
+ expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.any(Object));
2921
+ expect(addEventListenerSpy).not.toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.any(Object));
2922
+ addEventListenerSpy.mockClear();
2923
+
2924
+ // Simulate media query change to mobile
2925
+ const desktopMql = mockMQLs.get('(min-width: 1024px)');
2926
+ const mobileMql = mockMQLs.get('(max-width: 767px)');
2927
+ desktopMql.matches = false;
2928
+ mobileMql.matches = true;
2929
+
2930
+ // Trigger the media query change handler
2931
+ const listenerEntry = instance.mediaQueryListeners.get('responsive-element::trigger::0');
2932
+ expect(listenerEntry).toBeDefined();
2933
+ listenerEntry.handler({
2934
+ matches: false,
2935
+ media: '(min-width: 1024px)'
2936
+ });
2937
+
2938
+ // After reconciliation, the hover (mobile) handler should be added
2939
+ expect(addEventListenerSpy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.any(Object));
2940
+ });
2941
+ });
2728
2942
  });
2729
2943
  //# sourceMappingURL=interact.spec.js.map