Skip to content

Commit

Permalink
chore(refactor): move queries to TS / prettify some code according ABC (
Browse files Browse the repository at this point in the history
  • Loading branch information
simcha90 authored May 5, 2021
1 parent ffc8f26 commit b00dff9
Show file tree
Hide file tree
Showing 22 changed files with 163 additions and 106 deletions.
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function runWithExpensiveErrorDiagnosticsDisabled<T>(
}
}

export function configure(newConfig: Partial<Config> | ConfigFn) {
export function configure(newConfig: ConfigFn | Partial<Config>) {
if (typeof newConfig === 'function') {
// Pass the existing config out to the provided function
// and accept a delta in return
Expand Down
4 changes: 2 additions & 2 deletions src/get-node-text.js → src/get-node-text.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {TEXT_NODE} from './helpers'

function getNodeText(node) {
function getNodeText(node: HTMLElement): string {
if (node.matches('input[type=submit], input[type=button]')) {
return node.value
return (node as HTMLInputElement).value
}

return Array.from(node.childNodes)
Expand Down
13 changes: 8 additions & 5 deletions src/label-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {Nullish} from '../types'
import {TEXT_NODE} from './helpers'

const labelledNodeNames = [
Expand All @@ -11,7 +12,7 @@ const labelledNodeNames = [
]

function getTextContent(
node: Node | Element | HTMLInputElement,
node: Element | HTMLInputElement | Node,
): string | null {
if (labelledNodeNames.includes(node.nodeName.toLowerCase())) {
return ''
Expand All @@ -24,7 +25,7 @@ function getTextContent(
.join('')
}

function getLabelContent(element: Element): string | null {
function getLabelContent(element: Element): Nullish<string> {
let textContent: string | null
if (element.tagName.toLowerCase() === 'label') {
textContent = getTextContent(element)
Expand Down Expand Up @@ -58,12 +59,14 @@ function getLabels(
container: Element,
element: Element,
{selector = '*'} = {},
) {
): {content: Nullish<string>; formControl: Nullish<HTMLElement>}[] {
const ariaLabelledBy = element.getAttribute('aria-labelledby')
const labelsId = ariaLabelledBy ? ariaLabelledBy.split(' ') : []
return labelsId.length
? labelsId.map(labelId => {
const labellingElement = container.querySelector(`[id="${labelId}"]`)
const labellingElement = container.querySelector<HTMLElement>(
`[id="${labelId}"]`,
)
return labellingElement
? {content: getLabelContent(labellingElement), formControl: null}
: {content: '', formControl: null}
Expand All @@ -73,7 +76,7 @@ function getLabels(
const formControlSelector =
'button, input, meter, output, progress, select, textarea'
const labelledFormControl = Array.from(
label.querySelectorAll(formControlSelector),
label.querySelectorAll<HTMLElement>(formControlSelector),
).filter(formControlElement => formControlElement.matches(selector))[0]
return {content: textToMatch, formControl: labelledFormControl}
})
Expand Down
File renamed without changes.
13 changes: 8 additions & 5 deletions src/queries/alt-text.js → src/queries/alt-text.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {AllByBoundAttribute, GetErrorFunction} from '../../types'
import {matches, fuzzyMatches, makeNormalizer, buildQueries} from './all-utils'

function queryAllByAltText(
const queryAllByAltText: AllByBoundAttribute = (
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 =>
return Array.from(
container.querySelectorAll<HTMLElement>('img,input,area'),
).filter(node =>
matcher(node.getAttribute('alt'), node, alt, matchNormalizer),
)
}

const getMultipleError = (c, alt) =>
const getMultipleError: GetErrorFunction = (c, alt) =>
`Found multiple elements with the alt text: ${alt}`
const getMissingError = (c, alt) =>
const getMissingError: GetErrorFunction = (c, alt) =>
`Unable to find an element with the alt text: ${alt}`

const queryAllByAltTextWithSuggestions = wrapAllByQueryWithSuggestion(
Expand Down
42 changes: 24 additions & 18 deletions src/queries/display-value.js → src/queries/display-value.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {AllByBoundAttribute, GetErrorFunction} from '../../types'
import {
getNodeText,
matches,
Expand All @@ -8,33 +9,38 @@ import {
buildQueries,
} from './all-utils'

function queryAllByDisplayValue(
const queryAllByDisplayValue: AllByBoundAttribute = (
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(
node => {
if (node.tagName === 'SELECT') {
const selectedOptions = Array.from(node.options).filter(
option => option.selected,
)
return selectedOptions.some(optionNode =>
matcher(getNodeText(optionNode), optionNode, value, matchNormalizer),
)
} else {
return matcher(node.value, node, value, matchNormalizer)
}
},
)
return Array.from(
container.querySelectorAll<HTMLElement>(`input,textarea,select`),
).filter(node => {
if (node.tagName === 'SELECT') {
const selectedOptions = Array.from(
(node as HTMLSelectElement).options,
).filter(option => option.selected)
return selectedOptions.some(optionNode =>
matcher(getNodeText(optionNode), optionNode, value, matchNormalizer),
)
} else {
return matcher(
(node as HTMLInputElement).value,
node,
value,
matchNormalizer,
)
}
})
}

const getMultipleError = (c, value) =>
const getMultipleError: GetErrorFunction = (c, value) =>
`Found multiple elements with the display value: ${value}.`
const getMissingError = (c, value) =>
const getMissingError: GetErrorFunction = (c, value) =>
`Unable to find an element with the display value: ${value}.`

const queryAllByDisplayValueWithSuggestions = wrapAllByQueryWithSuggestion(
Expand Down
File renamed without changes.
38 changes: 26 additions & 12 deletions src/queries/label-text.js → src/queries/label-text.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {getConfig} from '../config'
import {checkContainerType} from '../helpers'
import {getLabels, getRealLabels, getLabelContent} from '../label-helpers'
import {AllByText, GetErrorFunction, Nullish} from '../../types'
import {
fuzzyMatches,
matches,
Expand All @@ -12,19 +13,21 @@ import {
wrapSingleQueryWithSuggestion,
} from './all-utils'

function queryAllLabels(container) {
return Array.from(container.querySelectorAll('label,input'))
function queryAllLabels(
container: HTMLElement,
): {textToMatch: Nullish<string>; node: HTMLElement}[] {
return Array.from(container.querySelectorAll<HTMLElement>('label,input'))
.map(node => {
return {node, textToMatch: getLabelContent(node)}
})
.filter(({textToMatch}) => textToMatch !== null)
}

function queryAllLabelsByText(
const queryAllLabelsByText: AllByText = (
container,
text,
{exact = true, trim, collapseWhitespace, normalizer} = {},
) {
) => {
const matcher = exact ? matches : fuzzyMatches
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})

Expand All @@ -37,27 +40,32 @@ function queryAllLabelsByText(
.map(({node}) => node)
}

function queryAllByLabelText(
const queryAllByLabelText: AllByText = (
container,
text,
{selector = '*', exact = true, collapseWhitespace, trim, normalizer} = {},
) {
) => {
checkContainerType(container)

const matcher = exact ? matches : fuzzyMatches
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
const matchingLabelledElements = Array.from(container.querySelectorAll('*'))
const matchingLabelledElements = Array.from(
container.querySelectorAll<HTMLElement>('*'),
)
.filter(element => {
return (
getRealLabels(element).length || element.hasAttribute('aria-labelledby')
)
})
.reduce((labelledElements, labelledElement) => {
.reduce<HTMLElement[]>((labelledElements, labelledElement) => {
const labelList = getLabels(container, labelledElement, {selector})
labelList
.filter(label => Boolean(label.formControl))
.forEach(label => {
if (matcher(label.content, label.formControl, text, matchNormalizer))
if (
matcher(label.content, label.formControl, text, matchNormalizer) &&
label.formControl
)
labelledElements.push(label.formControl)
})
const labelsValue = labelList
Expand Down Expand Up @@ -92,6 +100,9 @@ function queryAllByLabelText(
return labelledElements
}, [])
.concat(
// TODO: Remove ignore after `queryAllByAttribute` will be moved to TS
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
queryAllByAttribute('aria-label', container, text, {
exact,
normalizer: matchNormalizer,
Expand All @@ -110,7 +121,7 @@ function queryAllByLabelText(
// )
// however, we can give a more helpful error message than the generic one,
// so we're writing this one out by hand.
const getAllByLabelText = (container, text, ...rest) => {
const getAllByLabelText: AllByText = (container, text, ...rest) => {
const els = queryAllByLabelText(container, text, ...rest)
if (!els.length) {
const labels = queryAllLabelsByText(container, text, ...rest)
Expand Down Expand Up @@ -146,7 +157,10 @@ const getAllByLabelText = (container, text, ...rest) => {
return els
}

function getTagNameOfElementAssociatedWithLabelViaFor(container, label) {
function getTagNameOfElementAssociatedWithLabelViaFor(
container: Element,
label: Element,
): Nullish<string> {
const htmlFor = label.getAttribute('for')
if (!htmlFor) {
return null
Expand All @@ -157,7 +171,7 @@ function getTagNameOfElementAssociatedWithLabelViaFor(container, label) {
}

// the reason mentioned above is the same reason we're not using buildQueries
const getMultipleError = (c, text) =>
const getMultipleError: GetErrorFunction = (c, text) =>
`Found multiple elements with the text of: ${text}`
const queryByLabelText = wrapSingleQueryWithSuggestion(
makeSingleQuery(queryAllByLabelText, getMultipleError),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {AllByBoundAttribute, GetErrorFunction} from '../../types'
import {queryAllByAttribute, buildQueries} from './all-utils'

function queryAllByPlaceholderText(...args) {
checkContainerType(...args)
const queryAllByPlaceholderText: AllByBoundAttribute = (...args) => {
checkContainerType(args[0])
// TODO: Remove ignore after `queryAllByAttribute` will be moved to TS
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
return queryAllByAttribute('placeholder', ...args)
}
const getMultipleError = (c, text) =>
const getMultipleError: GetErrorFunction = (c, text) =>
`Found multiple elements with the placeholder text of: ${text}`
const getMissingError = (c, text) =>
const getMissingError: GetErrorFunction = (c, text) =>
`Unable to find an element with the placeholder text of: ${text}`

const queryAllByPlaceholderTextWithSuggestions = wrapAllByQueryWithSuggestion(
Expand Down
12 changes: 8 additions & 4 deletions src/queries/test-id.js → src/queries/test-id.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {checkContainerType} from '../helpers'
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {AllByBoundAttribute, GetErrorFunction} from '../../types'
import {queryAllByAttribute, getConfig, buildQueries} from './all-utils'

const getTestIdAttribute = () => getConfig().testIdAttribute

function queryAllByTestId(...args) {
checkContainerType(...args)
const queryAllByTestId: AllByBoundAttribute = (...args) => {
checkContainerType(args[0])
// TODO: Remove ignore after `queryAllByAttribute` will be moved to TS
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
return queryAllByAttribute(getTestIdAttribute(), ...args)
}

const getMultipleError = (c, id) =>
const getMultipleError: GetErrorFunction = (c, id) =>
`Found multiple elements by: [${getTestIdAttribute()}="${id}"]`
const getMissingError = (c, id) =>
const getMissingError: GetErrorFunction = (c, id) =>
`Unable to find an element by: [${getTestIdAttribute()}="${id}"]`

const queryAllByTestIdWithSuggestions = wrapAllByQueryWithSuggestion(
Expand Down
23 changes: 15 additions & 8 deletions src/queries/text.js → src/queries/text.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {DEFAULT_IGNORE_TAGS} from '../config'
import {AllByText, GetErrorFunction} from '../../types'
import {
fuzzyMatches,
matches,
Expand All @@ -9,7 +10,7 @@ import {
buildQueries,
} from './all-utils'

function queryAllByText(
const queryAllByText: AllByText = (
container,
text,
{
Expand All @@ -20,22 +21,28 @@ function queryAllByText(
ignore = DEFAULT_IGNORE_TAGS,
normalizer,
} = {},
) {
) => {
checkContainerType(container)
const matcher = exact ? matches : fuzzyMatches
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
let baseArray = []
let baseArray: HTMLElement[] = []
if (typeof container.matches === 'function' && container.matches(selector)) {
baseArray = [container]
}
return [...baseArray, ...Array.from(container.querySelectorAll(selector))]
.filter(node => !ignore || !node.matches(ignore))
.filter(node => matcher(getNodeText(node), node, text, matchNormalizer))
return (
[
...baseArray,
...Array.from(container.querySelectorAll<HTMLElement>(selector)),
]
// TODO: `matches` according lib.dom.d.ts can get only `string` but according our code it can handle also boolean :)
.filter(node => !ignore || !node.matches(ignore as string))
.filter(node => matcher(getNodeText(node), node, text, matchNormalizer))
)
}

const getMultipleError = (c, text) =>
const getMultipleError: GetErrorFunction = (c, text) =>
`Found multiple elements with the text: ${text}`
const getMissingError = (c, text) =>
const getMissingError: GetErrorFunction = (c, text) =>
`Unable to find an element with the text: ${text}. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.`

const queryAllByTextWithSuggestions = wrapAllByQueryWithSuggestion(
Expand Down
Loading

0 comments on commit b00dff9

Please sign in to comment.