UNPKG

qunit-dom

Version:

High Level DOM Assertions for QUnit

1,879 lines 65.8 kB
import { createDescriptor as createDescriptor$1, isDescriptor, resolveDOMElement, resolveDOMElements, resolveDescription } from 'dom-element-descriptors';

function exists(options, message) {
    let expectedCount = null;
    if (typeof options === 'string') {
        message = options;
    }
    else if (options) {
        expectedCount = options.count;
    }
    let elements = this.findElements();
    if (expectedCount === null) {
        let result = elements.length > 0;
        let expected = format$1(this.targetDescription);
        let actual = result ? expected : format$1(this.targetDescription, 0);
        if (!message) {
            message = expected;
        }
        this.pushResult({ result, actual, expected, message });
    }
    else if (typeof expectedCount === 'number') {
        let result = elements.length === expectedCount;
        let actual = format$1(this.targetDescription, elements.length);
        let expected = format$1(this.targetDescription, expectedCount);
        if (!message) {
            message = expected;
        }
        this.pushResult({ result, actual, expected, message });
    }
    else {
        throw new TypeError(`Unexpected Parameter: ${expectedCount}`);
    }
}
function format$1(selector, num) {
    if (selector === '<unknown>') {
        selector = '<not found>';
    }
    if (num === undefined || num === null) {
        return `Element ${selector} exists`;
    }
    else if (num === 0) {
        return `Element ${selector} does not exist`;
    }
    else if (num === 1) {
        return `Element ${selector} exists once`;
    }
    else if (num === 2) {
        return `Element ${selector} exists twice`;
    }
    else {
        return `Element ${selector} exists ${num} times`;
    }
}

// imported from https://github.com/nathanboktae/chai-dom
function elementToString(el) {
    if (!el)
        return '<not found>';
    let desc;
    if (el instanceof NodeList) {
        if (el.length === 0) {
            return 'empty NodeList';
        }
        desc = Array.prototype.slice.call(el, 0, 5).map(elementToString).join(', ');
        return el.length > 5 ? `${desc}... (+${el.length - 5} more)` : desc;
    }
    if (!(el instanceof HTMLElement || el instanceof SVGElement)) {
        return String(el);
    }
    desc = el.tagName.toLowerCase();
    if (el.id) {
        desc += `#${el.id}`;
    }
    if (el.className && !(el.className instanceof SVGAnimatedString)) {
        desc += `.${String(el.className).replace(/\s+/g, '.')}`;
    }
    Array.prototype.forEach.call(el.attributes, function (attr) {
        if (attr.name !== 'class' && attr.name !== 'id') {
            desc += `[${attr.name}${attr.value ? `="${attr.value}"]` : ']'}`;
        }
    });
    return desc;
}

function focused(message) {
    let element = this.findTargetElement();
    if (!element)
        return;
    let result = document.activeElement === element;
    let actual = elementToString(document.activeElement);
    let expected = this.targetDescription;
    if (!message) {
        message = `Element ${expected} is focused`;
    }
    this.pushResult({ result, actual, expected, message });
}

function notFocused(message) {
    let element = this.findTargetElement();
    if (!element)
        return;
    let result = document.activeElement !== element;
    let expected = `Element ${this.targetDescription} is not focused`;
    let actual = result ? expected : `Element ${this.targetDescription} is focused`;
    if (!message) {
        message = expected;
    }
    this.pushResult({ result, message, actual, expected });
}

function checked(message) {
    let element = this.findTargetElement();
    if (!element)
        return;
    let isChecked = element.checked === true;
    let isNotChecked = element.checked === false;
    let result = isChecked;
    let hasCheckedProp = isChecked || isNotChecked;
    if (!hasCheckedProp) {
        let ariaChecked = element.getAttribute('aria-checked');
        if (ariaChecked !== null) {
            result = ariaChecked === 'true';
        }
    }
    let actual = result ? 'checked' : 'not checked';
    let expected = 'checked';
    if (!message) {
        message = `Element ${this.targetDescription} is checked`;
    }
    this.pushResult({ result, actual, expected, message });
}

function notChecked(message) {
    let element = this.findTargetElement();
    if (!element)
        return;
    let isChecked = element.checked === true;
    let isNotChecked = element.checked === false;
    let result = !isChecked;
    let hasCheckedProp = isChecked || isNotChecked;
    if (!hasCheckedProp) {
        let ariaChecked = element.getAttribute('aria-checked');
        if (ariaChecked !== null) {
            result = ariaChecked !== 'true';
        }
    }
    let actual = result ? 'not checked' : 'checked';
    let expected = 'not checked';
    if (!message) {
        message = `Element ${this.targetDescription} is not checked`;
    }
    this.pushResult({ result, actual, expected, message });
}

function required(message) {
    let element = this.findTargetElement();
    if (!element)
        return;
    if (!(element instanceof HTMLInputElement ||
        element instanceof HTMLTextAreaElement ||
        element instanceof HTMLSelectElement)) {
        throw new TypeError(`Unexpected Element Type: ${element.toString()}`);
    }
    let result = element.required === true;
    let actual = result ? 'required' : 'not required';
    let expected = 'required';
    if (!message) {
        message = `Element ${this.targetDescription} is required`;
    }
    this.pushResult({ result, actual, expected, message });
}

function notRequired(message) {
    let element = this.findTargetElement();
    if (!element)
        return;
    if (!(element instanceof HTMLInputElement ||
        element instanceof HTMLTextAreaElement ||
        element instanceof HTMLSelectElement)) {
        throw new TypeError(`Unexpected Element Type: ${element.toString()}`);
    }
    let result = element.required === false;
    let actual = !result ? 'required' : 'not required';
    let expected = 'not required';
    if (!message) {
        message = `Element ${this.targetDescription} is not required`;
    }
    this.pushResult({ result, actual, expected, message });
}

