blob: 26a94c0139ec253a84161f789a5a2b0a9619846b [file] [log] [blame]
import { expect } from 'chai';
import { $, $$ } from 'common-sk/modules/dom';
import sinon from 'sinon';
import {
setUpElementUnderTest,
eventPromise,
noEventPromise,
expectQueryStringToEqual,
} from './test_util';
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: HTMLMarqueeElement;
let instance2: HTMLMarqueeElement;
// 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<HTMLMarqueeElement>('marquee');
let element: HTMLMarqueeElement; // 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($$<HTMLParagraphElement>('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: HTMLDivElement; // Element that we'll dispatch custom events from.
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
el = document.createElement('div');
document.body.appendChild(el);
clock = sinon.useFakeTimers();
});
afterEach(() => {
document.body.removeChild(el);
clock.restore();
});
describe('eventPromise', () => {
it('resolves when event is caught', async () => {
const hello = eventPromise<CustomEvent<string>>('hello');
el.dispatchEvent(new CustomEvent('hello', { bubbles: true, detail: 'hi' }));
const ev = await hello;
expect(ev.detail).to.equal('hi');
});
it('catches document-level events', async () => {
const hello = eventPromise<CustomEvent<string>>('hello');
document.dispatchEvent(new CustomEvent('hello', { bubbles: true, detail: 'hi' }));
const ev = await hello;
expect(ev.detail).to.equal('hi');
});
it('one single event resolves multiple promises', async () => {
const hello1 = eventPromise<CustomEvent<string>>('hello');
const hello2 = eventPromise<CustomEvent<string>>('hello');
// We'll emit two different events of the same type (see event detail).
el.dispatchEvent(new CustomEvent('hello', { bubbles: true, detail: 'hi' }));
el.dispatchEvent(new CustomEvent('hello', { bubbles: true, detail: 'goodbye' }));
const ev1 = await hello1;
const ev2 = await hello2;
// The first event above should resolve both promises.
expect(ev1.detail).to.equal('hi');
expect(ev2.detail).to.equal('hi');
});
it('times out if event is not caught', async () => {
const hello = eventPromise<CustomEvent<string>>('hello', 5000);
el.dispatchEvent(new CustomEvent('bye', { bubbles: true }));
clock.tick(10000);
try {
await hello;
expect.fail('promise should not have resolved');
} catch (error) {
expect(error.message).to.equal(
'timed out after 5000 ms while waiting to catch event "hello"',
);
}
});
it('never times out if timeoutMillis=0', async () => {
const hello = eventPromise<CustomEvent<string>>('hello', 0);
clock.tick(Number.MAX_SAFE_INTEGER);
el.dispatchEvent(new CustomEvent('hello', { bubbles: true, detail: 'hi' }));
const ev = await hello;
expect(ev.detail).to.equal('hi');
});
});
describe('noEventPromise', () => {
it('resolves when event is NOT caught', async () => {
const noHello = noEventPromise('hello', 200);
el.dispatchEvent(new CustomEvent('bye', { bubbles: true }));
clock.tick(10000);
await noHello;
});
it('rejects if event is caught', async () => {
const noHello = noEventPromise('hello', 200);
el.dispatchEvent(new CustomEvent('hello', { bubbles: true }));
try {
await noHello;
expect.fail('promise should not have resolved');
} catch (error) {
expect(error.message).to.equal(
'event "hello" was caught when none was expected',
);
}
});
it('never resolves if timeoutMillis=0', async () => {
const noHello = noEventPromise('hello', 0);
clock.tick(Number.MAX_SAFE_INTEGER);
el.dispatchEvent(new CustomEvent('hello', { bubbles: true }));
try {
await noHello;
expect.fail('promise should not have resolved');
} catch (error) {
expect(error.message).to.equal(
'event "hello" was caught when none was expected',
);
}
});
});
});
describe('expectQueryStringToEqual', () => {
it('matches empty string when query is empty', () => {
history.pushState(null, '', // these are empty as they do not affect the test.
window.location.origin + window.location.pathname);
expectQueryStringToEqual('');
});
it('matches the query params when query is not emtpy', () => {
// reset to known blank state
history.pushState(null, '', // these are empty as they do not affect the test.
window.location.origin + window.location.pathname);
// push some query params
history.pushState(null, '', '?foo=bar&alpha=beta&alpha=gamma');
expectQueryStringToEqual('?foo=bar&alpha=beta&alpha=gamma');
});
});
});