Skip to content

Commit

Permalink
feat: Add check for container type on queries (#604)
Browse files Browse the repository at this point in the history
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: #537
  • Loading branch information
Jnforja committed Jun 5, 2020
1 parent af7392f commit 16e41ac
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 7 deletions.
127 changes: 127 additions & 0 deletions 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."`,
)
})
})
27 changes: 26 additions & 1 deletion 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."`,
)
})
})
22 changes: 22 additions & 0 deletions src/helpers.js
Expand Up @@ -65,11 +65,33 @@ 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,
clearTimeoutFn as clearTimeout,
setImmediateFn as setImmediate,
setTimeoutFn as setTimeout,
runWithRealTimers,
checkContainerType,
}
4 changes: 3 additions & 1 deletion 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 =>
Expand Down
4 changes: 3 additions & 1 deletion src/queries/display-value.js
@@ -1,17 +1,19 @@
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {
getNodeText,
matches,
fuzzyMatches,
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(
Expand Down
3 changes: 3 additions & 0 deletions src/queries/label-text.js
@@ -1,4 +1,5 @@
import {getConfig} from '../config'
import {checkContainerType} from '../helpers'
import {
fuzzyMatches,
matches,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion 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) =>
Expand Down
4 changes: 3 additions & 1 deletion src/queries/role.js
Expand Up @@ -7,14 +7,15 @@ import {
isInaccessible,
isSubtreeInaccessible,
} from '../role-helpers'
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {
buildQueries,
fuzzyMatches,
getConfig,
makeNormalizer,
matches,
} from './all-utils'
import {wrapAllByQueryWithSuggestion} from '../query-helpers'

function queryAllByRole(
container,
Expand All @@ -30,6 +31,7 @@ function queryAllByRole(
selected,
} = {},
) {
checkContainerType(container)
const matcher = exact ? matches : fuzzyMatches
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})

Expand Down
4 changes: 3 additions & 1 deletion 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)
}

Expand Down
2 changes: 2 additions & 0 deletions 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,
Expand All @@ -20,6 +21,7 @@ function queryAllByText(
normalizer,
} = {},
) {
checkContainerType(container)
const matcher = exact ? matches : fuzzyMatches
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
let baseArray = []
Expand Down
4 changes: 3 additions & 1 deletion src/queries/title.js
@@ -1,17 +1,19 @@
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {
fuzzyMatches,
matches,
makeNormalizer,
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(
Expand Down

0 comments on commit 16e41ac

Please sign in to comment.