diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e90641b..5aa76146 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,8 @@ series [How to Contribute to an Open Source Project on GitHub][egghead] 2. Run `npm run setup` to install dependencies and run validation 3. Create a branch for your PR with `git checkout -b pr/your-branch-name` -> Tip: Keep your `main` branch pointing at the original repository and make -> pull requests from branches on your fork. To do this, run: +> Tip: Keep your `main` branch pointing at the original repository and make pull +> requests from branches on your fork. To do this, run: > > ``` > git remote add upstream https://github.com/testing-library/dom-testing-library.git @@ -21,10 +21,10 @@ series [How to Contribute to an Open Source Project on GitHub][egghead] > ``` > > This will add the original repository as a "remote" called "upstream," Then -> fetch the git information from that remote, then set your local `main` -> branch to use the upstream main branch whenever you run `git pull`. Then you -> can make all of your pull request branches based on this `main` branch. -> Whenever you want to update your version of `main`, do a regular `git pull`. +> fetch the git information from that remote, then set your local `main` branch +> to use the upstream main branch whenever you run `git pull`. Then you can make +> all of your pull request branches based on this `main` branch. Whenever you +> want to update your version of `main`, do a regular `git pull`. ## Committing and Pushing changes diff --git a/src/event-map.js b/src/event-map.js deleted file mode 100644 index 6b026094..00000000 --- a/src/event-map.js +++ /dev/null @@ -1,350 +0,0 @@ -export const eventMap = { - // Clipboard Events - copy: { - EventType: 'ClipboardEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - cut: { - EventType: 'ClipboardEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - paste: { - EventType: 'ClipboardEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - // Composition Events - compositionEnd: { - EventType: 'CompositionEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - compositionStart: { - EventType: 'CompositionEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - compositionUpdate: { - EventType: 'CompositionEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - // Keyboard Events - keyDown: { - EventType: 'KeyboardEvent', - defaultInit: {bubbles: true, cancelable: true, charCode: 0, composed: true}, - }, - keyPress: { - EventType: 'KeyboardEvent', - defaultInit: {bubbles: true, cancelable: true, charCode: 0, composed: true}, - }, - keyUp: { - EventType: 'KeyboardEvent', - defaultInit: {bubbles: true, cancelable: true, charCode: 0, composed: true}, - }, - // Focus Events - focus: { - EventType: 'FocusEvent', - defaultInit: {bubbles: false, cancelable: false, composed: true}, - }, - blur: { - EventType: 'FocusEvent', - defaultInit: {bubbles: false, cancelable: false, composed: true}, - }, - focusIn: { - EventType: 'FocusEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - focusOut: { - EventType: 'FocusEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - // Form Events - change: { - EventType: 'Event', - defaultInit: {bubbles: true, cancelable: false}, - }, - input: { - EventType: 'InputEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - invalid: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: true}, - }, - submit: { - EventType: 'Event', - defaultInit: {bubbles: true, cancelable: true}, - }, - reset: { - EventType: 'Event', - defaultInit: {bubbles: true, cancelable: true}, - }, - // Mouse Events - click: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, button: 0, composed: true}, - }, - contextMenu: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - dblClick: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - drag: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - dragEnd: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - dragEnter: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - dragExit: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - dragLeave: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - dragOver: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - dragStart: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - drop: { - EventType: 'DragEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - mouseDown: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - mouseEnter: { - EventType: 'MouseEvent', - defaultInit: {bubbles: false, cancelable: false, composed: true}, - }, - mouseLeave: { - EventType: 'MouseEvent', - defaultInit: {bubbles: false, cancelable: false, composed: true}, - }, - mouseMove: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - mouseOut: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - mouseOver: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - mouseUp: { - EventType: 'MouseEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - // Selection Events - select: { - EventType: 'Event', - defaultInit: {bubbles: true, cancelable: false}, - }, - // Touch Events - touchCancel: { - EventType: 'TouchEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - touchEnd: { - EventType: 'TouchEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - touchMove: { - EventType: 'TouchEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - touchStart: { - EventType: 'TouchEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - // UI Events - scroll: { - EventType: 'UIEvent', - defaultInit: {bubbles: false, cancelable: false}, - }, - // Wheel Events - wheel: { - EventType: 'WheelEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - // Media Events - abort: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - canPlay: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - canPlayThrough: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - durationChange: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - emptied: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - encrypted: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - ended: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - loadedData: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - loadedMetadata: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - loadStart: { - EventType: 'ProgressEvent', - defaultInit: {bubbles: false, cancelable: false}, - }, - pause: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - play: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - playing: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - progress: { - EventType: 'ProgressEvent', - defaultInit: {bubbles: false, cancelable: false}, - }, - rateChange: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - seeked: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - seeking: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - stalled: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - suspend: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - timeUpdate: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - volumeChange: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - waiting: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - // Image Events - load: { - EventType: 'UIEvent', - defaultInit: {bubbles: false, cancelable: false}, - }, - error: { - EventType: 'Event', - defaultInit: {bubbles: false, cancelable: false}, - }, - // Animation Events - animationStart: { - EventType: 'AnimationEvent', - defaultInit: {bubbles: true, cancelable: false}, - }, - animationEnd: { - EventType: 'AnimationEvent', - defaultInit: {bubbles: true, cancelable: false}, - }, - animationIteration: { - EventType: 'AnimationEvent', - defaultInit: {bubbles: true, cancelable: false}, - }, - // Transition Events - transitionEnd: { - EventType: 'TransitionEvent', - defaultInit: {bubbles: true, cancelable: true}, - }, - // pointer events - pointerOver: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - pointerEnter: { - EventType: 'PointerEvent', - defaultInit: {bubbles: false, cancelable: false}, - }, - pointerDown: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - pointerMove: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - pointerUp: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - pointerCancel: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - pointerOut: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: true, composed: true}, - }, - pointerLeave: { - EventType: 'PointerEvent', - defaultInit: {bubbles: false, cancelable: false}, - }, - gotPointerCapture: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - lostPointerCapture: { - EventType: 'PointerEvent', - defaultInit: {bubbles: true, cancelable: false, composed: true}, - }, - // history events - popState: { - EventType: 'PopStateEvent', - defaultInit: {bubbles: true, cancelable: false}, - }, - } - - export const eventAliasMap = { - doubleClick: 'dblClick', - } diff --git a/src/event-map.ts b/src/event-map.ts new file mode 100644 index 00000000..e8cb6a5d --- /dev/null +++ b/src/event-map.ts @@ -0,0 +1,350 @@ +export const eventMap = { + // Clipboard Events + copy: { + EventType: 'ClipboardEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + cut: { + EventType: 'ClipboardEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + paste: { + EventType: 'ClipboardEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + // Composition Events + compositionEnd: { + EventType: 'CompositionEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + compositionStart: { + EventType: 'CompositionEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + compositionUpdate: { + EventType: 'CompositionEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + // Keyboard Events + keyDown: { + EventType: 'KeyboardEvent', + defaultInit: {bubbles: true, cancelable: true, charCode: 0, composed: true}, + }, + keyPress: { + EventType: 'KeyboardEvent', + defaultInit: {bubbles: true, cancelable: true, charCode: 0, composed: true}, + }, + keyUp: { + EventType: 'KeyboardEvent', + defaultInit: {bubbles: true, cancelable: true, charCode: 0, composed: true}, + }, + // Focus Events + focus: { + EventType: 'FocusEvent', + defaultInit: {bubbles: false, cancelable: false, composed: true}, + }, + blur: { + EventType: 'FocusEvent', + defaultInit: {bubbles: false, cancelable: false, composed: true}, + }, + focusIn: { + EventType: 'FocusEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + focusOut: { + EventType: 'FocusEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + // Form Events + change: { + EventType: 'Event', + defaultInit: {bubbles: true, cancelable: false}, + }, + input: { + EventType: 'InputEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + invalid: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: true}, + }, + submit: { + EventType: 'Event', + defaultInit: {bubbles: true, cancelable: true}, + }, + reset: { + EventType: 'Event', + defaultInit: {bubbles: true, cancelable: true}, + }, + // Mouse Events + click: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, button: 0, composed: true}, + }, + contextMenu: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + dblClick: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + drag: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + dragEnd: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + dragEnter: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + dragExit: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + dragLeave: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + dragOver: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + dragStart: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + drop: { + EventType: 'DragEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + mouseDown: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + mouseEnter: { + EventType: 'MouseEvent', + defaultInit: {bubbles: false, cancelable: false, composed: true}, + }, + mouseLeave: { + EventType: 'MouseEvent', + defaultInit: {bubbles: false, cancelable: false, composed: true}, + }, + mouseMove: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + mouseOut: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + mouseOver: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + mouseUp: { + EventType: 'MouseEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + // Selection Events + select: { + EventType: 'Event', + defaultInit: {bubbles: true, cancelable: false}, + }, + // Touch Events + touchCancel: { + EventType: 'TouchEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + touchEnd: { + EventType: 'TouchEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + touchMove: { + EventType: 'TouchEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + touchStart: { + EventType: 'TouchEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + // UI Events + scroll: { + EventType: 'UIEvent', + defaultInit: {bubbles: false, cancelable: false}, + }, + // Wheel Events + wheel: { + EventType: 'WheelEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + // Media Events + abort: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + canPlay: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + canPlayThrough: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + durationChange: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + emptied: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + encrypted: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + ended: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + loadedData: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + loadedMetadata: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + loadStart: { + EventType: 'ProgressEvent', + defaultInit: {bubbles: false, cancelable: false}, + }, + pause: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + play: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + playing: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + progress: { + EventType: 'ProgressEvent', + defaultInit: {bubbles: false, cancelable: false}, + }, + rateChange: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + seeked: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + seeking: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + stalled: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + suspend: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + timeUpdate: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + volumeChange: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + waiting: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + // Image Events + load: { + EventType: 'UIEvent', + defaultInit: {bubbles: false, cancelable: false}, + }, + error: { + EventType: 'Event', + defaultInit: {bubbles: false, cancelable: false}, + }, + // Animation Events + animationStart: { + EventType: 'AnimationEvent', + defaultInit: {bubbles: true, cancelable: false}, + }, + animationEnd: { + EventType: 'AnimationEvent', + defaultInit: {bubbles: true, cancelable: false}, + }, + animationIteration: { + EventType: 'AnimationEvent', + defaultInit: {bubbles: true, cancelable: false}, + }, + // Transition Events + transitionEnd: { + EventType: 'TransitionEvent', + defaultInit: {bubbles: true, cancelable: true}, + }, + // pointer events + pointerOver: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + pointerEnter: { + EventType: 'PointerEvent', + defaultInit: {bubbles: false, cancelable: false}, + }, + pointerDown: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + pointerMove: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + pointerUp: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + pointerCancel: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + pointerOut: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: true, composed: true}, + }, + pointerLeave: { + EventType: 'PointerEvent', + defaultInit: {bubbles: false, cancelable: false}, + }, + gotPointerCapture: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + lostPointerCapture: { + EventType: 'PointerEvent', + defaultInit: {bubbles: true, cancelable: false, composed: true}, + }, + // history events + popState: { + EventType: 'PopStateEvent', + defaultInit: {bubbles: true, cancelable: false}, + }, +} + +export const eventAliasMap = { + doubleClick: 'dblClick', +} diff --git a/src/get-queries-for-element.js b/src/get-queries-for-element.ts similarity index 70% rename from src/get-queries-for-element.js rename to src/get-queries-for-element.ts index cc81578b..aa2e90da 100644 --- a/src/get-queries-for-element.js +++ b/src/get-queries-for-element.ts @@ -10,14 +10,17 @@ import * as defaultQueries from './queries' * @param {Object} initialValue for reducer * @returns {FuncMap} returns object of functions bound to container */ + +type BoundFunction = {[key: string]: Function} + function getQueriesForElement( - element, - queries = defaultQueries, - initialValue = {}, + element: HTMLElement, + queries: BoundFunction = defaultQueries, + initialValue: BoundFunction = {}, ) { return Object.keys(queries).reduce((helpers, key) => { const fn = queries[key] - helpers[key] = fn.bind(null, element) + helpers[key] = fn.bind(null, element) as Function return helpers }, initialValue) } diff --git a/src/get-user-code-frame.js b/src/get-user-code-frame.ts similarity index 54% rename from src/get-user-code-frame.js rename to src/get-user-code-frame.ts index 7cdb90b6..561be225 100644 --- a/src/get-user-code-frame.js +++ b/src/get-user-code-frame.ts @@ -1,21 +1,25 @@ // We try to load node dependencies -let chalk = null -let readFileSync = null -let codeFrameColumns = null +let chalk: {dim: Function} | null = null +let readFileSync: Function | null = null +let codeFrameColumns: Function | null = null try { - const nodeRequire = module && module.require + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const nodeRequire = module?.require + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access readFileSync = nodeRequire.call(module, 'fs').readFileSync + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment codeFrameColumns = nodeRequire.call(module, '@babel/code-frame') .codeFrameColumns + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment chalk = nodeRequire.call(module, 'chalk') } catch { // We're in a browser environment } // frame has the form "at myMethod (location/to/my/file.js:10:2)" -function getCodeFrame(frame) { +function getCodeFrame(frame: string) { const locationStart = frame.indexOf('(') + 1 const locationEnd = frame.indexOf(')') const frameLocation = frame.slice(locationStart, locationEnd) @@ -29,12 +33,14 @@ function getCodeFrame(frame) { let rawFileContents = '' try { - rawFileContents = readFileSync(filename, 'utf-8') + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + rawFileContents = readFileSync?.(filename, 'utf-8') } catch { return '' } - const codeFrame = codeFrameColumns( + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const codeFrame = codeFrameColumns?.( rawFileContents, { start: {line, column}, @@ -44,7 +50,7 @@ function getCodeFrame(frame) { linesBelow: 0, }, ) - return `${chalk.dim(frameLocation)}\n${codeFrame}\n` + return `${chalk?.dim(frameLocation)}\n${codeFrame}\n` } function getUserCodeFrame() { @@ -53,13 +59,15 @@ function getUserCodeFrame() { if (!readFileSync || !codeFrameColumns) { return '' } - const err = new Error() - const firstClientCodeFrame = err.stack + const err: Error = new Error() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const firstClientCodeFrame: string | undefined = err.stack .split('\n') .slice(1) // Remove first line which has the form "Error: TypeError" .find(frame => !frame.includes('node_modules/')) // Ignore frames from 3rd party libraries - return getCodeFrame(firstClientCodeFrame) + return getCodeFrame(firstClientCodeFrame ?? '') } export {getUserCodeFrame} diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/src/queries/role.js b/src/queries/role.ts similarity index 75% rename from src/queries/role.js rename to src/queries/role.ts index 747e2ca0..dd5d0d9e 100644 --- a/src/queries/role.js +++ b/src/queries/role.ts @@ -1,5 +1,9 @@ import {computeAccessibleName} from 'dom-accessibility-api' -import {roles as allRoles} from 'aria-query' +import { + ARIAAbstractRole, + ARIARoleDefintionKey, + roles as allRoles, +} from 'aria-query' import { computeAriaSelected, computeAriaChecked, @@ -13,6 +17,7 @@ import { } from '../role-helpers' import {wrapAllByQueryWithSuggestion} from '../query-helpers' import {checkContainerType} from '../helpers' +import {AllByRole, ByRoleMatcher, ByRoleOptionsName, Nullish} from '../../types' import { buildQueries, fuzzyMatches, @@ -21,7 +26,7 @@ import { matches, } from './all-utils' -function queryAllByRole( +const queryAllByRole: AllByRole = ( container, role, { @@ -38,28 +43,35 @@ function queryAllByRole( level, expanded, } = {}, -) { +) => { checkContainerType(container) const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) if (selected !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-selected'] === undefined) { + if ( + allRoles.get(role)?.props['aria-selected'] === + undefined + ) { throw new Error(`"aria-selected" is not supported on role "${role}".`) } } if (checked !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-checked'] === undefined) { + if ( + allRoles.get(role)?.props['aria-checked'] === undefined + ) { throw new Error(`"aria-checked" is not supported on role "${role}".`) } } if (pressed !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-pressed'] === undefined) { + if ( + allRoles.get(role)?.props['aria-pressed'] === undefined + ) { throw new Error(`"aria-pressed" is not supported on role "${role}".`) } } @@ -73,13 +85,15 @@ function queryAllByRole( if (expanded !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-expanded'] === undefined) { + if ( + allRoles.get(role)?.props['aria-expanded'] === undefined + ) { throw new Error(`"aria-expanded" is not supported on role "${role}".`) } } const subtreeIsInaccessibleCache = new WeakMap() - function cachedIsSubtreeInaccessible(element) { + function cachedIsSubtreeInaccessible(element: HTMLElement) { if (!subtreeIsInaccessibleCache.has(element)) { subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element)) } @@ -87,12 +101,12 @@ function queryAllByRole( return subtreeIsInaccessibleCache.get(element) } - return Array.from(container.querySelectorAll('*')) + return Array.from(container.querySelectorAll('*')) .filter(node => { const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { - const roleValue = node.getAttribute('role') + const roleValue = node.getAttribute('role') ?? '' if (queryFallbacks) { return roleValue .split(' ') @@ -108,7 +122,9 @@ function queryAllByRole( return matcher(firstWord, node, role, matchNormalizer) } - const implicitRoles = getImplicitAriaRoles(node) + // TODO: Remove ignore after `getImplicitAriaRoles` will be moved to TS + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const implicitRoles: Nullish[] = getImplicitAriaRoles(node) return implicitRoles.some(implicitRole => matcher(implicitRole, node, role, matchNormalizer), @@ -134,11 +150,14 @@ function queryAllByRole( return true }) .filter(element => { - return hidden === false - ? isInaccessible(element, { + return hidden + ? true + : !isInaccessible(element, { + // TODO: Remove ignore after `isInaccessible` will be moved to TS + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error isSubtreeInaccessible: cachedIsSubtreeInaccessible, - }) === false - : true + }) }) .filter(element => { if (name === undefined) { @@ -158,7 +177,11 @@ function queryAllByRole( }) } -const getMultipleError = (c, role, {name} = {}) => { +const getMultipleError = ( + c: HTMLElement, + role: ByRoleMatcher, + {name}: {name?: ByRoleOptionsName} = {}, +): string => { let nameHint = '' if (name === undefined) { nameHint = '' @@ -172,9 +195,12 @@ const getMultipleError = (c, role, {name} = {}) => { } const getMissingError = ( - container, - role, - {hidden = getConfig().defaultHidden, name} = {}, + container: HTMLElement, + role: string, + { + hidden = getConfig().defaultHidden, + name, + }: {hidden?: boolean; name?: ByRoleOptionsName} = {}, ) => { if (getConfig()._disableExpensiveErrorDiagnostics) { return `Unable to find role="${role}"` @@ -184,23 +210,26 @@ const getMissingError = ( Array.from(container.children).forEach(childElement => { roles += prettyRoles(childElement, { hidden, + // TODO: Remove ignore after `prettyRoles` will be moved to TS + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error includeName: name !== undefined, }) }) let roleMessage if (roles.length === 0) { - if (hidden === false) { + if (hidden) { + roleMessage = 'There are no available roles.' + } else { roleMessage = 'There are no accessible roles. But there might be some inaccessible roles. ' + 'If you wish to access them, then set the `hidden` option to `true`. ' + 'Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole' - } else { - roleMessage = 'There are no available roles.' } } else { roleMessage = ` -Here are the ${hidden === false ? 'accessible' : 'available'} roles: +Here are the ${hidden ? 'available' : 'accessible'} roles: ${roles.replace(/\n/g, '\n ').replace(/\n\s\s\n/g, '\n\n')} `.trim() @@ -217,7 +246,7 @@ Here are the ${hidden === false ? 'accessible' : 'available'} roles: return ` Unable to find an ${ - hidden === false ? 'accessible ' : '' + hidden ? '' : 'accessible ' }element with the role "${role}"${nameHint} ${roleMessage}`.trim() diff --git a/types/events.d.ts b/types/events.d.ts index 925da69f..aa323412 100644 --- a/types/events.d.ts +++ b/types/events.d.ts @@ -1,25 +1,23 @@ +import {Nullish} from './matches' + export type EventType = - | 'copy' - | 'cut' - | 'paste' - | 'compositionEnd' - | 'compositionStart' - | 'compositionUpdate' - | 'keyDown' - | 'keyPress' - | 'keyUp' - | 'focus' + | 'abort' + | 'animationEnd' + | 'animationIteration' + | 'animationStart' | 'blur' - | 'focusIn' - | 'focusOut' + | 'canPlay' + | 'canPlayThrough' | 'change' - | 'input' - | 'invalid' - | 'submit' - | 'reset' | 'click' + | 'compositionEnd' + | 'compositionStart' + | 'compositionUpdate' | 'contextMenu' + | 'copy' + | 'cut' | 'dblClick' + | 'doubleClick' | 'drag' | 'dragEnd' | 'dragEnter' @@ -28,83 +26,87 @@ export type EventType = | 'dragOver' | 'dragStart' | 'drop' - | 'mouseDown' - | 'mouseEnter' - | 'mouseLeave' - | 'mouseMove' - | 'mouseOut' - | 'mouseOver' - | 'mouseUp' - | 'popState' - | 'select' - | 'touchCancel' - | 'touchEnd' - | 'touchMove' - | 'touchStart' - | 'scroll' - | 'wheel' - | 'abort' - | 'canPlay' - | 'canPlayThrough' | 'durationChange' | 'emptied' | 'encrypted' | 'ended' + | 'error' + | 'focus' + | 'focusIn' + | 'focusOut' + | 'gotPointerCapture' + | 'input' + | 'invalid' + | 'keyDown' + | 'keyPress' + | 'keyUp' + | 'load' | 'loadedData' | 'loadedMetadata' | 'loadStart' + | 'lostPointerCapture' + | 'mouseDown' + | 'mouseEnter' + | 'mouseLeave' + | 'mouseMove' + | 'mouseOut' + | 'mouseOver' + | 'mouseUp' + | 'paste' | 'pause' | 'play' | 'playing' + | 'pointerCancel' + | 'pointerDown' + | 'pointerEnter' + | 'pointerLeave' + | 'pointerMove' + | 'pointerOut' + | 'pointerOver' + | 'pointerUp' + | 'popState' | 'progress' | 'rateChange' + | 'reset' + | 'scroll' | 'seeked' | 'seeking' + | 'select' | 'stalled' + | 'submit' | 'suspend' | 'timeUpdate' + | 'touchCancel' + | 'touchEnd' + | 'touchMove' + | 'touchStart' + | 'transitionEnd' | 'volumeChange' | 'waiting' - | 'load' - | 'error' - | 'animationStart' - | 'animationEnd' - | 'animationIteration' - | 'transitionEnd' - | 'doubleClick' - | 'pointerOver' - | 'pointerEnter' - | 'pointerDown' - | 'pointerMove' - | 'pointerUp' - | 'pointerCancel' - | 'pointerOut' - | 'pointerLeave' - | 'gotPointerCapture' - | 'lostPointerCapture' + | 'wheel' export type FireFunction = ( - element: Document | Element | Window | Node, - event: Event, + element: Nullish, + event: Nullish, ) => boolean export type FireObject = { [K in EventType]: ( - element: Document | Element | Window | Node, + element: Document | Element | Node | Window, options?: {}, ) => boolean } export type CreateFunction = ( eventName: string, - node: Document | Element | Window | Node, + node: Nullish, init?: {}, options?: {EventType?: string; defaultInit?: {}}, ) => Event export type CreateObject = { [K in EventType]: ( - element: Document | Element | Window | Node, + element: Document | Element | Node | Window, options?: {}, ) => Event } -export const createEvent: CreateObject & CreateFunction +export const createEvent: CreateFunction & CreateObject export const fireEvent: FireFunction & FireObject diff --git a/types/matches.d.ts b/types/matches.d.ts index 85e9c9a7..d5fff504 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -10,7 +10,7 @@ export type Matcher = MatcherFunction | RegExp | number | string // Get autocomplete for ARIARole union types, while still supporting another string // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972 -export type ByRoleMatcher = ARIARole | MatcherFunction | {} +export type ByRoleMatcher = ARIARole | Matcher | MatcherFunction export type NormalizerFn = (text: string) => string diff --git a/types/queries.d.ts b/types/queries.d.ts index 42777ffd..f0e71b5c 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -66,6 +66,11 @@ export type FindByText = ( waitForElementOptions?: waitForOptions, ) => Promise +export type ByRoleOptionsName = + | RegExp + | string + | ((accessibleName: string, element: Nullish) => boolean) + export interface ByRoleOptions extends MatcherOptions { /** * If true includes elements in the query set that are usually excluded from @@ -107,10 +112,7 @@ export interface ByRoleOptions extends MatcherOptions { /** * Only considers elements with the specified accessible name. */ - name?: - | RegExp - | string - | ((accessibleName: string, element: Element) => boolean) + name?: ByRoleOptionsName } export type AllByRole = (