Repository: ProjectEvergreen/wcc (js)

Issue:
add support for `addEventListener` as a no-op
## Type of Change

<!-- Delete what does not apply -->

- New Feature Request

## Summary
At the very least, we should be able to provide a no-op for [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener).

## Details
So a case like this should not error at least
```js
class MyComponent extends HTMLElement {
  constructor() {
    super();

    this.addEventListener('someCustomEvent', () => { console.log('it worked!'); });
  }

  connectedCallback() {
    this.innerHTML = '<h1>It worked!</h1>';
  }
}
```

Pull request description:
Feature/issue 81 add event listener (as a no-op)
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #81 

## Summary of Changes
1. Add support for `addEventListener` as a `noop`
1. Document as a feature
1. Minor refactoring and `console.log` cleanup

Patch:
diff --git a/docs/pages/index.md b/docs/pages/index.md
index e5a4e18..b945829 100644
--- a/docs/pages/index.md
+++ b/docs/pages/index.md
@@ -19,6 +19,7 @@ $ npm install wc-compiler --save-dev
     - `innerHTML`
     - `[get|set|has]Attribute`
 1. `<template>` / `DocumentFragment`
+1. `addEventListener` (as a no-op)
 1. Recursive rendering of nested custom elements
 1. Metadata and runtime hints to support various progressive hydration and lazy loading strategies
 
diff --git a/src/dom-shim.js b/src/dom-shim.js
index 71fcee0..3fc2956 100644
--- a/src/dom-shim.js
+++ b/src/dom-shim.js
@@ -1,15 +1,16 @@
+function noop() { }
+
 // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
