UNPKG

qunit-dom

Version:

High Level DOM Assertions for QUnit

1,889 lines 69.8 kB
var QUnitDOM = (function (exports, domElementDescriptors) {
  'use strict';

  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 domElementDescriptors.createDescriptor(new SelectorData(target, rootElement));
      }
      else if (target instanceof Element) {
          // element
          return domElementDescriptors.createDescriptor(new ElementData(target));
      }
      else if (target === null) {
          // null, which we treat as an unmatched element, e.g.
          // `createDescriptor(document.querySelector('.does-not-exist'))`
          return domElementDescriptors.createDescriptor({ element: null, description: '<unknown>' });
      }
      else if (domElementDescriptors.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 = domElementDescriptors.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(domElementDescriptors.resolveDOMElements(this.descriptor));
      }
      /**
       * @private
       */
      get targetDescription() {
          return domElementDescriptors.resolveDescription(this.descriptor) ?? 'undefined';
      }
      /**
       * @private
       */
      get selectedBy() {
          if (this.wasPassedElement) {
              return 'passed';
          }
          else if (domElementDescriptors.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);
  }

  /* global QUnit */
  install(QUnit.assert);

  exports.setup = setup;

  Object.defineProperty(exports, '__esModule', { value: true });

  return exports;

})({}, DOMElementDescriptors);
//# sourceMappingURL=qunit-dom.js.map