function isValid(message, options = {}) {
    let element = this.findTargetElement();
    if (!element)
        return;
    if (!(element instanceof HTMLFormElement ||
        element instanceof HTMLInputElement ||
        element instanceof HTMLTextAreaElement ||
        element instanceof HTMLButtonElement ||
        element instanceof HTMLOutputElement ||
        element instanceof HTMLSelectElement)) {
        throw new TypeError(`Unexpected Element Type: ${element.toString()}`);
    }
    let validity = element.reportValidity() === true;
    let result = validity === !options.inverted;
    let actual = validity ? 'valid' : 'not valid';
    let expected = options.inverted ? 'not valid' : 'valid';
    if (!message) {
        message = `Element ${this.targetDescription} is ${actual}`;
    }
    this.pushResult({ result, actual, expected, message });
}

// Visible logic based on jQuery's
// https://github.com/jquery/jquery/blob/4a2bcc27f9c3ee24b3effac0fbe1285d1ee23cc5/src/css/hiddenVisibleSelectors.js#L11-L13
function visible(el) {
    if (el === null)
        return false;
    if (el.offsetWidth === 0 || el.offsetHeight === 0)
        return false;
    let clientRects = el.getClientRects();
    if (clientRects.length === 0)
        return false;
    for (let i = 0; i < clientRects.length; i++) {
        let rect = clientRects[i];
        if (rect.width !== 0 && rect.height !== 0)
            return true;
    }
    return false;
}

function isVisible(options, message) {
    let expectedCount = null;
    if (typeof options === 'string') {
        message = options;
    }
    else if (options) {
        expectedCount = options.count;
    }
    let elements = this.findElements().filter(visible);
    if (expectedCount === null) {
        let result = elements.length > 0;
        let expected = format(this.targetDescription);
        let actual = result ? expected : format(this.targetDescription, 0);
        if (!message) {
            message = expected;
        }
        this.pushResult({ result, actual, expected, message });
    }
    else if (typeof expectedCount === 'number') {
        let result = elements.length === expectedCount;
        let actual = format(this.targetDescription, elements.length);
        let expected = format(this.targetDescription, expectedCount);
        if (!message) {
            message = expected;
        }
        this.pushResult({ result, actual, expected, message });
    }
    else {
        throw new TypeError(`Unexpected Parameter: ${expectedCount}`);
    }
}
function format(selector, num) {
    if (selector === '<unknown>') {
        selector = '<not found>';
    }
    if (num === undefined || num === null) {
        return `Element ${selector} is visible`;
    }
    else if (num === 0) {
        return `Element ${selector} is not visible`;
    }
    else if (num === 1) {
        return `Element ${selector} is visible once`;
    }
    else if (num === 2) {
        return `Element ${selector} is visible twice`;
    }
    else {
        return `Element ${selector} is visible ${num} times`;
    }
}

function isDisabled(message, options = {}) {
    let { inverted } = options;
    let element = this.findTargetElement();
    if (!element)
        return;
    if (!(element instanceof HTMLInputElement ||
        element instanceof HTMLTextAreaElement ||
        element instanceof HTMLSelectElement ||
        element instanceof HTMLButtonElement ||
        element instanceof HTMLOptGroupElement ||
        element instanceof HTMLOptionElement ||
        element instanceof HTMLFieldSetElement)) {
        throw new TypeError(`Unexpected Element Type: ${element.toString()}`);
    }
    let result = element.disabled === !inverted;
    let actual = element.disabled === false
        ? `Element ${this.targetDescription} is not disabled`
        : `Element ${this.targetDescription} is disabled`;
    let expected = inverted
        ? `Element ${this.targetDescription} is not disabled`
        : `Element ${this.targetDescription} is disabled`;
    if (!message) {
        message = expected;
    }
    this.pushResult({ result, actual, expected, message });
}

function matchesSelector(elements, compareSelector) {
    let failures = elements.filter(it => !it.matches(compareSelector));
    return failures.length;
}

function collapseWhitespace(string) {
    return string
        .replace(/[\t\r\n]/g, ' ')
        .replace(/ +/g, ' ')
        .replace(/^ /, '')
        .replace(/ $/, '');
}

/**
 * @ignore
 * Descriptor data for creating an {@link IDOMElementDescriptor} from a CSS
 * selector
 */
class SelectorData {
    selector;
    rootElement;
    constructor(selector, rootElement) {
        this.selector = selector;
        this.rootElement = rootElement;
    }
    get element() {
        return this.rootElement.querySelector(this.selector);
    }
    get elements() {
        return Array.from(this.rootElement.querySelectorAll(this.selector));
    }
    get description() {
        return this.selector;
    }
}
/**
 * @ignore
 * Descriptor data for creating an {@link IDOMElementDescriptor} from an
 * {@link Element}
 */
class ElementData {
    element;
    constructor(element) {
        this.element = element;
    }
    get description() {
        return elementToString(this.element);
    }
}
/**
 * @ignore
 * Create an {@link IDOMElementDescriptor} from a target and a root element
 */
function createDescriptor(target, rootElement) {
    if (typeof target === 'string') {
        // selector
        if (!rootElement) {
            throw new Error('Cannot do selector-based queries without a root element');
        }
        return createDescriptor$1(new SelectorData(target, rootElement));
    }
    else if (target instanceof Element) {
        // element
        return createDescriptor$1(new ElementData(target));
    }
    else if (target === null) {
        // null, which we treat as an unmatched element, e.g.
        // `createDescriptor(document.querySelector('.does-not-exist'))`
        return createDescriptor$1({ element: null, description: '<unknown>' });
    }
    else if (isDescriptor(target)) {
        // already a descriptor
        return target;
    }
    else {
        throw new TypeError(`Unexpected Parameter: ${target}`);
    }
}

/**
 * @namespace
 */