-class EventTarget { }
+class EventTarget {
+  constructor() {
+    this.addEventListener = noop;
+  }
+}
 
 // https://developer.mozilla.org/en-US/docs/Web/API/Node
 // EventTarget <- Node
 // TODO should be an interface?
 class Node extends EventTarget {
-  constructor() {
-    super();
-    // console.debug('Node constructor');
-  }
-
   // eslint-disable-next-line
   cloneNode(deep) {
     return this;
@@ -25,7 +26,6 @@ class Node extends EventTarget {
 class Element extends Node {
   constructor() {
     super();
-    // console.debug('Element constructor');
     this.shadowRoot = null;
     this.innerHTML;
     this.attributes = {};
@@ -44,24 +44,37 @@ class Element extends Node {
   }
 }
 
+// https://developer.mozilla.org/en-US/docs/Web/API/Document
+// EventTarget <- Node <- Document
+class Document extends Node {
+
+  createElement(tagName) {
+    switch (tagName) {
+
+      case 'template':
+        return new HTMLTemplateElement();
+
+      default:
+        return new HTMLElement();
+
+    }
+  }
+
+  createDocumentFragment(html) {
+    return new DocumentFragment(html);
+  }
+}
+
 // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
 // EventTarget <- Node <- Element <- HTMLElement
 class HTMLElement extends Element {
-  constructor() {
-    super();
-    // console.debug('HTMLElement::constructor');
-  }
-
   attachShadow(options) {
-    // console.debug('HTMLElement::attachShadow');
     this.shadowRoot = new ShadowRoot(options);
 
     return this.shadowRoot;
   }
 
-  connectedCallback() {
-    // console.debug('HTMLElement::connectedCallback');
-  }
+  connectedCallback() { }
 
   // https://github.com/mfreed7/declarative-shadow-dom/blob/master/README.md#serialization
   // eslint-disable-next-line
@@ -72,20 +85,13 @@ class HTMLElement extends Element {
 
 // https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
 // EventTarget <- Node <- DocumentFragment
-class DocumentFragment extends Node {
-  // eslint-disable-next-line
-  constructor(contents) {
-    super();
-    // console.debug('DocumentFragment constructor', contents);
-  }
-}
+class DocumentFragment extends Node { }
 
 // https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
 // EventTarget <- Node <- DocumentFragment <- ShadowRoot
 class ShadowRoot extends DocumentFragment {
   constructor(options) {
     super();
-    // console.debug('ShadowRoot constructor');
     this.mode = options.mode || 'closed';
   }
 }
@@ -95,8 +101,6 @@ class ShadowRoot extends DocumentFragment {
 class HTMLTemplateElement extends HTMLElement {
   constructor() {
     super();
-    // console.debug('HTMLTemplateElement constructor');
-
     this.content = new DocumentFragment();
   }
 
@@ -114,33 +118,24 @@ class HTMLTemplateElement extends HTMLElement {
   }
 }
 
-const customElementsRegistry = {};
-
-globalThis.document = {
-  createElement(tagName) {
-    switch (tagName) {
-
-      case 'template':
-        return new HTMLTemplateElement();
-
-      default:
-        return new HTMLElement();
+// https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry
+class CustomElementsRegistry {
+  constructor() {
+    this.customElementsRegistry = {};
+  }
 
-    }
-  },
-  createDocumentFragment(html) {
-    return new DocumentFragment(html);
+  define(tagName, BaseClass) {
+    this.customElementsRegistry[tagName] = BaseClass;
   }
-};
 
-globalThis.HTMLElement = HTMLElement;
-globalThis.customElements = {
-  define: (tagName, BaseClass) => {
-    // console.debug('customElements.define => ', tagName);
-    customElementsRegistry[tagName] = BaseClass;
-  },
-  get: (tagName) => {
-    // console.debug('customElements.get => ', tagName);
-    return customElementsRegistry[tagName];
+  get(tagName) {
+    return this.customElementsRegistry[tagName];
   }
-};
\ No newline at end of file
+}
+
+// mock top level aliases (globalThis === window)
+// https://developer.mozilla.org/en-US/docs/Web/API/Window
+globalThis.addEventListener = noop;
+globalThis.document = new Document();
+globalThis.customElements = new CustomElementsRegistry();
+globalThis.HTMLElement = HTMLElement;
\ No newline at end of file


Test patch:
diff --git a/test/cases/event-listener/event-listener.spec.js b/test/cases/event-listener/event-listener.spec.js
new file mode 100644
index 0000000..819f23b
--- /dev/null
+++ b/test/cases/event-listener/event-listener.spec.js
@@ -0,0 +1,34 @@
+/*
+ * Use Case
+ * Run wcc against a single custom element using addEventListener
+ *
+ * User Result
+ * Should run without any errors from the DOM shim.
+ *
+ * User Workspace
+ * src/
+ *   my-component.js
+ */
+
+import chai from 'chai';
+import { renderToString } from '../../../src/wcc.js';
+
+const expect = chai.expect;
+
+describe('Run WCC For ', function() {
+  const LABEL = 'Single Custom Element using addEventListener';
+  let rawHtml;
+
+  before(async function() {
+    const { html } = await renderToString(new URL('./src/my-component.js', import.meta.url));
+
+    rawHtml = html;
+  });
+
+  describe(LABEL, function() {
+
+    it('should do something', function() {
+      expect(rawHtml).to.contain('<h1>It worked!</h1>');
+    });
+  });
+});
\ No newline at end of file
diff --git a/test/cases/event-listener/src/my-component.js b/test/cases/event-listener/src/my-component.js
new file mode 100644
index 0000000..b9c0f14
--- /dev/null
+++ b/test/cases/event-listener/src/my-component.js
@@ -0,0 +1,15 @@
+class MyComponent extends HTMLElement {
+  constructor() {
+    super();
+
+    this.addEventListener('someCustomEvent', () => { console.log('it worked!'); });
+  }
+
+  connectedCallback() {
+    this.innerHTML = '<h1>It worked!</h1>';
+  }
+}
+
+export default MyComponent;
+
+customElements.define('my-component', MyComponent);
\ No newline at end of file