[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;