class DOMAssertions {
    testContext;
    /**
     * @ignore
     * The target of our assertions
     */
    descriptor;
    /**
     * @ignore
     * Whether we were constructed with an element, rather than a selector or
     * descriptor. Used to make error messages more helpful.
     */
    wasPassedElement;
    /**
     * @hideconstructor
     */
    constructor(target, rootElement, testContext) {
        this.testContext = testContext;
        this.descriptor = createDescriptor(target, rootElement);
        this.wasPassedElement = target instanceof Element;
    }
    /**
     * Assert an {@link HTMLElement} (or multiple) matching the `selector` exists.
     *
     * @param {object?} options
     * @param {number?} options.count
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').exists();
     * assert.dom('.choice').exists({ count: 4 });
     *
     * @see {@link #doesNotExist}
     */
    exists(...options) {
        exists.call(this, ...options);
        return this;
    }
    /**
     * Assert an {@link HTMLElement} matching the `selector` does not exists.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('.should-not-exist').doesNotExist();
     *
     * @see {@link #exists}
     */
    doesNotExist(message) {
        exists.call(this, { count: 0 }, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is currently checked.
     *
     * Note: This also supports `aria-checked="true/false"`.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input.active').isChecked();
     *
     * @see {@link #isNotChecked}
     */
    isChecked(message) {
        checked.call(this, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is currently unchecked.
     *
     * Note: This also supports `aria-checked="true/false"`.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input.active').isNotChecked();
     *
     * @see {@link #isChecked}
     */
    isNotChecked(message) {
        notChecked.call(this, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is currently focused.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input.email').isFocused();
     *
     * @see {@link #isNotFocused}
     */
    isFocused(message) {
        focused.call(this, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is not currently focused.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input[type="password"]').isNotFocused();
     *
     * @see {@link #isFocused}
     */
    isNotFocused(message) {
        notFocused.call(this, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is currently required.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input[type="text"]').isRequired();
     *
     * @see {@link #isNotRequired}
     */
    isRequired(message) {
        required.call(this, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is currently not required.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input[type="text"]').isNotRequired();
     *
     * @see {@link #isRequired}
     */
    isNotRequired(message) {
        notRequired.call(this, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} passes validation
     *
     * Validity is determined by asserting that:
     *
     * - `element.reportValidity() === true`
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('.input').isValid();
     *
     * @see {@link #isValid}
     */
    isValid(message) {
        isValid.call(this, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} does not pass validation
     *
     * Validity is determined by asserting that:
     *
     * - `element.reportValidity() === true`
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('.input').isNotValid();
     *
     * @see {@link #isValid}
     */
    isNotValid(message) {
        isValid.call(this, message, { inverted: true });
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` exists and is visible.
     *
     * Visibility is determined by asserting that:
     *
     * - the element's offsetWidth and offsetHeight are non-zero
     * - any of the element's DOMRect objects have a non-zero size
     *
     * Additionally, visibility in this case means that the element is visible on the page,
     * but not necessarily in the viewport.
     *
     * @param {object?} options
     * @param {number?} options.count
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').isVisible();
     * assert.dom('.choice').isVisible({ count: 4 });
     *
     * @see {@link #isNotVisible}
     */
    isVisible(...options) {
        isVisible.call(this, ...options);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` does not exist or is not visible on the page.
     *
     * Visibility is determined by asserting that:
     *
     * - the element's offsetWidth or offsetHeight are zero
     * - all of the element's DOMRect objects have a size of zero
     *
     * Additionally, visibility in this case means that the element is visible on the page,
     * but not necessarily in the viewport.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('.foo').isNotVisible();
     *
     * @see {@link #isVisible}
     */
    isNotVisible(message) {
        isVisible.call(this, { count: 0 }, message);
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} has an attribute with the provided `name`
     * and optionally checks if the attribute `value` matches the provided text
     * or regular expression.
     *
     * @param {string} name
     * @param {string|RegExp|object?} value
     * @param {string?} message
     *
     * @example
     * assert.dom('input.password-input').hasAttribute('type', 'password');
     *
     * @see {@link #doesNotHaveAttribute}
     */
    hasAttribute(name, value, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        if (arguments.length === 1) {
            value = { any: true };
        }
        let actualValue = element.getAttribute(name);
        if (value instanceof RegExp) {
            let result = typeof actualValue === 'string' && value.test(actualValue);
            let expected = `Element ${this.targetDescription} has attribute "${name}" with value matching ${value}`;
            let actual = actualValue === null
                ? `Element ${this.targetDescription} does not have attribute "${name}"`
                : `Element ${this.targetDescription} has attribute "${name}" with value ${JSON.stringify(actualValue)}`;
            if (!message) {
                message = expected;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else if (value.any === true) {
            let result = actualValue !== null;
            let expected = `Element ${this.targetDescription} has attribute "${name}"`;
            let actual = result
                ? expected
                : `Element ${this.targetDescription} does not have attribute "${name}"`;
            if (!message) {
                message = expected;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else {
            let result = value === actualValue;
            let expected = `Element ${this.targetDescription} has attribute "${name}" with value ${JSON.stringify(value)}`;
            let actual = actualValue === null
                ? `Element ${this.targetDescription} does not have attribute "${name}"`
                : `Element ${this.targetDescription} has attribute "${name}" with value ${JSON.stringify(actualValue)}`;
            if (!message) {
                message = expected;
            }
            this.pushResult({ result, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} has no attribute with the provided `name`.
     *
     * **Aliases:** `hasNoAttribute`, `lacksAttribute`
     *
     * @param {string} name
     * @param {string?} message
     *
     * @example
     * assert.dom('input.username').doesNotHaveAttribute('disabled');
     *
     * @see {@link #hasAttribute}
     */
    doesNotHaveAttribute(name, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let result = !element.hasAttribute(name);
        let expected = `Element ${this.targetDescription} does not have attribute "${name}"`;
        let actual = expected;
        if (!result) {
            let value = element.getAttribute(name);
            actual = `Element ${this.targetDescription} has attribute "${name}" with value ${JSON.stringify(value)}`;
        }
        if (!message) {
            message = expected;
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    hasNoAttribute(name, message) {
        return this.doesNotHaveAttribute(name, message);
    }
    lacksAttribute(name, message) {
        return this.doesNotHaveAttribute(name, message);
    }
    /**
     * Assert that the {@link HTMLElement} has an ARIA attribute with the provided
     * `name` and optionally checks if the attribute `value` matches the provided
     * text or regular expression.
     *
     * @param {string} name
     * @param {string|RegExp|object?} value
     * @param {string?} message
     *
     * @example
     * assert.dom('button').hasAria('pressed', 'true');
     *
     * @see {@link #doesNotHaveAria}
     */
    hasAria(name, ...value) {
        if (value.length === 0) {
            return this.hasAttribute(`aria-${name}`);
        }
        else {
            return this.hasAttribute(`aria-${name}`, ...value);
        }
    }
    /**
     * Assert that the {@link HTMLElement} has no ARIA attribute with the
     * provided `name`.
     *
     * **Aliases:** `hasNoAria`, `lacksAria`
     *
     * @param {string} name
     * @param {string?} message
     *
     * @example
     * assert.dom('button').doesNotHaveAria('pressed');
     *
     * @see {@link #hasAria}
     */
    doesNotHaveAria(name, message) {
        return this.doesNotHaveAttribute(`aria-${name}`, message);
    }
    hasNoAria(name, message) {
        return this.doesNotHaveAria(name, message);
    }
    lacksAria(name, message) {
        return this.doesNotHaveAria(name, message);
    }
    /**
     * Assert that the {@link HTMLElement} has a property with the provided `name`
     * and checks if the property `value` matches the provided text or regular
     * expression.
     *
     * @param {string} name
     * @param {RegExp|any} value
     * @param {string?} message
     *
     * @example
     * assert.dom('input.password-input').hasProperty('type', 'password');
     *
     * @see {@link #doesNotHaveProperty}
     */
    hasProperty(name, value, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let description = this.targetDescription;
        let actualValue = element[name];
        if (value instanceof RegExp) {
            let result = value.test(String(actualValue));
            let expected = `Element ${description} has property "${name}" with value matching ${value}`;
            let actual = `Element ${description} has property "${name}" with value ${JSON.stringify(actualValue)}`;
            if (!message) {
                message = expected;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else {
            let result = value === actualValue;
            let expected = `Element ${description} has property "${name}" with value ${JSON.stringify(value)}`;
            let actual = `Element ${description} has property "${name}" with value ${JSON.stringify(actualValue)}`;
            if (!message) {
                message = expected;
            }
            this.pushResult({ result, actual, expected, message });
        }
        return this;
    }
    /**
     *  Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is disabled.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('.foo').isDisabled();
     *
     * @see {@link #isNotDisabled}
     */
    isDisabled(message) {
        isDisabled.call(this, message);
        return this;
    }
    /**
     *  Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is not disabled.
     *
     * **Aliases:** `isEnabled`
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('.foo').isNotDisabled();
     *
     * @see {@link #isDisabled}
     */
    isNotDisabled(message) {
        isDisabled.call(this, message, { inverted: true });
        return this;
    }
    isEnabled(message) {
        return this.isNotDisabled(message);
    }
    /**
     * Assert that the {@link HTMLElement} has the `expected` CSS class using
     * [`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList).
     *
     * `expected` can also be a regular expression, and the assertion will return
     * true if any of the element's CSS classes match.
     *
     * @param {string|RegExp} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('input[type="password"]').hasClass('secret-password-input');
     *
     * @example
     * assert.dom('input[type="password"]').hasClass(/.*password-input/);
     *
     * @see {@link #doesNotHaveClass}
     */
    hasClass(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let actual = element.classList.toString();
        if (expected instanceof RegExp) {
            let classNames = Array.prototype.slice.call(element.classList);
            let result = classNames.some((className) => {
                return expected.test(className);
            });
            if (!message) {
                message = `Element ${this.targetDescription} has CSS class matching ${expected}`;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else {
            let result = element.classList.contains(expected);
            if (!message) {
                message = `Element ${this.targetDescription} has CSS class "${expected}"`;
            }
            this.pushResult({ result, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} does not have the `expected` CSS class using
     * [`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList).
     *
     * `expected` can also be a regular expression, and the assertion will return
     * true if none of the element's CSS classes match.
     *
     * **Aliases:** `hasNoClass`, `lacksClass`
     *
     * @param {string|RegExp} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('input[type="password"]').doesNotHaveClass('username-input');
     *
     * @example
     * assert.dom('input[type="password"]').doesNotHaveClass(/username-.*-input/);
     *
     * @see {@link #hasClass}
     */
    doesNotHaveClass(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let actual = element.classList.toString();
        if (expected instanceof RegExp) {
            let classNames = Array.prototype.slice.call(element.classList);
            let result = classNames.every((className) => {
                return !expected.test(className);
            });
            if (!message) {
                message = `Element ${this.targetDescription} does not have CSS class matching ${expected}`;
            }
            this.pushResult({ result, actual, expected: `not: ${expected}`, message });
        }
        else {
            let result = !element.classList.contains(expected);
            if (!message) {
                message = `Element ${this.targetDescription} does not have CSS class "${expected}"`;
            }
            this.pushResult({ result, actual, expected: `not: ${expected}`, message });
        }
        return this;
    }
    hasNoClass(expected, message) {
        return this.doesNotHaveClass(expected, message);
    }
    lacksClass(expected, message) {
        return this.doesNotHaveClass(expected, message);
    }
    /**
     * Assert that the [HTMLElement][] has the `expected` style declarations using
     * [`window.getComputedStyle`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle).
     *
     * @param {object} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('.progress-bar').hasStyle({
     *   opacity: 1,
     *   display: 'block'
     * });
     *
     * @see {@link #hasClass}
     */
    hasStyle(expected, message) {
        return this.hasPseudoElementStyle(null, expected, message);
    }
    /**
     * Assert that the pseudo element for `selector` of the [HTMLElement][] has the `expected` style declarations using
     * [`window.getComputedStyle`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle).
     *
     * @param {string} selector
     * @param {object} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('.progress-bar').hasPseudoElementStyle(':after', {
     *   content: '";"',
     * });
     *
     * @see {@link #hasClass}
     */
    hasPseudoElementStyle(selector, expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let computedStyle = window.getComputedStyle(element, selector);
        let expectedProperties = Object.keys(expected);
        if (expectedProperties.length <= 0) {
            throw new TypeError(`Missing style expectations. There must be at least one style property in the passed in expectation object.`);
        }
        let result = expectedProperties.every(property => (computedStyle.getPropertyValue(property.toString()) || computedStyle[property]) ===
            expected[property]);
        let actual = {};
        expectedProperties.forEach(property => (actual[property] =
            computedStyle.getPropertyValue(property.toString()) || computedStyle[property]));
        if (!message) {
            let normalizedSelector = selector ? selector.replace(/^:{0,2}/, '::') : '';
            message = `Element ${this.targetDescription}${normalizedSelector} has style "${JSON.stringify(expected)}"`;
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    /**
     * Assert that the [HTMLElement][] does not have the `expected` style declarations using
     * [`window.getComputedStyle`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle).
     *
     * @param {object} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('.progress-bar').doesNotHaveStyle({
     *   opacity: 1,
     *   display: 'block'
     * });
     *
     * @see {@link #hasClass}
     */
    doesNotHaveStyle(expected, message) {
        return this.doesNotHavePseudoElementStyle(null, expected, message);
    }
    /**
     * Assert that the pseudo element for `selector` of the [HTMLElement][] does not have the `expected` style declarations using
     * [`window.getComputedStyle`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle).
     *
     * @param {string} selector
     * @param {object} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('.progress-bar').doesNotHavePseudoElementStyle(':after', {
     *   content: '";"',
     * });
     *
     * @see {@link #hasClass}
     */
    doesNotHavePseudoElementStyle(selector, expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let computedStyle = window.getComputedStyle(element, selector);
        let expectedProperties = Object.keys(expected);
        if (expectedProperties.length <= 0) {
            throw new TypeError(`Missing style expectations. There must be at least one style property in the passed in expectation object.`);
        }
        let result = expectedProperties.some(property => computedStyle[property] !== expected[property]);
        let actual = {};
        expectedProperties.forEach(property => (actual[property] = computedStyle[property]));
        if (!message) {
            let normalizedSelector = selector ? selector.replace(/^:{0,2}/, '::') : '';
            message = `Element ${this.targetDescription}${normalizedSelector} does not have style "${JSON.stringify(expected)}"`;
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    /**
     * Assert that the text of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` matches the `expected` text, using the
     * [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
     * attribute and stripping/collapsing whitespace.
     *
     * `expected` can also be a regular expression.
     *
     * > Note: This assertion will collapse whitespace if the type you pass in is a string.
     * > If you are testing specifically for whitespace integrity, pass your expected text
     * > in as a RegEx pattern.
     *
     * **Aliases:** `matchesText`
     *
     * @param {string|RegExp} expected
     * @param {string?} message
     *
     * @example
     * // <h2 id="title">
     * //   Welcome to <b>QUnit</b>
     * // </h2>
     *
     * assert.dom('#title').hasText('Welcome to QUnit');
     *
     * @example
     * assert.dom('.foo').hasText(/[12]\d{3}/);
     *
     * @see {@link #includesText}
     */
    hasText(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        if (expected instanceof RegExp) {
            let result = typeof element.textContent === 'string' && expected.test(element.textContent);
            let actual = element.textContent;
            if (!message) {
                message = `Element ${this.targetDescription} has text matching ${expected}`;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else if (expected.any === true) {
            let result = Boolean(element.textContent);
            let expected = `Element ${this.targetDescription} has a text`;
            let actual = result ? expected : `Element ${this.targetDescription} has no text`;
            if (!message) {
                message = expected;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else if (typeof expected === 'string') {
            expected = collapseWhitespace(expected);
            let actual = collapseWhitespace(element.textContent || '');
            let result = actual === expected;
            if (!message) {
                message = `Element ${this.targetDescription} has text "${expected}"`;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else {
            throw new TypeError(`You must pass a string or Regular Expression to "hasText". You passed ${expected}.`);
        }
        return this;
    }
    matchesText(expected, message) {
        return this.hasText(expected, message);
    }
    /**
     * Assert that the `textContent` property of an {@link HTMLElement} is not empty.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('button.share').hasAnyText();
     *
     * @see {@link #hasText}
     */
    hasAnyText(message) {
        return this.hasText({ any: true }, message);
    }
    /**
     * Assert that the `textContent` property of an {@link HTMLElement} is empty.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('div').hasNoText();
     *
     * @see {@link #hasNoText}
     */
    hasNoText(message) {
        return this.hasText('', message);
    }
    /**
     * Assert that the text of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` contains the given `text`, using the
     * [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
     * attribute.
     *
     * > Note: This assertion will collapse whitespace in `textContent` before searching.
     * > If you would like to assert on a string that *should* contain line breaks, tabs,
     * > more than one space in a row, or starting/ending whitespace, use the {@link #hasText}
     * > selector and pass your expected text in as a RegEx pattern.
     *
     * **Aliases:** `containsText`, `hasTextContaining`
     *
     * @param {string} text
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').includesText('Welcome');
     *
     * @see {@link #hasText}
     */
    includesText(text, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let collapsedText = collapseWhitespace(element.textContent || '');
        let result = collapsedText.indexOf(text) !== -1;
        let actual = collapsedText;
        let expected = text;
        if (!message) {
            message = `Element ${this.targetDescription} has text containing "${text}"`;
        }
        if (!result && text !== collapseWhitespace(text)) {
            console.warn('The `.includesText()`, `.containsText()`, and `.hasTextContaining()` assertions collapse whitespace. The text you are checking for contains whitespace that may have made your test fail incorrectly. Try the `.hasText()` assertion passing in your expected text as a RegExp pattern. Your text:\n' +
                text);
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    containsText(expected, message) {
        return this.includesText(expected, message);
    }
    hasTextContaining(expected, message) {
        return this.includesText(expected, message);
    }
    /**
     * Assert that the text of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` does not include the given `text`, using the
     * [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
     * attribute.
     *
     * **Aliases:** `doesNotContainText`, `doesNotHaveTextContaining`
     *
     * @param {string} text
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').doesNotIncludeText('Welcome');
     */
    doesNotIncludeText(text, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let collapsedText = collapseWhitespace(element.textContent || '');
        let result = collapsedText.indexOf(text) === -1;
        let expected = `Element ${this.targetDescription} does not include text "${text}"`;
        let actual = expected;
        if (!result) {
            actual = `Element ${this.targetDescription} includes text "${text}"`;
        }
        if (!message) {
            message = expected;
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    doesNotContainText(unexpected, message) {
        return this.doesNotIncludeText(unexpected, message);
    }
    doesNotHaveTextContaining(unexpected, message) {
        return this.doesNotIncludeText(unexpected, message);
    }
    /**
     * Assert that the `value` property of an {@link HTMLInputElement} matches
     * the `expected` text or regular expression.
     *
     * If no `expected` value is provided, the assertion will fail if the
     * `value` is an empty string.
     *
     * @param {string|RegExp|object?} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('input.username').hasValue('HSimpson');
     *
     * @see {@link #includesValue}
     * @see {@link #hasAnyValue}
     * @see {@link #hasNoValue}
     */
    hasValue(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        if (arguments.length === 0) {
            expected = { any: true };
        }
        let value = element.value;
        if (expected instanceof RegExp) {
            let result = expected.test(value);
            let actual = value;
            if (!message) {
                message = `Element ${this.targetDescription} has value matching ${expected}`;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else if (expected.any === true) {
            let result = Boolean(value);
            let expected = `Element ${this.targetDescription} has a value`;
            let actual = result ? expected : `Element ${this.targetDescription} has no value`;
            if (!message) {
                message = expected;
            }
            this.pushResult({ result, actual, expected, message });
        }
        else {
            let actual = value;
            let result = actual === expected;
            if (!message) {
                message = `Element ${this.targetDescription} has value "${expected}"`;
            }
            this.pushResult({ result, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the `value` property of an {@link HTMLInputElement} includes
     * the `expected` text.
     *
     * **Aliases:** `containsValue`, `hasValueContaining`
     *
     * @param {string} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('textarea.description').includesValue('https://example.com');
     *
     * @see {@link #doesNotIncludeValue}
     */
    includesValue(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let actual = element.value;
        let result = actual.includes(expected);
        if (!message) {
            message = `Element ${this.targetDescription} includes value "${expected}"`;
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    containsValue(expected, message) {
        return this.includesValue(expected, message);
    }
    hasValueContaining(expected, message) {
        return this.includesValue(expected, message);
    }
    /**
     * Assert that the `value` property of an {@link HTMLInputElement} does not include
     * the `expected` text.
     *
     * **Aliases:** `doesNotContainValue`, `doesNotHaveValueContaining`
     *
     * @param {string} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('textarea.description').doesNotIncludeValue('https://example.com');
     *
     * @see {@link #includesValue}
     */
    doesNotIncludeValue(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let actual = element.value;
        let result = !actual.includes(expected);
        if (!message) {
            message = `Element ${this.targetDescription} does not include value "${expected}"`;
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    doesNotContainValue(expected, message) {
        return this.doesNotIncludeValue(expected, message);
    }
    doesNotHaveValueContaining(expected, message) {
        return this.doesNotIncludeValue(expected, message);
    }
    /**
     * Assert that the `value` property of an {@link HTMLInputElement} is not empty.
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input.username').hasAnyValue();
     *
     * @see {@link #hasValue}
     * @see {@link #hasNoValue}
     */
    hasAnyValue(message) {
        return this.hasValue({ any: true }, message);
    }
    /**
     * Assert that the `value` property of an {@link HTMLInputElement} is empty.
     *
     * **Aliases:** `lacksValue`
     *
     * @param {string?} message
     *
     * @example
     * assert.dom('input.username').hasNoValue();
     *
     * @see {@link #hasValue}
     * @see {@link #hasAnyValue}
     */
    hasNoValue(message) {
        return this.hasValue('', message);
    }
    lacksValue(message) {
        return this.hasNoValue(message);
    }
    /**
     * Assert that the target selector selects only Elements that are also selected by
     * compareSelector.
     *
     * @param {string} compareSelector
     * @param {string?} message
     *
     * @example
     * assert.dom('p.red').matchesSelector('div.wrapper p:last-child')
     */
    matchesSelector(compareSelector, message) {
        let targetElements = this.findElements();
        let targets = targetElements.length;
        let matchFailures = matchesSelector(targetElements, compareSelector);
        let singleElement = targets === 1;
        let selectedByPart = this.selectedBy;
        let actual;
        let expected;
        if (matchFailures === 0) {
            // no failures matching.
            if (!message) {
                message = singleElement
                    ? `The element ${selectedByPart} also matches the selector ${compareSelector}.`
                    : `${targets} elements, ${selectedByPart}, also match the selector ${compareSelector}.`;
            }
            actual = expected = message;
            this.pushResult({ result: true, actual, expected, message });
        }
        else {
            let difference = targets - matchFailures;
            // there were failures when matching.
            if (!message) {
                message = singleElement
                    ? `The element ${selectedByPart} did not also match the selector ${compareSelector}.`
                    : `${matchFailures} out of ${targets} elements ${selectedByPart} did not also match the selector ${compareSelector}.`;
            }
            actual = singleElement ? message : `${difference} elements matched ${compareSelector}.`;
            expected = singleElement
                ? `The element should have matched ${compareSelector}.`
                : `${targets} elements should have matched ${compareSelector}.`;
            this.pushResult({ result: false, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the target selector selects only Elements that are not also selected by
     * compareSelector.
     *
     * @param {string} compareSelector
     * @param {string?} message
     *
     * @example
     * assert.dom('input').doesNotMatchSelector('input[disabled]')
     */
    doesNotMatchSelector(compareSelector, message) {
        let targetElements = this.findElements();
        let targets = targetElements.length;
        let matchFailures = matchesSelector(targetElements, compareSelector);
        let singleElement = targets === 1;
        let selectedByPart = this.selectedBy;
        let actual;
        let expected;
        if (matchFailures === targets) {
            // the assertion is successful because no element matched the other selector.
            if (!message) {
                message = singleElement
                    ? `The element ${selectedByPart} did not also match the selector ${compareSelector}.`
                    : `${targets} elements, ${selectedByPart}, did not also match the selector ${compareSelector}.`;
            }
            actual = expected = message;
            this.pushResult({ result: true, actual, expected, message });
        }
        else {
            let difference = targets - matchFailures;
            // the assertion fails because at least one element matched the other selector.
            if (!message) {
                message = singleElement
                    ? `The element ${selectedByPart} must not also match the selector ${compareSelector}.`
                    : `${difference} elements out of ${targets}, ${selectedByPart}, must not also match the selector ${compareSelector}.`;
            }
            actual = singleElement
                ? `The element ${selectedByPart} matched ${compareSelector}.`
                : `${matchFailures} elements did not match ${compareSelector}.`;
            expected = singleElement
                ? message
                : `${targets} elements should not have matched ${compareSelector}.`;
            this.pushResult({ result: false, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is the same {@link Element} as the `expected` element.
     *
     * The elements are compared by reference (`===`), not by their contents, but
     * the failure messages use qunit-dom's element serialization to describe
     * both elements, making it easy to see which element was found instead.
     *
     * @param {Element} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').is(element);
     *
     * @see {@link #isNot}
     */
    is(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        if (!(expected instanceof Element)) {
            throw new TypeError(`You must pass an Element to "is". You passed ${expected}.`);
        }
        let result = element === expected;
        let expectedDescription = elementToString(expected);
        let actualDescription = elementToString(element);
        if (!result && actualDescription === expectedDescription) {
            actualDescription += ' (a different element with the same description)';
        }
        if (!message) {
            message = `Element ${this.targetDescription} is the same element as ${expectedDescription}`;
        }
        this.pushResult({
            result,
            actual: actualDescription,
            expected: expectedDescription,
            message,
        });
        return this;
    }
    /**
     * Assert that the {@link HTMLElement} or an {@link HTMLElement} matching the
     * `selector` is not the same {@link Element} as the `unexpected` element.
     *
     * The elements are compared by reference (`===`), not by their contents.
     *
     * @param {Element} unexpected
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').isNot(element);
     *
     * @see {@link #is}
     */
    isNot(unexpected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        if (!(unexpected instanceof Element)) {
            throw new TypeError(`You must pass an Element to "isNot". You passed ${unexpected}.`);
        }
        let result = element !== unexpected;
        let unexpectedDescription = elementToString(unexpected);
        let actualDescription = result
            ? elementToString(element)
            : `${elementToString(element)} (the same element)`;
        if (!message) {
            message = `Element ${this.targetDescription} is not the same element as ${unexpectedDescription}`;
        }
        this.pushResult({
            result,
            actual: actualDescription,
            expected: `not: ${unexpectedDescription}`,
            message,
        });
        return this;
    }
    /**
     * Assert that the tagName of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` matches the `expected` tagName, using the
     * [`tagName`](https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName)
     * property of the {@link HTMLElement}.
     *
     * @param {string} expected
     * @param {string?} message
     *
     * @example
     * // <h1 id="title">
     * //   Title
     * // </h1>
     *
     * assert.dom('#title').hasTagName('h1');
     */
    hasTagName(tagName, message) {
        let element = this.findTargetElement();
        let actual;
        let expected;
        if (!element)
            return this;
        if (typeof tagName !== 'string') {
            throw new TypeError(`You must pass a string to "hasTagName". You passed ${tagName}.`);
        }
        actual = element.tagName.toLowerCase();
        expected = tagName.toLowerCase();
        if (actual === expected) {
            if (!message) {
                message = `Element ${this.targetDescription} has tagName ${expected}`;
            }
            this.pushResult({ result: true, actual, expected, message });
        }
        else {
            if (!message) {
                message = `Element ${this.targetDescription} does not have tagName ${expected}`;
            }
            this.pushResult({ result: false, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the tagName of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` does not match the `expected` tagName, using the
     * [`tagName`](https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName)
     * property of the {@link HTMLElement}.
     *
     * @param {string} expected
     * @param {string?} message
     *
     * @example
     * // <section id="block">
     * //   Title
     * // </section>
     *
     * assert.dom('section#block').doesNotHaveTagName('div');
     */
    doesNotHaveTagName(tagName, message) {
        let element = this.findTargetElement();
        let actual;
        let expected;
        if (!element)
            return this;
        if (typeof tagName !== 'string') {
            throw new TypeError(`You must pass a string to "doesNotHaveTagName". You passed ${tagName}.`);
        }
        actual = element.tagName.toLowerCase();
        expected = tagName.toLowerCase();
        if (actual !== expected) {
            if (!message) {
                message = `Element ${this.targetDescription} does not have tagName ${expected}`;
            }
            this.pushResult({ result: true, actual, expected, message });
        }
        else {
            if (!message) {
                message = `Element ${this.targetDescription} has tagName ${expected}`;
            }
            this.pushResult({ result: false, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the html of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` matches the `expected` html, using the
     * [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)
     * property of the {@link HTMLElement}.
     *
     * `expected` can also be a regular expression.
     *
     * > Note: This assertion will collapse whitespace if the type you pass in is a string.
     * > If you are testing specifically for whitespace integrity, pass your expected html
     * > in as a RegEx pattern.
     *
     * @param {string|RegExp} expected
     * @param {string?} message
     *
     * @example
     * // <h1>
     * //   A <b>great</b> thing
     * // </h1>
     *
     * assert.dom('h1').hasHtml('A <b>great</b> thing');
     *
     * @example
     * assert.dom('h1').hasHtml(/.*\s<b>great.+/);
     */
    hasHtml(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        if (expected instanceof RegExp) {
            let result = expected.test(element.innerHTML);
            let actual = element.innerHTML;
            if (!message) {
                if (result) {
                    message = `Element ${this.targetDescription} has html matching ${expected}`;
                }
                else {
                    message = `Element ${this.targetDescription} does not have html matching ${expected}`;
                }
            }
            this.pushResult({ result, actual, expected, message });
        }
        else if (typeof expected === 'string') {
            expected = collapseWhitespace(expected);
            let actual = collapseWhitespace(element.innerHTML);
            let result = actual === expected;
            if (!message) {
                if (result) {
                    message = `Element ${this.targetDescription} has html "${expected}"`;
                }
                else {
                    message = `Element ${this.targetDescription} does not have html "${expected}"`;
                }
            }
            this.pushResult({ result, actual, expected, message });
        }
        else {
            throw new TypeError(`You must pass a string or Regular Expression to "hasHtml". You passed ${expected}.`);
        }
        return this;
    }
    /**
     * Assert that the html of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` does not match the `expected` html, using the
     * [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)
     * property of the {@link HTMLElement}.
     *
     * @param {string} expected
     * @param {string?} message
     *
     * @example
     * // <section>
     * //   a <b>great</b> thing
     * // </section>
     *
     * assert.dom('section').doesNotHaveHtml('<b>fantastic</b>');
     */
    doesNotHaveHtml(expected, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        if (typeof expected !== 'string') {
            throw new TypeError(`You must pass a string to "doesNotHaveHtml". You passed ${expected}.`);
        }
        let actual = element.innerHTML;
        if (actual !== expected) {
            if (!message) {
                message = `Element ${this.targetDescription} does not have html "${expected}"`;
            }
            this.pushResult({ result: true, actual, expected, message });
        }
        else {
            if (!message) {
                message = `Element ${this.targetDescription} has html "${expected}"`;
            }
            this.pushResult({ result: false, actual, expected, message });
        }
        return this;
    }
    /**
     * Assert that the html of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` contains the given `html`, using the
     * [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)
     * property.
     *
     * > Note: This assertion will collapse whitespace in `innerHTML` before searching.
     * > If you would like to assert on a string that *should* contain line breaks, tabs,
     * > more than one space in a row, or starting/ending whitespace, use the {@link #hasText}
     * > selector and pass your expected html in as a RegEx pattern.
     *
     * **Aliases:** `containsHtml`, `hasHtmlContaining`
     *
     * @param {string} expected
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').includesHtml('<b>nice</b>');
     *
     * @see {@link #hasHtml}
     */
    includesHtml(html, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let collapsedHtml = collapseWhitespace(element.innerHTML || '');
        let result = collapsedHtml.indexOf(html) !== -1;
        let actual = collapsedHtml;
        let expected = html;
        if (!message) {
            message = `Element ${this.targetDescription} has html containing "${html}"`;
        }
        if (!result && html !== collapseWhitespace(html)) {
            console.warn('The `.includesHtml()`, `.containsHtml()`, and `.hasHtmlContaining()` assertions collapse whitespace. The html you are checking for contains whitespace that may have made your test fail incorrectly. Try the `.hasHtml()` assertion passing in your expected html as a RegExp pattern. Your html:\n' +
                html);
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    containsHtml(expected, message) {
        return this.includesHtml(expected, message);
    }
    hasHtmlContaining(expected, message) {
        return this.includesHtml(expected, message);
    }
    /**
     * Assert that the html of the {@link HTMLElement} or an {@link HTMLElement}
     * matching the `selector` does not include the given `expected` html, using the
     * [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)
     * attribute.
     *
     * **Aliases:** `doesNotContainHtml`, `doesNotHaveHtmlContaining`
     *
     * @param {string} html
     * @param {string?} message
     *
     * @example
     * assert.dom('#title').doesNotIncludeHtml('<i>nope</i>');
     */
    doesNotIncludeHtml(html, message) {
        let element = this.findTargetElement();
        if (!element)
            return this;
        let collapsedHtml = collapseWhitespace(element.innerHTML || '');
        let result = collapsedHtml.indexOf(html) === -1;
        let expected = `Element ${this.targetDescription} does not include html "${html}"`;
        let actual = expected;
        if (!result) {
            actual = `Element ${this.targetDescription} includes html "${html}"`;
        }
        if (!message) {
            message = expected;
        }
        this.pushResult({ result, actual, expected, message });
        return this;
    }
    doesNotContainHtml(unexpected, message) {
        return this.doesNotIncludeHtml(unexpected, message);
    }
    doesNotHaveHtmlContaining(unexpected, message) {
        return this.doesNotIncludeHtml(unexpected, message);
    }
    /**
     * @private
     */
    pushResult(result) {
        this.testContext.pushResult(result);
    }
    /**
     * Finds a valid HTMLElement from target, or pushes a failing assertion if a valid
     * element is not found.
     * @private
     * @returns (HTMLElement|null) a valid HTMLElement, or null
     */
    findTargetElement() {
        let el = resolveDOMElement(this.descriptor);
        if (el === null) {
            let message = `Element ${this.targetDescription} should exist`;
            this.pushResult({ message, result: false, actual: undefined, expected: undefined });
            return null;
        }
        return el;
    }
    /**
     * Finds a collection of Element instances from target using querySelectorAll
     * @private
     * @returns (Element[]) an array of Element instances
     * @throws TypeError will be thrown if target is an unrecognized type
     */
    findElements() {
        return Array.from(resolveDOMElements(this.descriptor));
    }
    /**
     * @private
     */
    get targetDescription() {
        return resolveDescription(this.descriptor) ?? 'undefined';
    }
    /**
     * @private
     */
    get selectedBy() {
        if (this.wasPassedElement) {
            return 'passed';
        }
        else if (resolveDOMElement(this.descriptor)) {
            return `selected by ${this.targetDescription}`;
        }
        else {
            return 'selected by null';
        }
    }
}

let _getRootElement = () => null;
function overrideRootElement(fn) {
    _getRootElement = fn;
}
function getRootElement() {
    return _getRootElement();
}

function install (assert) {
    assert.dom = function (target, rootElement) {
        if (!isValidRootElement(rootElement)) {
            throw new Error(`${rootElement} is not a valid root element`);
        }
        rootElement = rootElement || this.dom.rootElement || getRootElement();
        // Only default to rootElement when NO arguments provided.
        // This allows assert.dom(undefined) to throw while assert.dom() still works.
        if (arguments.length === 0) {
            target = rootElement instanceof Element ? rootElement : null;
        }
        return new DOMAssertions(target, rootElement, this);
    };
    function isValidRootElement(element) {
        return (!element ||
            (typeof element === 'object' &&
                typeof element.querySelector === 'function' &&
                typeof element.querySelectorAll === 'function'));
    }
}

function setup(assert, options = {}) {
    install(assert);
    const getRootElement = typeof options.getRootElement === 'function'
        ? options.getRootElement
        : () => document.querySelector('#ember-testing');
    overrideRootElement(getRootElement);
}

export { install, setup };
//# sourceMappingURL=index.js.map