[gold] test_util.js: Add function setUpElementUnderTest.

This function returns a factory function that can be used to instantiate the element under test, and sets up an afterEach() hook to automatically remove it from the DOM after each test.

Notes:
 - It captures the component instantiation pattern currently in place on all our custom element tests.
 - I'll follow up with a refactor in which tests will rely on this function instead.
 - This should reduce boilerplate and hopefully make our tests easier to read.
 - Moving forward, this will reduce the chances of writing buggy tests by e.g. forgetting to clean up the element under test after each test case.

Bug: skia:9525
Change-Id: Ic061178c7ca6cd8aa0f75825339dc6fb17605c15
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/265221
Commit-Queue: Leandro Lovisolo <lovisolo@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/golden/modules/test_util.js b/golden/modules/test_util.js
index 82308e0..8adf4a9 100644
--- a/golden/modules/test_util.js
+++ b/golden/modules/test_util.js
@@ -8,6 +8,60 @@
  */
 
 /**
+ * Takes a DOM element name (e.g. 'my-component-sk') and returns a factory
+ * function that can be used to obtain new instances of that element.
+ *
+ * The element returned by the factory function is attached to document.body,
+ * and an afterEach() hook is set to automatically remove the element from the
+ * DOM after each test.
+ *
+ * The returned factory function optionally takes a callback function that will
+ * be called with the newly instantiated element before it is attached to the
+ * DOM, giving client code a chance to finish setting up the element before e.g.
+ * the element's connecetedCallback() method is invoked.
+ *
+ * Sample usage:
+ *
+ *   describe('my-component-sk', () => {
+ *     const newInstance = setUpElementUnderTest('my-component-sk');
+ *
+ *     it('should be correctly instantiated', () => {
+ *       const myComponentSk = newInstance((el) => {
+ *         // This is called before attaching the element to the DOM.
+ *         el.setAttribute('hello', 'world');
+ *       });
+ *
+ *       expect(myComponentSk.parentElement).to.equal(document.body);
+ *       expect(myComponentSk.getAttribute('hello')).to.equal('world');
+ *     });
+ *   });
+ *
+ * @param elementName {string} Name of the element to test, e.g. 'foo-sk'.
+ * @return {Function} A factory function that optionally takes a callback which
+ *     is invoked with the newly instantiated element before it is attached to
+ *     the DOM.
+ */
+export function setUpElementUnderTest(elementName) {
+  let element;
+
+  afterEach(() => {
+    if (element) {
+      document.body.removeChild(element);
+      element = null;
+    }
+  });
+
+  return (finishSetupCallbackFn) => {
+    element = document.createElement(elementName);
+    if (finishSetupCallbackFn !== undefined) {
+      finishSetupCallbackFn(element);
+    }
+    document.body.appendChild(element);
+    return element;
+  };
+}
+
+/**
  * Returns a promise that will resolve when the given DOM event is caught at the
  * document's body element, or reject if the event isn't caught within the given
  * amount of time.
diff --git a/golden/modules/test_util_test.js b/golden/modules/test_util_test.js
index 8814788..be0b42b 100644
--- a/golden/modules/test_util_test.js
+++ b/golden/modules/test_util_test.js
@@ -1,10 +1,90 @@
 import {
+  setUpElementUnderTest,
   eventPromise,
   noEventPromise,
   expectQueryStringToEqual
 } from './test_util';
+import { $, $$ } from 'common-sk/modules/dom'
 
 describe('test utilities', () => {
+  describe('setUpElementUnderTest', () => {
+    // We'll save references to the instances of the element under test created
+    // by setUpElementUnderTest, and make assertions against them later on.
+    let instance1, instance2;
+
+    // We run setUpElementUnderTest inside its own nested describe block to
+    // limit the scope of the afterEach hook it sets up.
+    describe('test suite with setUpElementUnderTest', () => {
+      // We'll use <marquee> as the element under test.
+      const newInstance = setUpElementUnderTest('marquee');
+
+      let element;  // Instance of the element under test.
+      beforeEach(() => {
+        expect(
+                $('marquee'),
+                'no other <marquee> elements should be present in the DOM ' +
+                'prior to instantiating the element under test')
+            .to.have.length(0);
+
+        // Instantiate the element under test.
+        element = newInstance((el) => el.innerHTML = '<p>hello world</p>');
+      });
+
+      afterEach(() => {
+        expect(
+                $('marquee'),
+                'no instances of the element under test should be found in ' +
+                'the DOM after each test case')
+            .to.have.length(0);
+        expect(
+                element.parentElement,
+                'element under test should be detached from its parent node ' +
+                'after each test case')
+            .to.be.null;
+      });
+
+      it('should correctly instantiate the element', () => {
+        instance1 = element;  // Save a reference to the current instance.
+        expect(element.tagName).to.equal('MARQUEE');
+        expect($$('p', element).innerText).to.equal('hello world');
+      });
+
+      it('should attach instance of element under test to document.body',
+          () => {
+        instance2 = element;  // Save a reference to the current instance.
+        expect($('marquee')).to.have.length(1);
+        expect(element.parentElement).to.equal(document.body);
+      });
+    });
+
+    // This describe block makes use of the fact that sibling describe blocks
+    // are run in the order they are defined, as explained in
+    // https://mochajs.org/#run-cycle-overview.
+    describe('after the "setUpElementUnderTest" test suite runs', () => {
+      // Assert that we've correctly captured the instances of the element under
+      // test, which the test cases below rely on.
+      it('should have correctly saved instances of the element under test',
+          () => {
+        expect(instance1.tagName).to.equal('MARQUEE');
+        expect(instance2.tagName).to.equal('MARQUEE');
+      });
+
+      it('creates fresh instances before each test case', () => {
+        expect(instance1).to.not.equal(instance2);
+      });
+
+      it('should detach instances from the DOM after each test case', () => {
+        expect(instance1.parentElement).to.be.null;
+        expect(instance2.parentElement).to.be.null;
+      });
+
+      it('no stray instances left on the test runner page after tests end',
+          () => {
+        expect($('marquee')).to.have.length(0);
+      });
+    });
+  });
+
   describe('event promise functions', () => {
     let el; // Element that we'll dispatch custom events from.
     let clock;