Skip to content

feat: migrate some files to typescript #848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"node": ">=10"
},
"scripts": {
"build": "kcd-scripts build --ignore \"**/__tests__/**,**/__node_tests__/**,**/__mocks__/**\" && kcd-scripts build --bundle --no-clean",
"build": "kcd-scripts build --no-ts-defs --ignore \"**/__tests__/**,**/__node_tests__/**,**/__mocks__/**\" && kcd-scripts build --no-ts-defs --bundle --no-clean",
"lint": "kcd-scripts lint",
"setup": "npm install && npm run validate -s",
"test": "kcd-scripts test",
Expand Down Expand Up @@ -53,11 +53,14 @@
"jest-serializer-ansi": "^1.0.3",
"jest-watch-select-projects": "^2.0.0",
"jsdom": "^16.4.0",
"kcd-scripts": "^7.5.1",
"kcd-scripts": "^7.5.3",
"typescript": "^4.1.2"
},
"eslintConfig": {
"extends": "./node_modules/kcd-scripts/eslint.js",
"extends": [
"./node_modules/kcd-scripts/eslint.js",
"plugin:import/typescript"
],
Comment on lines -60 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@marcosvega91 marcosvega91 Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I see, the strange thing is that is should already be in kcd

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be. That's an oversight

"rules": {
"import/prefer-default-export": "off",
"import/no-unassigned-import": "off",
Expand Down
14 changes: 11 additions & 3 deletions src/config.js → src/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import {Config, ConfigFn} from '../types/config'
import {prettyDOM} from './pretty-dom'

type Callback<T> = () => T
interface InternalConfig extends Config {
_disableExpensiveErrorDiagnostics: boolean
}

// It would be cleaner for this to live inside './queries', but
// other parts of the code assume that all exports from
// './queries' are query functions.
let config = {
let config: InternalConfig = {
testIdAttribute: 'data-testid',
asyncUtilTimeout: 1000,
// this is to support React's async `act` function.
Expand Down Expand Up @@ -36,7 +42,9 @@ let config = {
}

export const DEFAULT_IGNORE_TAGS = 'script, style'
export function runWithExpensiveErrorDiagnosticsDisabled(callback) {
export function runWithExpensiveErrorDiagnosticsDisabled<T>(
callback: Callback<T>,
) {
try {
config._disableExpensiveErrorDiagnostics = true
return callback()
Expand All @@ -45,7 +53,7 @@ export function runWithExpensiveErrorDiagnosticsDisabled(callback) {
}
}

export function configure(newConfig) {
export function configure(newConfig: Partial<Config> | ConfigFn) {
if (typeof newConfig === 'function') {
// Pass the existing config out to the provided function
// and accept a delta in return
Expand Down
15 changes: 9 additions & 6 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,15 @@ function createEvent(
/* istanbul ignore if */
if (typeof window.DataTransfer === 'function') {
Object.defineProperty(event, dataTransferKey, {
value: Object
.getOwnPropertyNames(dataTransferValue)
.reduce((acc, propName) => {
Object.defineProperty(acc, propName, {value: dataTransferValue[propName]});
return acc;
}, new window.DataTransfer())
value: Object.getOwnPropertyNames(dataTransferValue).reduce(
(acc, propName) => {
Object.defineProperty(acc, propName, {
value: dataTransferValue[propName],
})
return acc
},
new window.DataTransfer(),
),
})
} else {
Object.defineProperty(event, dataTransferKey, {
Expand Down
37 changes: 22 additions & 15 deletions src/label-helpers.js → src/label-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const labelledNodeNames = [
'input',
]

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

function getLabelContent(node) {
let textContent
if (node.tagName.toLowerCase() === 'label') {
textContent = getTextContent(node)
function getLabelContent(element: Element): string | null {
let textContent: string | null
if (element.tagName.toLowerCase() === 'label') {
textContent = getTextContent(element)
} else {
textContent = node.value || node.textContent
textContent = (element as HTMLInputElement).value || element.textContent
}
return textContent
}

// Based on https://github.com/eps1lon/dom-accessibility-api/pull/352
function getRealLabels(element) {
if (element.labels !== undefined) return element.labels ?? []
function getRealLabels(element: Element) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- types are not aware of older browsers that don't implement `labels`
if ((element as HTMLInputElement).labels !== undefined) {
return (element as HTMLInputElement).labels ?? []
}

if (!isLabelable(element)) return []

const labels = element.ownerDocument.querySelectorAll('label')
return Array.from(labels).filter(label => label.control === element)
}

function isLabelable(element) {
function isLabelable(element: Element) {
return (
element.tagName.match(/BUTTON|METER|OUTPUT|PROGRESS|SELECT|TEXTAREA/) ||
/BUTTON|METER|OUTPUT|PROGRESS|SELECT|TEXTAREA/.test(element.tagName) ||
(element.tagName === 'INPUT' && element.getAttribute('type') !== 'hidden')
)
}

function getLabels(container, element, {selector = '*'} = {}) {
const labelsId = element.getAttribute('aria-labelledby')
? element.getAttribute('aria-labelledby').split(' ')
: []
function getLabels(
container: Element,
element: Element,
{selector = '*'} = {},
) {
const ariaLabelledBy = element.getAttribute('aria-labelledby')
const labelsId = ariaLabelledBy ? ariaLabelledBy.split(' ') : []
return labelsId.length
? labelsId.map(labelId => {
const labellingElement = container.querySelector(`[id="${labelId}"]`)
Expand All @@ -67,7 +75,6 @@ function getLabels(container, element, {selector = '*'} = {}) {
const labelledFormControl = Array.from(
label.querySelectorAll(formControlSelector),
).filter(formControlElement => formControlElement.matches(selector))[0]

return {content: textToMatch, formControl: labelledFormControl}
})
}
Expand Down
44 changes: 37 additions & 7 deletions src/matches.js → src/matches.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
function assertNotNullOrUndefined(matcher) {
if (matcher == null) {
import {
Matcher,
NormalizerFn,
NormalizerOptions,
DefaultNormalizerOptions,
} from '../types'

type Nullish<T> = T | null | undefined

function assertNotNullOrUndefined<T>(
matcher: T,
): asserts matcher is NonNullable<T> {
if (matcher === null || matcher === undefined) {
throw new Error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- implicitly converting `T` to `string`
`It looks like ${matcher} was passed instead of a matcher. Did you do something like getByText(${matcher})?`,
)
}
}

function fuzzyMatches(textToMatch, node, matcher, normalizer) {
function fuzzyMatches(
textToMatch: Nullish<string>,
node: Nullish<Element>,
matcher: Nullish<Matcher>,
normalizer: NormalizerFn,
) {
if (typeof textToMatch !== 'string') {
return false
}

assertNotNullOrUndefined(matcher)

const normalizedText = normalizer(textToMatch)

if (typeof matcher === 'string') {
return normalizedText.toLowerCase().includes(matcher.toLowerCase())
} else if (typeof matcher === 'function') {
Expand All @@ -23,7 +40,12 @@ function fuzzyMatches(textToMatch, node, matcher, normalizer) {
}
}

function matches(textToMatch, node, matcher, normalizer) {
function matches(
textToMatch: Nullish<string>,
node: Nullish<Element>,
matcher: Nullish<Matcher>,
normalizer: NormalizerFn,
) {
if (typeof textToMatch !== 'string') {
return false
}
Expand All @@ -40,7 +62,10 @@ function matches(textToMatch, node, matcher, normalizer) {
}
}

function getDefaultNormalizer({trim = true, collapseWhitespace = true} = {}) {
function getDefaultNormalizer({
trim = true,
collapseWhitespace = true,
}: DefaultNormalizerOptions = {}): NormalizerFn {
return text => {
let normalizedText = text
normalizedText = trim ? normalizedText.trim() : normalizedText
Expand All @@ -60,7 +85,12 @@ function getDefaultNormalizer({trim = true, collapseWhitespace = true} = {}) {
* @param {Function|undefined} normalizer The user-specified normalizer
* @returns {Function} A normalizer
*/
function makeNormalizer({trim, collapseWhitespace, normalizer}) {

function makeNormalizer({
trim,
collapseWhitespace,
normalizer,
}: NormalizerOptions) {
if (normalizer) {
// User has specified a custom normalizer
if (
Expand Down
7 changes: 7 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "./node_modules/kcd-scripts/shared-tsconfig.json",
"compilerOptions": {
"allowJs": true
},
"include": ["./src", "./types"]
}
4 changes: 3 additions & 1 deletion types/config.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
export interface Config {
testIdAttribute: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
asyncWrapper(cb: (...args: any[]) => any): Promise<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventWrapper(cb: (...args: any[]) => any): void
asyncUtilTimeout: number
computedStyleSupportsPseudoElements: boolean
defaultHidden: boolean
showOriginalStackTrace: boolean
throwSuggestions: boolean
getElementError: (message: string, container: HTMLElement) => Error
getElementError: (message: string | null, container: Element) => Error
}

export interface ConfigFn {
Expand Down
13 changes: 11 additions & 2 deletions types/matches.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import {ARIARole} from 'aria-query'

export type MatcherFunction = (content: string, element: HTMLElement) => boolean
export type Matcher = MatcherFunction | {}
type Nullish<T> = T | null | undefined

export type MatcherFunction = (
content: string,
element: Nullish<Element>,
) => boolean
export type Matcher = MatcherFunction | RegExp | 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 NormalizerFn = (text: string) => string

export interface NormalizerOptions extends DefaultNormalizerOptions {
normalizer?: NormalizerFn
}

export interface MatcherOptions {
exact?: boolean
/** Use normalizer with getDefaultNormalizer instead */
Expand Down
3 changes: 1 addition & 2 deletions types/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"extends": "../node_modules/kcd-scripts/shared-tsconfig.json",
"include": ["."]
"extends": "../tsconfig.json"
}