diff --git a/src/__tests__/suggestions.js b/src/__tests__/suggestions.js new file mode 100644 index 00000000..75d6f90e --- /dev/null +++ b/src/__tests__/suggestions.js @@ -0,0 +1,256 @@ +import {configure} from '../config' +import {screen} from '..' +import {renderIntoDocument} from './helpers/test-utils' + +beforeAll(() => { + configure({throwSuggestions: true}) +}) + +afterAll(() => { + configure({throwSuggestions: false}) +}) + +test('does not suggest when using getByRole', () => { + renderIntoDocument(``) + + expect(() => screen.getByRole('button', {name: /submit/i})).not.toThrowError() +}) + +test('should not suggest when nothing available', () => { + renderIntoDocument(``) + + expect(() => screen.queryByTestId('foo')).not.toThrowError() +}) + +test(`should not suggest if the suggestion would give different results`, () => { + renderIntoDocument(` + + `) + + expect(() => + screen.getAllByTestId('foo', {suggest: false}), + ).not.toThrowError() +}) + +test('should not suggest if there would be mixed suggestions', () => { + renderIntoDocument(` + + `) + + expect(() => screen.getAllByTestId('foo')).not.toThrowError() +}) + +test('should not suggest when suggest is turned off for a query', () => { + renderIntoDocument(` + + `) + + expect(() => screen.getByTestId('foo', {suggest: false})).not.toThrowError() + expect(() => + screen.getAllByTestId(/foo/, {suggest: false}), + ).not.toThrowError() +}) + +test('should suggest getByRole when used with getBy', () => { + renderIntoDocument(``) + + expect(() => screen.getByTestId('foo')).toThrowErrorMatchingInlineSnapshot(` +"A better query is available, try this: +getByRole("button", {name: /submit/i}) + + + + +" +`) +}) + +test('should suggest getAllByRole when used with getAllByTestId', () => { + renderIntoDocument(` + + `) + + expect(() => screen.getAllByTestId('foo')) + .toThrowErrorMatchingInlineSnapshot(` +"A better query is available, try this: +getAllByRole("button", {name: /submit/i}) + + + + + + + + + +" +`) +}) +test('should suggest findByRole when used with findByTestId', async () => { + renderIntoDocument(` + + + `) + + await expect(screen.findByTestId('foo')).rejects.toThrowError( + /findByRole\("button", \{name: \/submit\/i\}\)/, + ) + await expect(screen.findAllByTestId(/foo/)).rejects.toThrowError( + /findAllByRole\("button", \{name: \/submit\/i\}\)/, + ) +}) + +test('should suggest img role w/ alt text', () => { + renderIntoDocument(`Incredibles 2 Poster`) + + expect(() => screen.getByAltText('Incredibles 2 Poster')).toThrowError( + /getByRole\("img", \{name: \/incredibles 2 poster\/i\}\)/, + ) +}) + +test('should suggest getByLabelText when no role available', () => { + renderIntoDocument( + ``, + ) + expect(() => screen.getByTestId('foo')).toThrowError( + /getByLabelText\("Username"\)/, + ) +}) + +test(`should suggest getByLabel on non form elements`, () => { + renderIntoDocument(` +
+ Section One +

some content

+
+ `) + + expect(() => screen.getByTestId('foo')).toThrowError( + /getByLabelText\("Section One"\)/, + ) +}) + +test.each([ + ``, + ``, + ``, +])('%s\nshould suggest getByRole over', async html => { + renderIntoDocument(html) + + expect(() => screen.getByLabelText('Username')).toThrowError( + /getByRole\("textbox", \{name: \/username\/i\}\)/, + ) + expect(() => screen.getAllByLabelText('Username')).toThrowError( + /getAllByRole\("textbox", \{name: \/username\/i\}\)/, + ) + + expect(() => screen.queryByLabelText('Username')).toThrowError( + /queryByRole\("textbox", \{name: \/username\/i\}\)/, + ) + expect(() => screen.queryAllByLabelText('Username')).toThrowError( + /queryAllByRole\("textbox", \{name: \/username\/i\}\)/, + ) + + await expect(screen.findByLabelText('Username')).rejects.toThrowError( + /findByRole\("textbox", \{name: \/username\/i\}\)/, + ) + await expect(screen.findAllByLabelText(/Username/)).rejects.toThrowError( + /findAllByRole\("textbox", \{name: \/username\/i\}\)/, + ) +}) + +test(`should suggest label over placeholder text`, () => { + renderIntoDocument( + ``, + ) + + expect(() => screen.getByPlaceholderText('Username')).toThrowError( + /getByLabelText\("Username"\)/, + ) +}) + +test(`should suggest getByPlaceholderText`, () => { + renderIntoDocument(``) + + expect(() => screen.getByTestId('foo')).toThrowError( + /getByPlaceholderText\("Username"\)/, + ) +}) + +test(`should suggest getByText for simple elements`, () => { + renderIntoDocument(`
hello there
`) + + expect(() => screen.getByTestId('foo')).toThrowError( + /getByText\("hello there"\)/, + ) +}) + +test(`should suggest getByDisplayValue`, () => { + renderIntoDocument(``) + + document.getElementById('lastName').value = 'Prine' // RIP John Prine + + expect(() => screen.getByTestId('lastName')).toThrowError( + /getByDisplayValue\("Prine"\)/, + ) +}) + +test(`should suggest getByAltText`, () => { + renderIntoDocument(` + + + Computer + + `) + + expect(() => screen.getByTestId('input')).toThrowError( + /getByAltText\("last name"\)/, + ) + expect(() => screen.getByTestId('area')).toThrowError( + /getByAltText\("Computer"\)/, + ) +}) + +test(`should suggest getByTitle`, () => { + renderIntoDocument(` + + + Close + + `) + + expect(() => screen.getByTestId('delete')).toThrowError( + /getByTitle\("Delete"\)/, + ) + expect(() => screen.getAllByTestId('delete')).toThrowError( + /getAllByTitle\("Delete"\)/, + ) + expect(() => screen.queryByTestId('delete')).toThrowError( + /queryByTitle\("Delete"\)/, + ) + expect(() => screen.queryAllByTestId('delete')).toThrowError( + /queryAllByTitle\("Delete"\)/, + ) + expect(() => screen.queryAllByTestId('delete')).toThrowError( + /queryAllByTitle\("Delete"\)/, + ) + expect(() => screen.queryAllByTestId('delete')).toThrowError( + /queryAllByTitle\("Delete"\)/, + ) + + // Since `ByTitle` and `ByText` will both return the element + // `getByText` will always be the suggested query as it is higher up the list. + expect(() => screen.getByTestId('svg')).toThrowError(/getByText\("Close"\)/) +}) diff --git a/src/config.js b/src/config.js index c93ad92e..bce4d959 100644 --- a/src/config.js +++ b/src/config.js @@ -16,9 +16,12 @@ let config = { asyncWrapper: cb => cb(), // default value for the `hidden` option in `ByRole` queries defaultHidden: false, - //showOriginalStackTrace flag to show the full error stack traces for async errors + // showOriginalStackTrace flag to show the full error stack traces for async errors showOriginalStackTrace: false, + // throw errors w/ suggestions for better queries. Opt in so off by default. + throwSuggestions: false, + // called when getBy* queries fail. (message, container) => Error getElementError(message, container) { const error = new Error( diff --git a/src/index.js b/src/index.js index aff9388d..e5888b99 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,7 @@ export * from './query-helpers' export {getRoles, logRoles, isInaccessible} from './role-helpers' export * from './pretty-dom' export {configure} from './config' +export * from './suggestions' export { // "within" reads better in user-code diff --git a/src/queries/alt-text.js b/src/queries/alt-text.js index debb2b33..aebda550 100644 --- a/src/queries/alt-text.js +++ b/src/queries/alt-text.js @@ -1,4 +1,5 @@ import {matches, fuzzyMatches, makeNormalizer, buildQueries} from './all-utils' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByAltText( container, @@ -16,6 +17,12 @@ const getMultipleError = (c, alt) => `Found multiple elements with the alt text: ${alt}` const getMissingError = (c, alt) => `Unable to find an element with the alt text: ${alt}` + +const queryAllByAltTextWithSuggestions = wrapAllByQueryWithSuggestion( + queryAllByAltText, + queryAllByAltText.name, + 'queryAll', +) const [ queryByAltText, getAllByAltText, @@ -26,7 +33,7 @@ const [ export { queryByAltText, - queryAllByAltText, + queryAllByAltTextWithSuggestions as queryAllByAltText, getByAltText, getAllByAltText, findAllByAltText, diff --git a/src/queries/display-value.js b/src/queries/display-value.js index d53df31a..a90966d5 100644 --- a/src/queries/display-value.js +++ b/src/queries/display-value.js @@ -5,6 +5,7 @@ import { makeNormalizer, buildQueries, } from './all-utils' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByDisplayValue( container, @@ -33,6 +34,13 @@ const getMultipleError = (c, value) => `Found multiple elements with the display value: ${value}.` const getMissingError = (c, value) => `Unable to find an element with the display value: ${value}.` + +const queryAllByDisplayValueWithSuggestions = wrapAllByQueryWithSuggestion( + queryAllByDisplayValue, + queryAllByDisplayValue.name, + 'queryAll', +) + const [ queryByDisplayValue, getAllByDisplayValue, @@ -43,7 +51,7 @@ const [ export { queryByDisplayValue, - queryAllByDisplayValue, + queryAllByDisplayValueWithSuggestions as queryAllByDisplayValue, getByDisplayValue, getAllByDisplayValue, findAllByDisplayValue, diff --git a/src/queries/label-text.js b/src/queries/label-text.js index 94eeed79..9e900025 100644 --- a/src/queries/label-text.js +++ b/src/queries/label-text.js @@ -6,6 +6,8 @@ import { queryAllByAttribute, makeFindQuery, makeSingleQuery, + wrapAllByQueryWithSuggestion, + wrapSingleQueryWithSuggestion, } from './all-utils' import {queryAllByText} from './text' @@ -117,7 +119,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. -function getAllByLabelText(container, text, ...rest) { +const getAllByLabelText = (container, text, ...rest) => { const els = queryAllByLabelText(container, text, ...rest) if (!els.length) { const labels = queryAllLabelsByText(container, text, ...rest) @@ -139,17 +141,45 @@ function getAllByLabelText(container, text, ...rest) { // the reason mentioned above is the same reason we're not using buildQueries const getMultipleError = (c, text) => `Found multiple elements with the text of: ${text}` -const queryByLabelText = makeSingleQuery(queryAllByLabelText, getMultipleError) +const queryByLabelText = wrapSingleQueryWithSuggestion( + makeSingleQuery(queryAllByLabelText, getMultipleError), + queryAllByLabelText.name, + 'query', +) const getByLabelText = makeSingleQuery(getAllByLabelText, getMultipleError) -const findAllByLabelText = makeFindQuery(getAllByLabelText) -const findByLabelText = makeFindQuery(getByLabelText) +const findAllByLabelText = makeFindQuery( + wrapAllByQueryWithSuggestion( + getAllByLabelText, + getAllByLabelText.name, + 'findAll', + ), +) +const findByLabelText = makeFindQuery( + wrapSingleQueryWithSuggestion(getByLabelText, getByLabelText.name, 'find'), +) + +const getAllByLabelTextWithSuggestions = wrapAllByQueryWithSuggestion( + getAllByLabelText, + getAllByLabelText.name, + 'getAll', +) +const getByLabelTextWithSuggestions = wrapSingleQueryWithSuggestion( + getByLabelText, + getAllByLabelText.name, + 'get', +) -export { +const queryAllByLabelTextWithSuggestions = wrapAllByQueryWithSuggestion( queryAllByLabelText, + queryAllByLabelText.name, + 'queryAll', +) +export { + queryAllByLabelTextWithSuggestions as queryAllByLabelText, queryByLabelText, - getAllByLabelText, - getByLabelText, + getAllByLabelTextWithSuggestions as getAllByLabelText, + getByLabelTextWithSuggestions as getByLabelText, findAllByLabelText, findByLabelText, } diff --git a/src/queries/placeholder-text.js b/src/queries/placeholder-text.js index 8e58ab5f..7f2bc62b 100644 --- a/src/queries/placeholder-text.js +++ b/src/queries/placeholder-text.js @@ -1,12 +1,20 @@ import {queryAllByAttribute, buildQueries} from './all-utils' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' -const queryAllByPlaceholderText = queryAllByAttribute.bind(null, 'placeholder') - +function queryAllByPlaceholderText(...args) { + return queryAllByAttribute('placeholder', ...args) +} const getMultipleError = (c, text) => `Found multiple elements with the placeholder text of: ${text}` const getMissingError = (c, text) => `Unable to find an element with the placeholder text of: ${text}` +const queryAllByPlaceholderTextWithSuggestions = wrapAllByQueryWithSuggestion( + queryAllByPlaceholderText, + queryAllByPlaceholderText.name, + 'queryAll', +) + const [ queryByPlaceholderText, getAllByPlaceholderText, @@ -17,7 +25,7 @@ const [ export { queryByPlaceholderText, - queryAllByPlaceholderText, + queryAllByPlaceholderTextWithSuggestions as queryAllByPlaceholderText, getByPlaceholderText, getAllByPlaceholderText, findAllByPlaceholderText, diff --git a/src/queries/role.js b/src/queries/role.js index f7eeff18..54aec533 100644 --- a/src/queries/role.js +++ b/src/queries/role.js @@ -14,6 +14,7 @@ import { makeNormalizer, matches, } from './all-utils' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByRole( container, @@ -158,7 +159,11 @@ Unable to find an ${ ${roleMessage}`.trim() } - +const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion( + queryAllByRole, + queryAllByRole.name, + 'queryAll', +) const [ queryByRole, getAllByRole, @@ -169,7 +174,7 @@ const [ export { queryByRole, - queryAllByRole, + queryAllByRoleWithSuggestions as queryAllByRole, getAllByRole, getByRole, findAllByRole, diff --git a/src/queries/test-id.js b/src/queries/test-id.js index 6d36c164..5f5047da 100644 --- a/src/queries/test-id.js +++ b/src/queries/test-id.js @@ -1,15 +1,23 @@ import {queryAllByAttribute, getConfig, buildQueries} from './all-utils' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' const getTestIdAttribute = () => getConfig().testIdAttribute -const queryAllByTestId = (...args) => - queryAllByAttribute(getTestIdAttribute(), ...args) +function queryAllByTestId(...args) { + return queryAllByAttribute(getTestIdAttribute(), ...args) +} const getMultipleError = (c, id) => `Found multiple elements by: [${getTestIdAttribute()}="${id}"]` const getMissingError = (c, id) => `Unable to find an element by: [${getTestIdAttribute()}="${id}"]` +const queryAllByTestIdWithSuggestions = wrapAllByQueryWithSuggestion( + queryAllByTestId, + queryAllByTestId.name, + 'queryAll', +) + const [ queryByTestId, getAllByTestId, @@ -20,7 +28,7 @@ const [ export { queryByTestId, - queryAllByTestId, + queryAllByTestIdWithSuggestions as queryAllByTestId, getByTestId, getAllByTestId, findAllByTestId, diff --git a/src/queries/text.js b/src/queries/text.js index 18cf9cb7..aa7419d9 100644 --- a/src/queries/text.js +++ b/src/queries/text.js @@ -5,6 +5,7 @@ import { getNodeText, buildQueries, } from './all-utils' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByText( container, @@ -34,6 +35,12 @@ const getMultipleError = (c, text) => const getMissingError = (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( + queryAllByText, + queryAllByText.name, + 'queryAll', +) + const [ queryByText, getAllByText, @@ -44,7 +51,7 @@ const [ export { queryByText, - queryAllByText, + queryAllByTextWithSuggestions as queryAllByText, getByText, getAllByText, findAllByText, diff --git a/src/queries/title.js b/src/queries/title.js index c696572f..93c2cf5b 100644 --- a/src/queries/title.js +++ b/src/queries/title.js @@ -5,6 +5,7 @@ import { getNodeText, buildQueries, } from './all-utils' +import {wrapAllByQueryWithSuggestion} from '../query-helpers' function queryAllByTitle( container, @@ -25,6 +26,12 @@ const getMultipleError = (c, title) => const getMissingError = (c, title) => `Unable to find an element with the title: ${title}.` +const queryAllByTitleWithSuggestions = wrapAllByQueryWithSuggestion( + queryAllByTitle, + queryAllByTitle.name, + 'queryAll', +) + const [ queryByTitle, getAllByTitle, @@ -35,7 +42,7 @@ const [ export { queryByTitle, - queryAllByTitle, + queryAllByTitleWithSuggestions as queryAllByTitle, getByTitle, getAllByTitle, findAllByTitle, diff --git a/src/query-helpers.js b/src/query-helpers.js index 7fcc6bd9..0b7793f0 100644 --- a/src/query-helpers.js +++ b/src/query-helpers.js @@ -1,3 +1,4 @@ +import {getSuggestedQuery} from './suggestions' import {fuzzyMatches, matches, makeNormalizer} from './matches' import {waitFor} from './wait-for' import {getConfig} from './config' @@ -53,6 +54,15 @@ function makeSingleQuery(allQuery, getMultipleError) { } } +function getSuggestionError(suggestion, container) { + return getConfig().getElementError( + `A better query is available, try this: +${suggestion.toString()} +`, + container, + ) +} + // this accepts a query function and returns a function which throws an error // if an empty list of elements is returned function makeGetAllQuery(allQuery, getMissingError) { @@ -64,6 +74,7 @@ function makeGetAllQuery(allQuery, getMissingError) { container, ) } + return els } } @@ -72,21 +83,95 @@ function makeGetAllQuery(allQuery, getMissingError) { // waitFor and passing a function which invokes the getter. function makeFindQuery(getter) { return (container, text, options, waitForOptions) => - waitFor(() => getter(container, text, options), waitForOptions) + waitFor(() => { + return getter(container, text, options) + }, waitForOptions) +} + +const wrapSingleQueryWithSuggestion = (query, queryAllByName, variant) => ( + container, + ...args +) => { + const element = query(container, ...args) + const [{suggest = getConfig().throwSuggestions} = {}] = args.slice(-1) + if (suggest) { + const suggestion = getSuggestedQuery(element, variant) + if (suggestion && !queryAllByName.endsWith(suggestion.queryName)) { + throw getSuggestionError(suggestion.toString(), container) + } + } + + return element +} + +const wrapAllByQueryWithSuggestion = (query, queryAllByName, variant) => ( + container, + ...args +) => { + const els = query(container, ...args) + + const [{suggest = getConfig().throwSuggestions} = {}] = args.slice(-1) + if (suggest) { + // get a unique list of all suggestion messages. We are only going to make a suggestion if + // all the suggestions are the same + const uniqueSuggestionMessages = [ + ...new Set( + els.map(element => getSuggestedQuery(element, variant)?.toString()), + ), + ] + + if ( + // only want to suggest if all the els have the same suggestion. + uniqueSuggestionMessages.length === 1 && + !queryAllByName.endsWith(getSuggestedQuery(els[0], variant).queryName) + ) { + throw getSuggestionError(uniqueSuggestionMessages[0], container) + } + } + + return els } function buildQueries(queryAllBy, getMultipleError, getMissingError) { - const queryBy = makeSingleQuery(queryAllBy, getMultipleError) + const queryBy = wrapSingleQueryWithSuggestion( + makeSingleQuery(queryAllBy, getMultipleError), + queryAllBy.name, + 'query', + ) const getAllBy = makeGetAllQuery(queryAllBy, getMissingError) + const getBy = makeSingleQuery(getAllBy, getMultipleError) - const findAllBy = makeFindQuery(getAllBy) - const findBy = makeFindQuery(getBy) + const getByWithSuggestions = wrapSingleQueryWithSuggestion( + getBy, + queryAllBy.name, + 'get', + ) + const getAllWithSuggestions = wrapAllByQueryWithSuggestion( + getAllBy, + queryAllBy.name.replace('query', 'get'), + 'getAll', + ) + + const findAllBy = makeFindQuery( + wrapAllByQueryWithSuggestion(getAllBy, queryAllBy.name, 'findAll'), + ) + const findBy = makeFindQuery( + wrapSingleQueryWithSuggestion(getBy, queryAllBy.name, 'find'), + ) - return [queryBy, getAllBy, getBy, findAllBy, findBy] + return [ + queryBy, + getAllWithSuggestions, + getByWithSuggestions, + findAllBy, + findBy, + ] } export { getElementError, + wrapAllByQueryWithSuggestion, + wrapSingleQueryWithSuggestion, getMultipleElementsFoundError, queryAllByAttribute, queryByAttribute, diff --git a/src/suggestions.js b/src/suggestions.js new file mode 100644 index 00000000..7d109bd3 --- /dev/null +++ b/src/suggestions.js @@ -0,0 +1,81 @@ +import {computeAccessibleName} from 'dom-accessibility-api' +import {getRoles} from './role-helpers' +import {getDefaultNormalizer} from './matches' + +const normalize = getDefaultNormalizer() + +function getLabelTextFor(element) { + let label = + element.labels && + Array.from(element.labels).find(el => Boolean(normalize(el.textContent))) + + // non form elements that are using aria-labelledby won't be included in `element.labels` + if (!label) { + const ariaLabelledBy = element.getAttribute('aria-labelledby') + if (ariaLabelledBy) { + // we're using this notation because with the # selector we would have to escape special characters e.g. user.name + // see https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Escaping_special_characters + label = document.querySelector(`[id=${ariaLabelledBy}]`) + } + } + + if (label) { + return label.textContent + } + return undefined +} + +function makeSuggestion(queryName, content, {variant, name}) { + return { + queryName, + toString() { + const options = name ? `, {name: /${name.toLowerCase()}/i}` : '' + return `${variant}By${queryName}("${content}"${options})` + }, + } +} + +export function getSuggestedQuery(element, variant) { + const roles = getRoles(element) + + const roleNames = Object.keys(roles) + if (roleNames.length) { + const [role] = roleNames + return makeSuggestion('Role', role, { + variant, + name: computeAccessibleName(element), + }) + } + + const labelText = getLabelTextFor(element) + if (labelText) { + return makeSuggestion('LabelText', labelText, {variant}) + } + + const placeholderText = element.getAttribute('placeholder') + if (placeholderText) { + return makeSuggestion('PlaceholderText', placeholderText, {variant}) + } + + const textContent = normalize(element.textContent) + if (textContent) { + return makeSuggestion('Text', textContent, {variant}) + } + + if (element.value) { + return makeSuggestion('DisplayValue', normalize(element.value), {variant}) + } + + const alt = element.getAttribute('alt') + if (alt) { + return makeSuggestion('AltText', alt, {variant}) + } + + const title = element.getAttribute('title') + + if (title) { + return makeSuggestion('Title', title, {variant}) + } + + return undefined +} diff --git a/src/wait-for.js b/src/wait-for.js index ea99eae6..b271a00d 100644 --- a/src/wait-for.js +++ b/src/wait-for.js @@ -51,6 +51,7 @@ function waitFor( clearTimeout(overallTimeoutTimer) clearInterval(intervalId) setImmediate(() => observer.disconnect()) + if (error) { reject(error) } else { diff --git a/types/index.d.ts b/types/index.d.ts index 406db91c..5b199dcf 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -22,3 +22,4 @@ export * from './get-queries-for-element'; export * from './pretty-dom'; export * from './role-helpers'; export * from './config'; +export * from './suggestions'; diff --git a/types/matches.d.ts b/types/matches.d.ts index 0b8dad4d..6454c86a 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -1,29 +1,33 @@ -export type MatcherFunction = (content: string, element: HTMLElement) => boolean; -export type Matcher = string | RegExp | MatcherFunction; +export type MatcherFunction = (content: string, element: HTMLElement) => boolean +export type Matcher = string | RegExp | MatcherFunction -export type NormalizerFn = (text: string) => string; +export type NormalizerFn = (text: string) => string export interface MatcherOptions { - exact?: boolean; - /** Use normalizer with getDefaultNormalizer instead */ - trim?: boolean; - /** Use normalizer with getDefaultNormalizer instead */ - collapseWhitespace?: boolean; - normalizer?: NormalizerFn; + exact?: boolean + /** Use normalizer with getDefaultNormalizer instead */ + trim?: boolean + /** Use normalizer with getDefaultNormalizer instead */ + collapseWhitespace?: boolean + normalizer?: NormalizerFn + /** suppress suggestions for a specific query */ + suggest?: boolean } export type Match = ( - textToMatch: string, - node: HTMLElement | null, - matcher: Matcher, - options?: MatcherOptions, -) => boolean; + textToMatch: string, + node: HTMLElement | null, + matcher: Matcher, + options?: MatcherOptions, +) => boolean export interface DefaultNormalizerOptions { - trim?: boolean; - collapseWhitespace?: boolean; + trim?: boolean + collapseWhitespace?: boolean } -export function getDefaultNormalizer(options?: DefaultNormalizerOptions): NormalizerFn; +export function getDefaultNormalizer( + options?: DefaultNormalizerOptions, +): NormalizerFn // N.B. Don't expose fuzzyMatches + matches here: they're not public API diff --git a/types/suggestions.d.ts b/types/suggestions.d.ts new file mode 100644 index 00000000..f574f344 --- /dev/null +++ b/types/suggestions.d.ts @@ -0,0 +1,6 @@ +export interface Suggestion { + queryName: string + toString(): string +} + +export function getSuggestedQuery(element: HTMLElement): Suggestion | undefined