|
1 | 1 | import mock from 'xhr-mock';
|
2 | 2 |
|
3 |
| -const windowKeys = JSON.parse(JSON.stringify(Object.keys(window))); |
| 3 | +const sideEffects = { |
| 4 | + document: { |
| 5 | + addEventListener: { |
| 6 | + fn: document.addEventListener, |
| 7 | + refs: [], |
| 8 | + }, |
| 9 | + keys: Object.keys(document), |
| 10 | + }, |
| 11 | + window: { |
| 12 | + addEventListener: { |
| 13 | + fn: window.addEventListener, |
| 14 | + refs: [], |
| 15 | + }, |
| 16 | + keys: Object.keys(window), |
| 17 | + }, |
| 18 | +}; |
4 | 19 |
|
5 | 20 | // Lifecycle Hooks
|
6 | 21 | // -----------------------------------------------------------------------------
|
7 |
| -// Soft-reset jsdom. This clears the DOM and removes all attribute from the |
8 |
| -// root element, however it does not undo changes made to jsdom globals like |
9 |
| -// the window or document object. Tests requiring a full jsdom reset should be |
10 |
| -// stored in separate files, as this is the only way (?) to do a complete |
11 |
| -// reset of JSDOM with Jest. |
| 22 | +beforeAll(async () => { |
| 23 | + // Spy addEventListener |
| 24 | + ['document', 'window'].forEach(obj => { |
| 25 | + const fn = sideEffects[obj].addEventListener.fn; |
| 26 | + const refs = sideEffects[obj].addEventListener.refs; |
| 27 | + |
| 28 | + function addEventListenerSpy(type, listener, options) { |
| 29 | + // Store listener reference so it can be removed during reset |
| 30 | + refs.push({ type, listener, options }); |
| 31 | + // Call original window.addEventListener |
| 32 | + fn(type, listener, options); |
| 33 | + } |
| 34 | + |
| 35 | + // Add to default key arry to prevent removal during reset |
| 36 | + sideEffects[obj].keys.push('addEventListener'); |
| 37 | + |
| 38 | + // Replace addEventListener with mock |
| 39 | + global[obj].addEventListener = addEventListenerSpy; |
| 40 | + }); |
| 41 | +}); |
| 42 | + |
| 43 | +// Reset JSDOM. This attempts to remove side effects from tests, however it does |
| 44 | +// not reset all changes made to globals like the the window and document |
| 45 | +// objects. Tests requiring a full JSDOM reset should be stored in separate |
| 46 | +// files, which is only way to do a complete reset of JSDOM with Jest. |
12 | 47 | beforeEach(async () => {
|
13 | 48 | const rootElm = document.documentElement;
|
14 | 49 |
|
| 50 | + // Remove attributes on root element |
| 51 | + [...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name)); |
| 52 | + |
15 | 53 | // Remove elements (faster the setting innerHTML)
|
16 | 54 | while (rootElm.firstChild) {
|
17 | 55 | rootElm.removeChild(rootElm.firstChild);
|
18 | 56 | }
|
19 | 57 |
|
20 |
| - // Remove jest/docsify side-effects |
21 |
| - Object.keys(window) |
22 |
| - .filter(key => !windowKeys.includes(key)) |
23 |
| - .forEach(key => { |
24 |
| - delete window[key]; |
25 |
| - }); |
| 58 | + // Remove global listeners and keys |
| 59 | + ['document', 'window'].forEach(obj => { |
| 60 | + const refs = sideEffects[obj].addEventListener.refs; |
26 | 61 |
|
27 |
| - // Remove attributes |
28 |
| - [...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name)); |
| 62 | + // Listeners |
| 63 | + while (refs.length) { |
| 64 | + const { type, listener, options } = refs.pop(); |
| 65 | + global[obj].removeEventListener(type, listener, options); |
| 66 | + } |
| 67 | + |
| 68 | + // Keys |
| 69 | + Object.keys(global[obj]) |
| 70 | + .filter(key => !sideEffects[obj].keys.includes(key)) |
| 71 | + .forEach(key => { |
| 72 | + delete global[obj][key]; |
| 73 | + }); |
| 74 | + }); |
29 | 75 |
|
30 | 76 | // Restore base elements
|
31 | 77 | rootElm.innerHTML = '<html><head></head><body></body></html>';
|
|
0 commit comments