Skip to content
This repository was archived by the owner on Oct 1, 2024. It is now read-only.

Commit 334e85b

Browse files
author
george
committed
adds edge case error handling to focus trap util
1 parent e787b55 commit 334e85b

File tree

3 files changed

+33
-34
lines changed

3 files changed

+33
-34
lines changed

src/js/__tests__/utils.spec.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -330,36 +330,36 @@ describe("createFocusTrap(container, options = {})", () => {
330330
)
331331
})
332332

333-
it("prints console error if options.useArrow is not strictly a boolean", () => {
333+
it("prints console error if options.matchers is given a non-string matcher", () => {
334334
// Given
335335
renderDOM(testDOM)
336336
// When
337-
createFocusTrap(CONTAINER_SELECTOR, { useArrows: "hello" })
337+
createFocusTrap(CONTAINER_SELECTOR, { matchers: ["a", "button", 8] })
338338
// Then
339339
expect(console.error).toBeCalledWith(
340-
"Invalid data type given to options.useArrows for createFocusTrap. Expected: Boolean."
340+
"Invalid matcher given to options.matchers for createFocusTrap. Expected: String. Recieved: number."
341341
)
342342
})
343343

344-
it("prints console error if options.children is not array-like", () => {
344+
it("prints console error if options.matchers is not strictly an array", () => {
345345
// Given
346346
renderDOM(testDOM)
347347
// When
348-
createFocusTrap(CONTAINER_SELECTOR, { children: { hello: "there" } })
348+
createFocusTrap(CONTAINER_SELECTOR, { matchers: true })
349349
// Then
350350
expect(console.error).toBeCalledWith(
351-
"Invalid data type given to options.children for createFocusTrap. Expected: Array-Like."
351+
"Invalid data type given to options.matchers for createFocusTrap. Expected: Array."
352352
)
353353
})
354354

355355
it("prints console error if options.matchers is not strictly an array", () => {
356356
// Given
357357
renderDOM(testDOM)
358358
// When
359-
createFocusTrap(CONTAINER_SELECTOR, { matchers: true })
359+
createFocusTrap(CONTAINER_SELECTOR, { matchers: [] })
360360
// Then
361361
expect(console.error).toBeCalledWith(
362-
"Invalid data type given to options.matchers for createFocusTrap. Expected: Array."
362+
"Invalid value given to options.matchers for createFocusTrap; value must be an array with at least one selector string"
363363
)
364364
})
365365
})

src/js/collapsible.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default class Collapsible {
116116
/**
117117
* Toggles the collapsible.
118118
*
119-
* @param {{ collapsible: Element, trigger: Element, content: Element, id: String, nextAriaExpandState: Boolean, nextAriaHiddenState: Boolean }} metadata
119+
* @param {{ collapsible: Element, trigger: Element, content: Element, id: String, nextAriaExpandState: String, nextAriaHiddenState: String }} metadata
120120
*/
121121
toggleCollapsible(metadata = {}) {
122122
const { collapsible, trigger, content, id, nextAriaExpandState, nextAriaHiddenState } = metadata

src/js/utils.js

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ const Events = {
2121
const Messages = {
2222
NO_SELECTOR_STRING_OR_CHILDREN_ERROR:
2323
"createFocusTrap must be given one or both of: first parameter (as selector string) and/or options.children (array of elements).",
24-
OPTION_USE_ARROWS_DATA_TYPE_ERROR:
25-
"Invalid data type given to options.useArrows for createFocusTrap. Expected: Boolean.",
2624
OPTION_MATCHERS_DATA_TYPE_ERROR:
2725
"Invalid data type given to options.matchers for createFocusTrap. Expected: Array.",
28-
OPTION_CHILDREN_DATA_TYPE_ERROR:
29-
"Invalid data type given to options.children for createFocusTrap. Expected: Array-Like.",
26+
INCORRECT_MATCHER_TYPE_ERROR: type =>
27+
`Invalid matcher given to options.matchers for createFocusTrap. Expected: String. Recieved: ${type}.`,
28+
NO_MATCHER_LENGTH_ERROR:
29+
"Invalid value given to options.matchers for createFocusTrap; value must be an array with at least one selector string",
3030
NO_PARENT_FOUND_IN_SCOPE: id => `Element couldn't be found with selector string: '${id}'`,
3131
DUPLICATE_SCOPE_ERROR: id =>
3232
`You tried to start an Undernet component with scope '${id}', but that scope is already active.\n\nYou must call COMPONENT_NAME.stop(scopeSelector) first, then.`,
@@ -102,8 +102,8 @@ export const dom = {
102102
* ```
103103
*
104104
* @param {String} selectorString - The selector string of the container element.
105-
* @param {Array<String>} matchers - Optional matchers override. Defaults to common focusable selectors.
106-
* @returns {Array<Element>} Static array of HTML elements
105+
* @param {String[]} matchers - Optional matchers override. Defaults to common focusable selectors.
106+
* @returns {Element[]} Static array of HTML elements
107107
*/
108108
export const getFocusableElements = (selectorString, matchers = Selectors.FOCUSABLE_TAGS) => {
109109
const focusables = matchers
@@ -126,11 +126,6 @@ export const getFocusableElements = (selectorString, matchers = Selectors.FOCUSA
126126
*/
127127
export const iOSMobile = isBrowserEnv ? /(iphone|ipod|ipad)/i.test(navigator.userAgent) : false
128128

129-
const isElementCollection = value => {
130-
if (!isBrowserEnv || !value.constructor) return false
131-
return value.constructor.name === "NodeList" || value.constructor.name === "HTMLCollection"
132-
}
133-
134129
/**
135130
* Factory function that creates focus trap helpers.
136131
*
@@ -152,36 +147,40 @@ const isElementCollection = value => {
152147
* ```
153148
*
154149
* @param {String} selectorString
155-
* @param {{ useArrows: Boolean, children: (Array<Element>|NodeList), matchers: Array<String> }} options
150+
* @param {{ useArrows, children: (NodeList|Element[]), matchers: String[] }} options - useArrows is coerced to true/false.
156151
* @returns {{ start: Function, stop: Function }}
157152
*/
158153
export const createFocusTrap = (selectorString, options = {}) => {
159154
if (!isBrowserEnv) return
160-
const { useArrows = false, children = [], matchers = Selectors.FOCUSABLE_TAGS } = options
155+
const { useArrows, children, matchers = Selectors.FOCUSABLE_TAGS } = options
161156

162157
if (!selectorString && !children.length) {
163158
log(Messages.NO_SELECTOR_STRING_OR_CHILDREN_ERROR)
164159
return
165160
}
166161

167-
if (typeof useArrows !== "boolean") {
168-
log(Messages.OPTION_USE_ARROWS_DATA_TYPE_ERROR)
169-
return
170-
}
171-
172162
if (!Array.isArray(matchers)) {
173163
log(Messages.OPTION_MATCHERS_DATA_TYPE_ERROR)
174164
return
175-
}
165+
} else if (matchers.length) {
166+
let hasBadMatcher = false
167+
168+
matchers.forEach(matcher => {
169+
const type = typeof matcher
170+
if (type !== "string") {
171+
log(Messages.INCORRECT_MATCHER_TYPE_ERROR(type))
172+
hasBadMatcher = true
173+
}
174+
})
176175

177-
if (!Array.isArray(children) && !isElementCollection(children)) {
178-
log(Messages.OPTION_CHILDREN_DATA_TYPE_ERROR)
176+
if (hasBadMatcher) return
177+
} else if (!matchers.length) {
178+
log(Messages.NO_MATCHER_LENGTH_ERROR)
179179
return
180180
}
181181

182-
const focusableChildren = children.length
183-
? children
184-
: getFocusableElements(selectorString, matchers)
182+
const focusableChildren =
183+
children && children.length ? children : getFocusableElements(selectorString, matchers)
185184
const focusableFirstChild = focusableChildren[0]
186185
const focusableLastChild = focusableChildren[focusableChildren.length - 1]
187186

@@ -354,7 +353,7 @@ export const focusOnce = element => {
354353
/**
355354
* Filters an array of elements by if a given attribute has a value.
356355
*
357-
* @param {Array<Element>} elements
356+
* @param {Element[]} elements
358357
* @param {String} attribute
359358
* @param {String} errorMessage
360359
*/

0 commit comments

Comments
 (0)