File size: 3,974 Bytes
da96562
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*!
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import MatomoModal from './MatomoModal.vue';

const activeWrappers: Array<{ unmount: () => void }> = [];

function mountModal(extraProps: Record<string, unknown> = {}, slots: Record<string, string> = {}) {
  const wrapper = mount(MatomoModal, {
    attachTo: document.body,
    props: {
      modelValue: false,
      ...extraProps,
    },
    slots: {
      default: '<p class="body-text">modal body</p>',
      ...slots,
    },
  });

  activeWrappers.push(wrapper);
  return wrapper;
}

function getModal(): HTMLElement {
  return document.body.querySelector('.modal') as HTMLElement;
}

function getOverlay(): HTMLElement | null {
  return document.body.querySelector('.modal-overlay');
}

async function settle() {
  await nextTick();
  await nextTick();
}

describe('CoreHome/MatomoModal', () => {
  beforeEach(() => {
    document.body.style.overflow = '';
  });

  afterEach(() => {
    while (activeWrappers.length) {
      activeWrappers.pop()!.unmount();
    }
    document.body.innerHTML = '';
  });

  it('renders its content and optional footer with modal classes and attributes', () => {
    mountModal({
      ariaLabel: 'My Modal',
      classes: ['custom-modal', { secondary: true }],
      contentClass: 'custom-content',
    }, {
      footer: '<button class="footer-btn">OK</button>',
    });

    const modal = getModal();

    expect(modal.querySelector('.modal-content .body-text')?.textContent).toBe('modal body');
    expect(modal.querySelector('.modal-footer .footer-btn')?.textContent).toBe('OK');
    expect(modal.classList.contains('open')).toBe(false);
    expect(modal.getAttribute('role')).toBe('dialog');
    expect(modal.getAttribute('aria-modal')).toBe('true');
    expect(modal.getAttribute('aria-label')).toBe('My Modal');
    expect(modal.classList.contains('custom-modal')).toBe(true);
    expect(modal.classList.contains('secondary')).toBe(true);
    expect(modal.querySelector('.modal-content')?.classList.contains('custom-content')).toBe(true);
  });

  it('locks scroll, focuses the modal, and emits lifecycle events when opened and closed', async () => {
    document.body.style.overflow = 'auto';
    const trigger = document.createElement('button');
    document.body.appendChild(trigger);
    trigger.focus();

    const wrapper = mountModal();

    await wrapper.setProps({ modelValue: true });
    await settle();

    const modal = getModal();

    expect(modal.classList.contains('open')).toBe(true);
    expect(getOverlay()?.classList.contains('open')).toBe(true);
    expect(document.body.style.overflow).toBe('hidden');
    expect(document.activeElement).toBe(modal);
    expect(wrapper.emitted('opened')?.[0]?.[0]).toBe(modal);

    await wrapper.setProps({ modelValue: false });
    await settle();

    expect(getOverlay()).toBeNull();
    expect(document.body.style.overflow).toBe('auto');
    expect(document.activeElement).toBe(trigger);
    expect(wrapper.emitted('closed')).toHaveLength(1);
  });

  it('requests close when Escape is pressed or the overlay is clicked', async () => {
    const wrapper = mountModal({ modelValue: true });
    await settle();

    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
    getOverlay()?.dispatchEvent(new MouseEvent('click', { bubbles: true }));

    expect(wrapper.emitted('update:modelValue')).toEqual([[false], [false]]);
  });

  it('cleans up global side effects when unmounted while open', async () => {
    const wrapper = mountModal({ modelValue: true });
    await settle();

    wrapper.unmount();
    activeWrappers.pop();

    expect(document.body.style.overflow).toBe('');
    expect(() => {
      document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
    }).not.toThrow();
  });
});