diff --git a/packages/qunit-dom/lib/__tests__/does-not-exist.ts b/packages/qunit-dom/lib/__tests__/does-not-exist.ts index 59fdedb97..378200814 100644 --- a/packages/qunit-dom/lib/__tests__/does-not-exist.ts +++ b/packages/qunit-dom/lib/__tests__/does-not-exist.ts @@ -54,6 +54,21 @@ describe('assert.dom(...).doesNotExist()', () => { }, ]); }); + + test('fails if passed null', () => { + document.body.innerHTML = '

foo

bar'; + + assert.dom(null).doesNotExist(); + + expect(assert.results).toEqual([ + { + actual: 'Element does not exist', + expected: 'Element does not exist', + message: 'Element does not exist', + result: true, + }, + ]); + }); }); test('custom message', () => { diff --git a/packages/qunit-dom/lib/__tests__/does-not-have-attribute.ts b/packages/qunit-dom/lib/__tests__/does-not-have-attribute.ts index 40a1beff5..55ac346c2 100644 --- a/packages/qunit-dom/lib/__tests__/does-not-have-attribute.ts +++ b/packages/qunit-dom/lib/__tests__/does-not-have-attribute.ts @@ -83,6 +83,17 @@ describe('assert.dom(...).doesNotHaveAttribute()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).doesNotHaveAttribute('disabled'); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).doesNotHaveAttribute('disabled')).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/does-not-have-class.ts b/packages/qunit-dom/lib/__tests__/does-not-have-class.ts index acaa0f6fb..028837cea 100644 --- a/packages/qunit-dom/lib/__tests__/does-not-have-class.ts +++ b/packages/qunit-dom/lib/__tests__/does-not-have-class.ts @@ -117,6 +117,17 @@ describe('assert.dom(...).doesNotHaveClass()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).doesNotHaveClass('foo'); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).doesNotHaveClass('foo')).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/does-not-have-style.ts b/packages/qunit-dom/lib/__tests__/does-not-have-style.ts index 755dda489..fab9f06b3 100644 --- a/packages/qunit-dom/lib/__tests__/does-not-have-style.ts +++ b/packages/qunit-dom/lib/__tests__/does-not-have-style.ts @@ -90,6 +90,18 @@ describe('assert.dom(...).doesNotHaveStyle()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).doesNotHaveStyle({ + opacity: 0, + }); + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).doesNotHaveStyle({ opacity: 1 })).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/does-not-match-selector.ts b/packages/qunit-dom/lib/__tests__/does-not-match-selector.ts index d07ae80be..2b753604b 100644 --- a/packages/qunit-dom/lib/__tests__/does-not-match-selector.ts +++ b/packages/qunit-dom/lib/__tests__/does-not-match-selector.ts @@ -82,6 +82,21 @@ describe('assert.dom(...).doesNotMatchSelector()', () => { ]); }); + test('null passed', () => { + assert.dom(null).doesNotMatchSelector('div>p:last-child'); + + expect(assert.results).toEqual([ + { + actual: '0 elements, selected by null, did not also match the selector div>p:last-child.', + expected: + '0 elements, selected by null, did not also match the selector div>p:last-child.', + message: + '0 elements, selected by null, did not also match the selector div>p:last-child.', + result: true, + }, + ]); + }); + test('multiple elements, some matching compareSelector', () => { assert.dom('p').doesNotMatchSelector('div>p'); diff --git a/packages/qunit-dom/lib/__tests__/has-any-text.ts b/packages/qunit-dom/lib/__tests__/has-any-text.ts index c491db025..602e802fe 100644 --- a/packages/qunit-dom/lib/__tests__/has-any-text.ts +++ b/packages/qunit-dom/lib/__tests__/has-any-text.ts @@ -64,6 +64,17 @@ describe('assert.dom(...).hasAnyText()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasAnyText(); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasAnyText()).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-any-value.ts b/packages/qunit-dom/lib/__tests__/has-any-value.ts index fa130c4ba..d9d84653c 100644 --- a/packages/qunit-dom/lib/__tests__/has-any-value.ts +++ b/packages/qunit-dom/lib/__tests__/has-any-value.ts @@ -65,6 +65,17 @@ describe('assert.dom(...).hasAnyValue()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasAnyValue(); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasAnyValue()).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-attribute.ts b/packages/qunit-dom/lib/__tests__/has-attribute.ts index ca3cfd942..1b2eb9cd1 100644 --- a/packages/qunit-dom/lib/__tests__/has-attribute.ts +++ b/packages/qunit-dom/lib/__tests__/has-attribute.ts @@ -238,6 +238,17 @@ describe('assert.dom(...).hasAttribute()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasAttribute('foo'); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasAttribute('foo')).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-class.ts b/packages/qunit-dom/lib/__tests__/has-class.ts index ac9ab744c..95db00759 100644 --- a/packages/qunit-dom/lib/__tests__/has-class.ts +++ b/packages/qunit-dom/lib/__tests__/has-class.ts @@ -112,6 +112,17 @@ describe('assert.dom(...).hasClass()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasClass('foo'); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasClass('foo')).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-no-text.ts b/packages/qunit-dom/lib/__tests__/has-no-text.ts index 48e97383c..fe6f4e3ce 100644 --- a/packages/qunit-dom/lib/__tests__/has-no-text.ts +++ b/packages/qunit-dom/lib/__tests__/has-no-text.ts @@ -62,6 +62,17 @@ describe('assert.dom(...).hasNoText()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasNoText(); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasNoText()).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-no-value.ts b/packages/qunit-dom/lib/__tests__/has-no-value.ts index 63de2fc45..cb3653e57 100644 --- a/packages/qunit-dom/lib/__tests__/has-no-value.ts +++ b/packages/qunit-dom/lib/__tests__/has-no-value.ts @@ -64,6 +64,17 @@ describe('assert.dom(...).hasNoValue()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasNoValue(); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasNoValue()).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-property.ts b/packages/qunit-dom/lib/__tests__/has-property.ts index bce3a2d6b..101ea21c0 100644 --- a/packages/qunit-dom/lib/__tests__/has-property.ts +++ b/packages/qunit-dom/lib/__tests__/has-property.ts @@ -150,6 +150,17 @@ describe('assert.dom(...).hasProperty()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasProperty('foo', 'bar'); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasProperty('foo', 'bar')).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-style.ts b/packages/qunit-dom/lib/__tests__/has-style.ts index d6f3af590..d6676c68c 100644 --- a/packages/qunit-dom/lib/__tests__/has-style.ts +++ b/packages/qunit-dom/lib/__tests__/has-style.ts @@ -108,6 +108,18 @@ describe('assert.dom(...).hasStyle()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasStyle({ + opacity: 0, + }); + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasStyle({ opacity: 1 })).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/has-value.ts b/packages/qunit-dom/lib/__tests__/has-value.ts index 538405fda..2201d8448 100644 --- a/packages/qunit-dom/lib/__tests__/has-value.ts +++ b/packages/qunit-dom/lib/__tests__/has-value.ts @@ -221,6 +221,17 @@ describe('assert.dom(...).hasValue()', () => { ]); }); + test('fails for null', () => { + assert.dom(null).hasValue('foo'); + + expect(assert.results).toEqual([ + { + message: 'Element should exist', + result: false, + }, + ]); + }); + test('throws for unexpected parameter types', () => { //@ts-ignore -- These assertions are for JavaScript users who don't have type checking expect(() => assert.dom(5).hasValue('foo')).toThrow('Unexpected Parameter: 5'); diff --git a/packages/qunit-dom/lib/__tests__/is-not-visible.ts b/packages/qunit-dom/lib/__tests__/is-not-visible.ts index c9e6531cf..4276c2dc8 100644 --- a/packages/qunit-dom/lib/__tests__/is-not-visible.ts +++ b/packages/qunit-dom/lib/__tests__/is-not-visible.ts @@ -34,6 +34,23 @@ describe('assert.dom(...).isNotVisible()', () => { }); }); + describe('element only', () => { + test('succeeds if element is missing', () => { + document.body.innerHTML = '

foo

bar'; + + assert.dom(null).isNotVisible(); + + expect(assert.results).toEqual([ + { + actual: 'Element is not visible', + expected: 'Element is not visible', + message: 'Element is not visible', + result: true, + }, + ]); + }); + }); + describe('custom messages', () => { test('shows custom messages', () => { document.body.innerHTML = '

foo

bar'; diff --git a/packages/qunit-dom/lib/__tests__/matches-selector.ts b/packages/qunit-dom/lib/__tests__/matches-selector.ts index 3be42b8ee..6075615dd 100644 --- a/packages/qunit-dom/lib/__tests__/matches-selector.ts +++ b/packages/qunit-dom/lib/__tests__/matches-selector.ts @@ -82,6 +82,19 @@ describe('assert.dom(...).matchesSelector()', () => { ]); }); + test('null passed', () => { + assert.dom(null).matchesSelector('div>p:last-child'); + + expect(assert.results).toEqual([ + { + actual: '0 elements, selected by null, also match the selector div>p:last-child.', + expected: '0 elements, selected by null, also match the selector div>p:last-child.', + message: '0 elements, selected by null, also match the selector div>p:last-child.', + result: true, + }, + ]); + }); + test('multiple elements not all matching compareSelector', () => { assert.dom('p').matchesSelector('p + p'); diff --git a/packages/qunit-dom/lib/assertions.ts b/packages/qunit-dom/lib/assertions.ts index 68e646b93..c117fe0d7 100644 --- a/packages/qunit-dom/lib/assertions.ts +++ b/packages/qunit-dom/lib/assertions.ts @@ -74,8 +74,8 @@ export default class DOMAssertions { * * @see {@link #doesNotExist} */ - exists(options: ExistsOptions | string, message?: string): DOMAssertions { - exists.call(this, options, message); + exists(...args: [options: ExistsOptions, message?: string] | [message?: string]): DOMAssertions { + exists.call(this, ...args); return this; } @@ -298,8 +298,10 @@ export default class DOMAssertions { * * @see {@link #isNotVisible} */ - isVisible(options: ExistsOptions | string, message?: string): DOMAssertions { - isVisible.call(this, options, message); + isVisible( + ...args: [options: ExistsOptions, message?: string] | [message?: string] + ): DOMAssertions { + isVisible.call(this, ...args); return this; } @@ -388,7 +390,7 @@ export default class DOMAssertions { let actualValue = element.getAttribute(name); if (value instanceof RegExp) { - let result = value.test(actualValue); + let result = typeof actualValue === 'string' && value.test(actualValue); let expected = `Element ${this.targetDescription} has attribute "${name}" with value matching ${value}`; let actual = actualValue === null @@ -451,7 +453,7 @@ export default class DOMAssertions { */ doesNotHaveAttribute(name: string, message?: string): DOMAssertions { let element = this.findTargetElement(); - if (!element) return; + if (!element) return this; let result = !element.hasAttribute(name); let expected = `Element ${this.targetDescription} does not have attribute "${name}"`; @@ -480,6 +482,35 @@ export default class DOMAssertions { return this.doesNotHaveAttribute(name, message); } + /** + * Assert that the {@link HTMLElement} has an ARIA attribute with the provided + * `name`. + * + * @param {string} name + * + * @example + * assert.dom('button').hasAria('pressed'); + * + * @see {@link #doesNotHaveAria} + */ + hasAria(name: string): DOMAssertions; + + /** + * Assert that the {@link HTMLElement} has an ARIA attribute with the provided + * `name` and 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 #doesNotHaveAttribute} + */ + hasAria(name: string, value: string | RegExp | { any: true }, message?: string): DOMAssertions; + /** * Assert that the {@link HTMLElement} has an ARIA attribute with the provided * `name` and optionally checks if the attribute `value` matches the provided @@ -494,8 +525,15 @@ export default class DOMAssertions { * * @see {@link #doesNotHaveAria} */ - hasAria(name: string, value?: string | RegExp | { any: true }, message?: string): DOMAssertions { - return this.hasAttribute(`aria-${name}`, value, message); + hasAria( + name: string, + ...args: [value: string | RegExp | { any: true }, message?: string] | [] + ): DOMAssertions { + if (args.length === 0) { + return this.hasAttribute(`aria-${name}`); + } else { + return this.hasAttribute(`aria-${name}`, ...args); + } } /** @@ -740,7 +778,7 @@ export default class DOMAssertions { return this.hasPseudoElementStyle(null, expected, message); } - hasPseudoElementStyle(selector: string, expected: object, message?: string): DOMAssertions; + hasPseudoElementStyle(selector: string | null, expected: object, message?: string): DOMAssertions; /** * Assert that the pseudo element for `selector` of the [HTMLElement][] has the `expected` style declarations using @@ -816,7 +854,11 @@ export default class DOMAssertions { return this.doesNotHavePseudoElementStyle(null, expected, message); } - doesNotHavePseudoElementStyle(selector: string, expected: object, message: string): DOMAssertions; + doesNotHavePseudoElementStyle( + selector: string | null, + expected: object, + message?: string + ): DOMAssertions; /** * Assert that the pseudo element for `selector` of the [HTMLElement][] does not have the `expected` style declarations using @@ -836,7 +878,7 @@ export default class DOMAssertions { doesNotHavePseudoElementStyle( selector: string | null, expected: Record, - message: string + message?: string ): DOMAssertions { let element = this.findTargetElement(); if (!element) return this; @@ -902,7 +944,7 @@ export default class DOMAssertions { if (!element) return this; if (expected instanceof RegExp) { - let result = expected.test(element.textContent); + let result = typeof element.textContent === 'string' && expected.test(element.textContent); let actual = element.textContent; if (!message) { @@ -923,7 +965,7 @@ export default class DOMAssertions { this.pushResult({ result, actual, expected, message }); } else if (typeof expected === 'string') { expected = collapseWhitespace(expected); - let actual = collapseWhitespace(element.textContent); + let actual = collapseWhitespace(element.textContent || ''); let result = actual === expected; if (!message) { @@ -997,7 +1039,7 @@ export default class DOMAssertions { let element = this.findTargetElement(); if (!element) return this; - let collapsedText = collapseWhitespace(element.textContent); + let collapsedText = collapseWhitespace(element.textContent || ''); let result = collapsedText.indexOf(text) !== -1; let actual = collapsedText; let expected = text; @@ -1043,7 +1085,7 @@ export default class DOMAssertions { let element = this.findTargetElement(); if (!element) return this; - let collapsedText = collapseWhitespace(element.textContent); + let collapsedText = collapseWhitespace(element.textContent || ''); let result = collapsedText.indexOf(text) === -1; let expected = `Element ${this.targetDescription} does not include text "${text}"`; let actual = expected; @@ -1386,6 +1428,9 @@ export default class DOMAssertions { if (this.target === null) { return null; } else if (typeof this.target === 'string') { + if (!this.rootElement) { + throw new Error('Cannot do selector-based queries without a root element'); + } return this.rootElement.querySelector(this.target); } else if (this.target instanceof Element) { return this.target; @@ -1404,6 +1449,9 @@ export default class DOMAssertions { if (this.target === null) { return []; } else if (typeof this.target === 'string') { + if (!this.rootElement) { + throw new Error('Cannot do selector-based queries without a root element'); + } return toArray(this.rootElement.querySelectorAll(this.target)); } else if (this.target instanceof Element) { return [this.target]; diff --git a/packages/qunit-dom/lib/install.ts b/packages/qunit-dom/lib/install.ts index af8b877ba..5c9034be1 100644 --- a/packages/qunit-dom/lib/install.ts +++ b/packages/qunit-dom/lib/install.ts @@ -20,11 +20,11 @@ export default function (assert: Assert) { rootElement = rootElement || this.dom.rootElement || getRootElement(); - if (arguments.length === 0) { - target = rootElement instanceof Element ? rootElement : null; - } - - return new DOMAssertions(target, rootElement, this); + return new DOMAssertions( + target !== undefined ? target : rootElement instanceof Element ? rootElement : null, + rootElement, + this + ); }; function isValidRootElement(element: any): element is Element { diff --git a/packages/qunit-dom/lib/root-element.ts b/packages/qunit-dom/lib/root-element.ts index c92eb5fcc..4c78318bd 100644 --- a/packages/qunit-dom/lib/root-element.ts +++ b/packages/qunit-dom/lib/root-element.ts @@ -1,6 +1,6 @@ let _getRootElement: () => Element | null = () => null; -export function overrideRootElement(fn: () => Element) { +export function overrideRootElement(fn: () => Element | null) { _getRootElement = fn; } diff --git a/packages/qunit-dom/tsconfig.json b/packages/qunit-dom/tsconfig.json index c5a2810cc..d2396043f 100644 --- a/packages/qunit-dom/tsconfig.json +++ b/packages/qunit-dom/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "noImplicitAny": true, + "strictNullChecks": true, "declaration": true, // Forces import type when imports are only used as types. "verbatimModuleSyntax": true,