From 1b63d1a9cebf974dffc08ddae240312a4ac63b0a Mon Sep 17 00:00:00 2001 From: qijixin2 Date: Wed, 9 Feb 2022 22:07:32 +0800 Subject: [PATCH] feat: Add check for container type on queries (#604) This is intended to give an informative warning when a base-query is called with a container that's neitheir an Element, Document or DocumentFragment. Closes: testing-library/dom-testing-library#537 --- .../base-queries-warn-on-invalid-container.js | 127 ++++++++++++++++++ src/__tests__/helpers.js | 27 +++- src/helpers.js | 22 +++ src/queries/alt-text.js | 4 +- src/queries/display-value.js | 4 +- src/queries/label-text.js | 3 + src/queries/placeholder-text.js | 4 +- src/queries/role.js | 4 +- src/queries/test-id.js | 4 +- src/queries/text.js | 2 + src/queries/title.js | 4 +- 11 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 src/__tests__/base-queries-warn-on-invalid-container.js diff --git a/src/__tests__/base-queries-warn-on-invalid-container.js b/src/__tests__/base-queries-warn-on-invalid-container.js new file mode 100644 index 0000000..fe2dc7d --- /dev/null +++ b/src/__tests__/base-queries-warn-on-invalid-container.js @@ -0,0 +1,127 @@ +import { + getByLabelText, + getAllByLabelText, + queryAllByLabelText, + queryByLabelText, + findByLabelText, + findAllByLabelText, + getByPlaceholderText, + getAllByPlaceholderText, + queryAllByPlaceholderText, + queryByPlaceholderText, + findByPlaceholderText, + findAllByPlaceholderText, + getByText, + getAllByText, + queryAllByText, + queryByText, + findByText, + findAllByText, + getByAltText, + getAllByAltText, + queryAllByAltText, + queryByAltText, + findByAltText, + findAllByAltText, + getByTitle, + getAllByTitle, + queryAllByTitle, + queryByTitle, + findByTitle, + findAllByTitle, + getByDisplayValue, + getAllByDisplayValue, + queryAllByDisplayValue, + queryByDisplayValue, + findByDisplayValue, + findAllByDisplayValue, + getByRole, + getAllByRole, + queryAllByRole, + queryByRole, + findByRole, + findAllByRole, + getByTestId, + getAllByTestId, + queryAllByTestId, + queryByTestId, + findByTestId, + findAllByTestId, +} from '..' + +describe('synchronous queries throw on invalid container type', () => { + test.each([ + ['getByLabelText', getByLabelText], + ['getAllByLabelText', getAllByLabelText], + ['queryByLabelText', queryByLabelText], + ['queryAllByLabelText', queryAllByLabelText], + ['getByPlaceholderText', getByPlaceholderText], + ['getAllByPlaceholderText', getAllByPlaceholderText], + ['queryByPlaceholderText', queryByPlaceholderText], + ['queryAllByPlaceholderText', queryAllByPlaceholderText], + ['getByText', getByText], + ['getAllByText', getAllByText], + ['queryByText', queryByText], + ['queryAllByText', queryAllByText], + ['getByAltText', getByAltText], + ['getAllByAltText', getAllByAltText], + ['queryByAltText', queryByAltText], + ['queryAllByAltText', queryAllByAltText], + ['getByTitle', getByTitle], + ['getAllByTitle', getAllByTitle], + ['queryByTitle', queryByTitle], + ['queryAllByTitle', queryAllByTitle], + ['getByDisplayValue', getByDisplayValue], + ['getAllByDisplayValue', getAllByDisplayValue], + ['queryByDisplayValue', queryByDisplayValue], + ['queryAllByDisplayValue', queryAllByDisplayValue], + ['getByRole', getByRole], + ['getAllByRole', getAllByRole], + ['queryByRole', queryByRole], + ['queryAllByRole', queryAllByRole], + ['getByTestId', getByTestId], + ['getAllByTestId', getAllByTestId], + ['queryByTestId', queryByTestId], + ['queryAllByTestId', queryAllByTestId], + ])('%s', (_queryName, query) => { + expect(() => + query('invalid type for container', 'irrelevant text'), + ).toThrowErrorMatchingInlineSnapshot( + `"Expected container to be an Element, a Document or a DocumentFragment but got string."`, + ) + }) +}) + +describe('asynchronous queries throw on invalid container type', () => { + test.each([ + ['findByLabelText', findByLabelText], + ['findAllByLabelText', findAllByLabelText], + ['findByPlaceholderText', findByPlaceholderText], + ['findAllByPlaceholderText', findAllByPlaceholderText], + ['findByText', findByText], + ['findAllByText', findAllByText], + ['findByAltText', findByAltText], + ['findAllByAltText', findAllByAltText], + ['findByTitle', findByTitle], + ['findAllByTitle', findAllByTitle], + ['findByDisplayValue', findByDisplayValue], + ['findAllByDisplayValue', findAllByDisplayValue], + ['findByRole', findByRole], + ['findAllByRole', findAllByRole], + ['findByTestId', findByTestId], + ['findAllByTestId', findAllByTestId], + ])('%s', (_queryName, query) => { + const queryOptions = {} + const waitOptions = {timeout: 1} + return expect( + query( + 'invalid type for container', + 'irrelevant text', + queryOptions, + waitOptions, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Expected container to be an Element, a Document or a DocumentFragment but got string."`, + ) + }) +}) diff --git a/src/__tests__/helpers.js b/src/__tests__/helpers.js index 109fe18..717fd77 100644 --- a/src/__tests__/helpers.js +++ b/src/__tests__/helpers.js @@ -1,5 +1,30 @@ -import {getDocument} from '../helpers' +import {getDocument, checkContainerType} from '../helpers' test('returns global document if exists', () => { expect(getDocument()).toBe(document) }) + +describe('query container validation throws when validation fails', () => { + test('undefined as container', () => { + expect(() => + checkContainerType(undefined), + ).toThrowErrorMatchingInlineSnapshot( + `"Expected container to be an Element, a Document or a DocumentFragment but got undefined."`, + ) + }) + test('null as container', () => { + expect(() => checkContainerType(null)).toThrowErrorMatchingInlineSnapshot( + `"Expected container to be an Element, a Document or a DocumentFragment but got null."`, + ) + }) + test('array as container', () => { + expect(() => checkContainerType([])).toThrowErrorMatchingInlineSnapshot( + `"Expected container to be an Element, a Document or a DocumentFragment but got Array."`, + ) + }) + test('object as container', () => { + expect(() => checkContainerType({})).toThrowErrorMatchingInlineSnapshot( + `"Expected container to be an Element, a Document or a DocumentFragment but got Object."`, + ) + }) +}) diff --git a/src/helpers.js b/src/helpers.js index cc2321b..6403156 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -65,6 +65,27 @@ function getWindowFromNode(node) { } } +function checkContainerType(container) { + if ( + !container || + !(typeof container.querySelector === 'function') || + !(typeof container.querySelectorAll === 'function') + ) { + throw new TypeError( + `Expected container to be an Element, a Document or a DocumentFragment but got ${getTypeName( + container, + )}.`, + ) + } + + function getTypeName(object) { + if (typeof object === 'object') { + return object === null ? 'null' : object.constructor.name + } + return typeof object + } +} + export { getWindowFromNode, getDocument, @@ -72,4 +93,5 @@ export { setImmediateFn as setImmediate, setTimeoutFn as setTimeout, runWithRealTimers, + checkContainerType, } diff --git a/src/queries/alt-text.js b/src/queries/alt-text.js index aebda55..a31c516 100644 --- a/src/queries/alt-text.js +++ b/src/queries/alt-text.js @@ -1,11 +1,13 @@ -import {matches, fuzzyMatches, makeNormalizer, buildQueries} from './all-utils' import {wrapAllByQueryWithSuggestion} from '../query-helpers' +import {checkContainerType} from '../helpers' +import {matches, fuzzyMatches, makeNormalizer, buildQueries} from './all-utils' function queryAllByAltText( container, alt, {exact = true, collapseWhitespace, trim, normalizer} = {}, ) { + checkContainerType(container) const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) return Array.from(container.querySelectorAll('img,input,area')).filter(node => diff --git a/src/queries/display-value.js b/src/queries/display-value.js index a90966d..75ab083 100644 --- a/src/queries/display-value.js +++ b/src/queries/display-value.js @@ -1,3 +1,5 @@ +import {wrapAllByQueryWithSuggestion} from '../query-helpers' +import {checkContainerType} from '../helpers' import { getNodeText, matches, @@ -5,13 +7,13 @@ import { makeNormalizer, buildQueries, } from './all-utils' -import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByDisplayValue( container, value, {exact = true, collapseWhitespace, trim, normalizer} = {}, ) { + checkContainerType(container) const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) return Array.from(container.querySelectorAll(`input,textarea,select`)).filter( diff --git a/src/queries/label-text.js b/src/queries/label-text.js index 9e90002..e7eda11 100644 --- a/src/queries/label-text.js +++ b/src/queries/label-text.js @@ -1,4 +1,5 @@ import {getConfig} from '../config' +import {checkContainerType} from '../helpers' import { fuzzyMatches, matches, @@ -42,6 +43,8 @@ function queryAllByLabelText( text, {selector = '*', exact = true, collapseWhitespace, trim, normalizer} = {}, ) { + checkContainerType(container) + const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) const labels = queryAllLabelsByText(container, text, { exact, diff --git a/src/queries/placeholder-text.js b/src/queries/placeholder-text.js index 7f2bc62..bdea594 100644 --- a/src/queries/placeholder-text.js +++ b/src/queries/placeholder-text.js @@ -1,7 +1,9 @@ -import {queryAllByAttribute, buildQueries} from './all-utils' import {wrapAllByQueryWithSuggestion} from '../query-helpers' +import {checkContainerType} from '../helpers' +import {queryAllByAttribute, buildQueries} from './all-utils' function queryAllByPlaceholderText(...args) { + checkContainerType(...args) return queryAllByAttribute('placeholder', ...args) } const getMultipleError = (c, text) => diff --git a/src/queries/role.js b/src/queries/role.js index 54aec53..07fe077 100644 --- a/src/queries/role.js +++ b/src/queries/role.js @@ -7,6 +7,8 @@ import { isInaccessible, isSubtreeInaccessible, } from '../role-helpers' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' +import {checkContainerType} from '../helpers' import { buildQueries, fuzzyMatches, @@ -14,7 +16,6 @@ import { makeNormalizer, matches, } from './all-utils' -import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByRole( container, @@ -30,6 +31,7 @@ function queryAllByRole( selected, } = {}, ) { + checkContainerType(container) const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) diff --git a/src/queries/test-id.js b/src/queries/test-id.js index 5f5047d..f2ef5a9 100644 --- a/src/queries/test-id.js +++ b/src/queries/test-id.js @@ -1,9 +1,11 @@ -import {queryAllByAttribute, getConfig, buildQueries} from './all-utils' +import {checkContainerType} from '../helpers' import {wrapAllByQueryWithSuggestion} from '../query-helpers' +import {queryAllByAttribute, getConfig, buildQueries} from './all-utils' const getTestIdAttribute = () => getConfig().testIdAttribute function queryAllByTestId(...args) { + checkContainerType(...args) return queryAllByAttribute(getTestIdAttribute(), ...args) } diff --git a/src/queries/text.js b/src/queries/text.js index 4d31b6c..903bba2 100644 --- a/src/queries/text.js +++ b/src/queries/text.js @@ -1,4 +1,5 @@ import {wrapAllByQueryWithSuggestion} from '../query-helpers' +import {checkContainerType} from '../helpers' import {DEFAULT_IGNORE_TAGS} from '../config' import { fuzzyMatches, @@ -20,6 +21,7 @@ function queryAllByText( normalizer, } = {}, ) { + checkContainerType(container) const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) let baseArray = [] diff --git a/src/queries/title.js b/src/queries/title.js index 93c2cf5..ee30446 100644 --- a/src/queries/title.js +++ b/src/queries/title.js @@ -1,3 +1,5 @@ +import {wrapAllByQueryWithSuggestion} from '../query-helpers' +import {checkContainerType} from '../helpers' import { fuzzyMatches, matches, @@ -5,13 +7,13 @@ import { getNodeText, buildQueries, } from './all-utils' -import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByTitle( container, text, {exact = true, collapseWhitespace, trim, normalizer} = {}, ) { + checkContainerType(container) const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) return Array.from(container.querySelectorAll('[title], svg > title')).filter(