| /** @module golden/test_util |
| * @description |
| * |
| * <p> |
| * A general set of useful functions for tests and demos, |
| * e.g. reducing boilerplate. |
| * </p> |
| */ |
| |
| /** |
| * 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. |
| * |
| * Sample usage: |
| * |
| * // Code under test. |
| * function doSomethingThatTriggersCustomEvent() { |
| * ... |
| * this.dispatchEvent( |
| * new CustomEvent('my-event', {detail: 'hello world', bubbles: true}); |
| * } |
| * |
| * // Test. |
| * it('should trigger a custom event', async () => { |
| * const myEvent = eventPromise('my-event'); |
| * doSomethingThatTriggersCustomEvent(); |
| * const ev = await myEvent; |
| * expect(ev.detail).to.equal('hello world'); |
| * }); |
| * |
| * Set timeoutMillis = 0 to skip call to setTimeout(). This is necessary on |
| * tests that simulate the passing of time with sinon.useFakeTimers(), which |
| * could trigger the timeout before the promise has a chance to catch the event. |
| * |
| * @param event {string} Name of event to catch. |
| * @param timeoutMillis {number} How long to wait for the event before rejecting |
| * the returned promise. |
| * @return {Promise} A promise that will resolve to the caught event. |
| */ |
| export function eventPromise(event, timeoutMillis = 5000) { |
| const eventCaughtCallback = (resolve, _, e) => resolve(e); |
| const timeoutCallback = (_, reject) => reject(new Error(`timed out after ${timeoutMillis} ms ` |
| + `while waiting to catch event "${event}"`)); |
| return buildEventPromise( |
| event, timeoutMillis, eventCaughtCallback, timeoutCallback, |
| ); |
| } |
| |
| /** |
| * Returns a promise that will resolve if the given DOM event is *not* caught at |
| * the document's body element after the given amount of time, or reject if the |
| * event is caught. |
| * |
| * Useful for testing code that emits an event based on some condition. |
| * |
| * Sample usage: |
| * |
| * // Code under test. |
| * function maybeTriggerCustomEvent(condition) { |
| * if (condition) { |
| * this.dispatchEvent( |
| * new CustomEvent('my-event', {detail: 'hello world', bubbles: true}); |
| * } else { |
| * // Do nothing. |
| * } |
| * } |
| * |
| * // Test. |
| * it('should not trigger a custom event', async () => { |
| * const noEvent = noEventPromise('my-event'); |
| * maybeTriggerCustomEvent(false); |
| * await noEvent; |
| * }); |
| * |
| * Set timeoutMillis = 0 to skip call to setTimeout(). This is necessary on |
| * tests that simulate the passing of time with sinon.useFakeTimers(), which |
| * could trigger the timeout before the promise has a chance to catch the event. |
| * |
| * @param event {string} Name of event to catch. |
| * @param timeoutMillis {number} How long to wait for the event before rejecting |
| * the returned promise. |
| * @return {Promise} A promise that will resolve to the caught event. |
| */ |
| export function noEventPromise(event, timeoutMillis = 200) { |
| const eventCaughtCallback = (_, reject) => reject(new Error(`event "${event}" was caught when none was expected`)); |
| const timeoutCallback = (resolve, _) => resolve(); |
| return buildEventPromise( |
| event, timeoutMillis, eventCaughtCallback, timeoutCallback, |
| ); |
| } |
| |
| /** |
| * Helper function to construct promises based on DOM events. |
| * |
| * @param event {string} Name of event to add a listener for at document.body. |
| * @param timeoutMillis {number} Milliseconds to wait before timing out. |
| * @param eventCaughtCallback {Function} Called when the event is caught with |
| * parameters (resolve, reject, event), where resolve and reject are the |
| * functions passed to the promise's executor function, and event is the |
| * Event object that was caught. |
| * @param timeoutCallback Called after timeoutMillis if no event is caught, with |
| * arguments (resolve, reject) as passed to eventCaughtCallback. |
| * @return {Promise} A promise that will resolve or reject based exclusively on |
| * what the callback functions do with the resolve and reject parameters. |
| */ |
| function buildEventPromise( |
| event, timeoutMillis, eventCaughtCallback, timeoutCallback, |
| ) { |
| // The executor function passed as a constructor argument to the Promise |
| // object is executed immediately. This guarantees that the event handler |
| // is added to document.body before returning. |
| return new Promise((resolve, reject) => { |
| let timeout; |
| const handler = (e) => { |
| document.body.removeEventListener(event, handler); |
| clearTimeout(timeout); |
| eventCaughtCallback(resolve, reject, e); |
| }; |
| // Skip setTimeout() call with timeoutMillis = 0. Useful when faking time in |
| // tests with sinon.useFakeTimers(). See |
| // https://sinonjs.org/releases/v7.5.0/fake-timers/. |
| if (timeoutMillis !== 0) { |
| timeout = setTimeout(() => { |
| document.body.removeEventListener(event, handler); |
| timeoutCallback(resolve, reject); |
| }, timeoutMillis); |
| } |
| document.body.addEventListener(event, handler); |
| }); |
| } |
| |
| /** |
| * Asserts that there the given string exactly matches the current query string |
| * of the url bar. For example, expectQueryStringToEqual('?foo=bar&foo=orange'); |
| */ |
| export function expectQueryStringToEqual(expected) { |
| expect(window.location.search).to.equal(expected); |
| } |