import mock from 'xhr-mock';

const sideEffects = {
  document: {
    addEventListener: {
      fn: document.addEventListener,
      refs: [],
    },
    keys: Object.keys(document),
  },
  window: {
    addEventListener: {
      fn: window.addEventListener,
      refs: [],
    },
    keys: Object.keys(window),
  },
};

// Lifecycle Hooks
// -----------------------------------------------------------------------------
beforeAll(async () => {
  // Spy addEventListener
  ['document', 'window'].forEach(obj => {
    const fn = sideEffects[obj].addEventListener.fn;
    const refs = sideEffects[obj].addEventListener.refs;

    function addEventListenerSpy(type, listener, options) {
      // Store listener reference so it can be removed during reset
      refs.push({ type, listener, options });
      // Call original window.addEventListener
      fn(type, listener, options);
    }

    // Add to default key arry to prevent removal during reset
    sideEffects[obj].keys.push('addEventListener');

    // Replace addEventListener with mock
    global[obj].addEventListener = addEventListenerSpy;
  });
});

// Reset JSDOM. This attempts to remove side effects from tests, however it does
// not reset all changes made to globals like the the window and document
// objects. Tests requiring a full JSDOM reset should be stored in separate
// files, which is only way to do a complete reset of JSDOM with Jest.
beforeEach(async () => {
  const rootElm = document.documentElement;

  // Remove attributes on root element
  [...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name));

  // Remove elements (faster the setting innerHTML)
  while (rootElm.firstChild) {
    rootElm.removeChild(rootElm.firstChild);
  }

  // Remove global listeners and keys
  ['document', 'window'].forEach(obj => {
    const refs = sideEffects[obj].addEventListener.refs;

    // Listeners
    while (refs.length) {
      const { type, listener, options } = refs.pop();
      global[obj].removeEventListener(type, listener, options);
    }

    // Keys
    Object.keys(global[obj])
      .filter(key => !sideEffects[obj].keys.includes(key))
      .forEach(key => {
        delete global[obj][key];
      });
  });

  // Restore base elements
  rootElm.innerHTML = '<html><head></head><body></body></html>';
});

afterEach(async () => {
  // Restore the global XMLHttpRequest object to its original state
  mock.teardown();
});

@@ -910,16 +910,17 @@
  key arr
+a
 y to pre
@@ -941,24 +941,24 @@
 uring reset%0A
-
     sideEffe
@@ -1238,20 +1238,16 @@
 ike the 
-the 
 window a
@@ -1383,22 +1383,19 @@
 ete 
-reset of JSDOM
+JSDOM reset
  wit
@@ -1622,17 +1622,18 @@
 aster th
-e
+an
  setting
@@ -2260,14 +2260,8 @@
 = '%3C
-html%3E%3C
 head
@@ -2285,15 +2285,8 @@
 ody%3E
-%3C/html%3E
 ';%0A%7D

Fix base element reset