diff --git a/.eslintrc.js b/.eslintrc.js index ca50012..65e8203 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -28,5 +28,12 @@ module.exports = { 'jest/no-done-callback': 'off', }, }, + { + files: ['lib/fixture/**/*.+(js|ts)'], + rules: { + 'no-empty-pattern': 'off', + 'no-underscore-dangle': ['error', {allow: ['__testingLibraryReviver']}], + }, + }, ], } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3eac2fe..6050de1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,12 +13,16 @@ on: jobs: build: - name: Build + Test + Release / Node ${{ matrix.node }} + name: Build + Test + Release / Node ${{ matrix.node }} / playwright@${{ matrix.playwright }} runs-on: ubuntu-latest strategy: matrix: node: ['12', '14', '16'] - playwright: ['1.12.0', 'latest'] + # TODO: technically we still support down to 1.12 but `locator.waitFor` + # was introduced in 1.16 so anything earlier blows up type-checking. + # This minimum will be bumped in the next breaking release that will be + # entirely built around the `Locator` APIs, so update this then. + playwright: ['1.16.0', 'latest'] steps: - name: Checkout @@ -32,7 +36,7 @@ jobs: - name: Update to npm 7 run: npm i -g npm@7 --registry=https://registry.npmjs.org - - uses: actions/cache@v3.0.1 + - uses: actions/cache@v3.0.8 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} @@ -47,11 +51,22 @@ jobs: npm install playwright@${{ matrix.playwright }} npm install @playwright/test@${{ matrix.playwright }} - - name: Check types, run lint + tests + - name: Check types + run: npm run test:types + + - name: Run lint + tests + if: ${{ matrix.playwright == 'latest' }} + run: | + npm why playwright + npm why @playwright/test + npm run test + + - name: Run lint + tests + if: ${{ matrix.playwright != 'latest' }} run: | npm why playwright npm why @playwright/test - npm run validate + npm run test:legacy # Only release on Node 14 diff --git a/.node-version b/.node-version index 31102b2..2a4e4ab 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14.18.1 +16.17.0 diff --git a/README.md b/README.md index 50d6adf..e76a97f 100644 --- a/README.md +++ b/README.md @@ -22,103 +22,271 @@
-## ✨ Features +## 🎛 Features -All of your favorite user-centric querying functions from **@testing-library/react** and **@testing-library/dom** available from Playwright! +All of your favorite user-centric querying functions from **@testing-library/react** and **@testing-library/dom** available from within Playwright! -- Playwright Test [fixture](https://playwright.dev/docs/test-fixtures) — **`@playwright-testing-library/test/fixture`** or... -- Standalone queries — **`playwright-testing-library`**/**`@playwright-testing-library/test`** -- Asynchronous assertion helper (via **[wait-for-expect](https://github.com/TheBrainFamily/wait-for-expect)**) +- Playwright Test [fixture](https://playwright.dev/docs/test-fixtures) for **@playwright/test** via **@playwright-testing-library/test** + - ✨ **New** — `Locator` queries fixture (`locatorFixtures`) [↓](#playwright-test-locator-fixture) + - `ElementHandle` queries fixture (`fixtures`) [↓](#legacy-playwright-test-fixture) +- Standalone queries for **playwright** via **playwright-testing-library** + - `ElementHandle` queries (`getDocument` + `queries`) [↓](#standalone-playwright-queries) + - Asynchronous `waitFor` assertion helper (via **[wait-for-expect](https://github.com/TheBrainFamily/wait-for-expect)**) -## 🌱 Getting Started - -### 1. Install +## 🌱 Installation ```bash -# For use with Playwright +# For use with Playwright Test (@playwright/test) +npm install --save-dev @playwright-testing-library/test + +# For use with Playwright (playwright) npm install --save-dev playwright-testing-library +``` -# For use with Playwright Test -npm install --save-dev @playwright-testing-library/test +## 📝 Usage + +There are currently a few different ways to use Playwright Testing Library, depending, however using the `Locator` queries fixture with Playwright Test (**@playwright/test**) is the recommended approach. + +> ⚠️ The `ElementHandle` query APIs were created before Playwright introduced its `Locator` API and will be replaced in the next major version of Playwright Testing Library. If you can't use **@playwright/test** at the moment, you'll need to use the `ElementHandle` query API, but a migration path will be provided when we switch to the new `Locator` APIs. + +### Playwright Test Fixture + +Using the `Locator` Playwright Test (**@playwright/test**) fixture with **@playwright-testing-library/test**. + +#### Setup + +```ts +import {test as base} from '@playwright/test' +import { + locatorFixtures as fixtures, + LocatorFixtures as TestingLibraryFixtures, +} from '@playwright-testing-library/test/fixture' + +const test = base.extend(fixtures) + +const {expect} = test + +test('my form', async ({screen, within}) => { + // Screen provides `Locator` queries scoped to current Playwright `Page` + const formLocator = screen.getByTestId('my-form') + + // Scope queries to `Locator` with `within` + // (note that this is a fixture from `test`, not the `within` import) + const emailInputLocator = within(formLocator).getByLabelText('Email') + + // Interact via `Locator` API 🥳 + await emailInputLocator.fill('email@playwright.dev') + await emailInputLocator.press('Enter') + + // Screen also provides Playwright's `Page` API + screen.goto('/account') + + const emailLocator = screen.getByRole('heading', {level: 2}) + + // Assert via `Locator` APIs 🎉 + await expect(emailLocator).toHaveText('email@playwright.dev') +}) ``` -or +#### Configuration -```bash -# For use with Playwright -yarn add --dev playwright-testing-library +The `Locator` query API is configured using Playwright's `use` API. See Playwright's documentation for [global](https://playwright.dev/docs/api/class-testconfig#test-config-use), [project](https://playwright.dev/docs/api/class-testproject#test-project-use), and [test](https://playwright.dev/docs/api/class-test#test-use). + +##### Global + +Configuring Testing Library globally in `playwright.config.ts` + +```ts +import type {PlaywrightTestConfig} from '@playwright/test' + +const config: PlaywrightTestConfig = { + use: { + // These are the defaults + testIdAttribute: 'data-testid', + asyncUtilTimeout: 1000, + asyncUtilExpectedState: 'visible', + }, +} -# For use with Playwright Test -yarn add --dev @playwright-testing-library/test +export default config ``` -### 2a. Use _Playwright Test [fixture](https://playwright.dev/docs/test-fixtures)_ +##### Local + +Scoping Testing Library configuration to test suites or `describe` blocks ```ts -import {test as baseTest} from '@playwright/test' +import {test as base} from '@playwright/test' +import { + locatorFixtures as fixtures, + LocatorFixtures as TestingLibraryFixtures, +} from '@playwright-testing-library/test/fixture' + +const test = base.extend(fixtures) + +const {describe, expect, use} = test + +// Entire test suite +use({testIdAttribute: 'data-custom-test-id'}) + +describe(() => { + // Specific block + use({ + testIdAttribute: 'some-other-test-id', + asyncUtilsTimeout: 5000, + asyncUtilExpectedState: 'attached', + }) + + test('my form', async ({screen}) => { + // ... + }) +}) +``` + +### Legacy Playwright Test Fixture + +Using the `ElementHandle` Playwright Test (**@playwright/test**) fixture with **@playwright-testing-library/test**. + +> ⚠️ See note in [Usage](#-usage) as you should be using the `Locator` fixture if possible + +#### Setup + +```ts +import {test as base} from '@playwright/test' import {fixtures, within, TestingLibraryFixtures} from '@playwright-testing-library/test/fixture' -// As only fixture -const test = baseTest.extend(fixtures) +const test = base.extend(fixtures) -// Alternatively, with other fixtures -interface Fixtures extends TestingLibraryFixtures { - // ... additional fixture types -} +const {expect} = test + +test('my form', async ({page, queries}) => { + // Query methods are available in `test` blocks + const formHandle = await queries.getByTestId('my-form') + + // Scope queries to an `ElementHandle` with `within` + const emailInputHandle = await within(formHandle).getByLabelText('Email') -const test = baseTest.extend({ - ...fixtures, - // ... additional fixtures + // Interact via `ElementHandle` API + await emailInputHandle.fill('email@playwright.dev') + await emailInputHandle.press('Enter') + + page.goto('/account') + + const emailHandle = queries.getByRole('heading', {level: 2}) + + // Assert via `ElementHandle` APIs + expect(await emailHandle.textContent()).toEqual('email@playwright.dev') }) +``` -const {expect} = test +#### Configuration + +```ts +import {test as base} from '@playwright/test' +import { + configure, + fixtures, + within, + TestingLibraryFixtures, +} from '@playwright-testing-library/test/fixture' -// Query methods are available in `test` blocks -test('my form', async ({queries: {getByTestId}}) => { - const $form = await getByTestId('my-form') +const test = base.extend(fixtures) - // Scope queries with `within` - const {getByLabelText} = within($form) +const {beforeEach, describe, expect} = test - const $email = await getByLabelText('Email') +// Global (these are the defaults) +configure({asyncUtilTimeout: 1000, testIdAttribute: 'data-testid'}) - // Interact with Playwright like usual - await $email.type('playwright@example.com') +// Specific block +describe('my page', () => { + beforeEach(() => configure({asyncUtilTimeout: 5000, testIdAttribute: 'data-custom-test-id'})) - // ... + afterEach(() => configure({})) + + test('my form', async ({page, queries}) => { + // ... + }) }) ``` -### 2b. Use _standalone queries_ +### Standalone Playwright Queries + +Using the `ElementHandle` queries with Playwright (**playwright**) and **playwright-testing-library**. -```js -const {webkit} = require('playwright') // or 'firefox' or 'chromium' -const {getDocument, queries} = require('playwright-testing-library') +> ⚠️ See note in [Usage](#-usage) as you should be using **@playwright/test** with the `Locator` fixture if possible. The `Locator` queries will be made available for standalone **playwright** in the next major release. -const {getByTestId, getByLabelText} = queries +```ts +import {beforeAll, expect, jest, test} from '@jest/globals' +import {webkit} from 'playwright' // or 'firefox' or 'chromium' +import {getDocument, queries, within} from 'playwright-testing-library' + +let browser: playwright.Browser +let page: playwright.Page + +beforeAll(() => { + const browser = await webkit.launch() + const page = await browser.newPage() +}) -const browser = await webkit.launch() -const page = await browser.newPage() +test('my form', () => { + // Get `ElementHandle` for document from `Page` + const documentHandle = await getDocument(page) -// Grab ElementHandle for document -const $document = await getDocument(page) + // Global query methods take document handle as the first parameter + const formHandle = await queries.getByTestId(documentHandle, 'my-form') -// Your favorite query methods are available -const $form = await getByTestId($document, 'my-form') + // Scope queries to an `ElementHandle` with `within` + const emailInputHandle = await within(formHandle).getByLabelText('Email') -// Returned elements are ElementHandles too! -const $email = await getByLabelText($form, 'Email') + // Interact via `ElementHandle` API + await emailInputHandle.fill('email@playwright.dev') + await emailInputHandle.press('Enter') -// Interact with playwright like usual -await $email.type('playwright@example.com') + page.goto('/account') -// ... + const accountHandle = getDocument(page) + const emailHandle = queries.getByRole(accountHandle, 'heading', {level: 2}) + + // Assert via `ElementHandle` APIs + expect(await emailHandle.textContent()).toEqual('email@playwright.dev') +}) +``` + +#### Configuration + +```ts +import {beforeEach, afterEach, expect, jest, test} from '@jest/globals' +import {configure, getDocument, queries, within} from 'playwright-testing-library' + +// Global (these are the defaults) +configure({asyncUtilTimeout: 1000, testIdAttribute: 'data-testid'}) + +// Specific block +describe('my page', () => { + beforeEach(() => configure({asyncUtilTimeout: 5000, testIdAttribute: 'data-custom-test-id'})) + + afterEach(() => configure({})) + + test('my form', async ({page, queries}) => { + // ... + }) +}) ``` ## 🔌 API +### Testing Library + +All queries from **[@testing-library/dom](https://github.com/testing-library/dom-testing-library#usage)** are supported. + +> 📝 The **`find*`** queries for the `Locator` queries return `Promise` which resolves when the element is found before the timeout specified via `asyncUtilTimeout` + +### Additional + Unique methods, not part of **@testing-library/dom** +> ⚠️ These only apply to the `ElementHandle` queries + - Get an `ElementHandle` for the document ```ts @@ -135,72 +303,23 @@ Unique methods, not part of **@testing-library/dom** ): Promise<{}> ``` ---- - -The **[@testing-library/dom](https://github.com/testing-library/dom-testing-library#usage)** — All **`get*`** and **`query*`** methods are supported. - -- `getQueriesForElement(handle: ElementHandle): ElementHandle & QueryUtils` - extend the input object with the query API and return it -- `getNodeText(handle: ElementHandle): Promise` - get the text content of the element -- `queries: QueryUtils` - the query subset of `@testing-library/dom` exports - - `queryByPlaceholderText` - - `queryAllByPlaceholderText` - - `getByPlaceholderText` - - `getAllByPlaceholderText` - - `findByPlaceholderText` - - `findAllByPlaceholderText` - - `queryByText` - - `queryAllByText` - - `getByText` - - `getAllByText` - - `findByText` - - `findAllByText` - - `queryByLabelText` - - `queryAllByLabelText` - - `getByLabelText` - - `getAllByLabelText` - - `findByLabelText` - - `findAllByLabelText` - - `queryByAltText` - - `queryAllByAltText` - - `getByAltText` - - `getAllByAltText` - - `findByAltText` - - `findAllByAltText` - - `queryByTestId` - - `queryAllByTestId` - - `getByTestId` - - `getAllByTestId` - - `findByTestId` - - `findAllByTestId` - - `queryByTitle` - - `queryAllByTitle` - - `getByTitle` - - `getAllByTitle` - - `findByTitle` - - `findAllByTitle` - - `queryByDisplayValue`, - - `queryAllByDisplayValue`, - - `getByDisplayValue`, - - `getAllByDisplayValue`, - - `findByDisplayValue`, - - `findAllByDisplayValue`, - ## Known Limitations -- Async utilities `waitForElement`, `waitForElementToBeRemoved` and `waitForDomChange` are not exposed. Consider using a `find*` query. -- `fireEvent` method is not exposed, use Playwright's built-ins instead. -- `expect` assertion extensions are not available. +- Only `testIdAttribute` and `asyncUtilTimeout` are supported as configuration options +- Async utilities `waitForElement`, `waitForElementToBeRemoved` and `waitForDomChange` are not exposed. Consider using a `find*` query or a Playwright built-in like [`Locator.waitFor()`](https://playwright.dev/docs/api/class-locator#locator-wait-for). +- The `fireEvent` method is not exposed, use Playwright's built-ins instead. +- Assertion extensions from [**jest-dom**](https://testing-library.com/docs/ecosystem-jest-dom/) are not compatible, use Playwright Test if possible. +- The [`getNodeText()`](https://testing-library.com/docs/dom-testing-library/api-custom-queries/#getnodetext) function is not currently supported for `Locator`. ## Special Thanks - [pptr-testing-library](https://github.com/testing-library/pptr-testing-library) -- [@testing-library/dom](https://github.com/testing-library/dom-testing-library) of course! +- [@testing-library/dom](https://github.com/testing-library/dom-testing-library) ## Related Playwright Test Utilities - [jest-playwright](https://github.com/playwright-community/jest-playwright) - [expect-playwright](https://github.com/playwright-community/expect-playwright) -- Yours! Name TBD, PR welcome ;) ## LICENSE diff --git a/lib/common.ts b/lib/common.ts index e515a5a..09bcf7c 100644 --- a/lib/common.ts +++ b/lib/common.ts @@ -1,6 +1,24 @@ -import {Queries} from './typedefs' +import {Config as TestingLibraryConfig, queries} from '@testing-library/dom' -export const queryNames: Array = [ +export type Config = Pick + +export const configureTestingLibraryScript = ( + script: string, + {testIdAttribute, asyncUtilTimeout}: Partial, +) => { + const withTestId = testIdAttribute + ? script.replace( + /testIdAttribute: (['|"])data-testid(['|"])/g, + `testIdAttribute: $1${testIdAttribute}$2`, + ) + : script + + return asyncUtilTimeout + ? withTestId.replace(/asyncUtilTimeout: \d+/g, `asyncUtilTimeout: ${asyncUtilTimeout}`) + : withTestId +} + +export const queryNames: Array = [ 'queryByPlaceholderText', 'queryAllByPlaceholderText', 'getByPlaceholderText', diff --git a/lib/fixture.ts b/lib/fixture/element-handle.ts similarity index 50% rename from lib/fixture.ts rename to lib/fixture/element-handle.ts index c3f6978..7bc12c1 100644 --- a/lib/fixture.ts +++ b/lib/fixture/element-handle.ts @@ -1,15 +1,10 @@ import type {PlaywrightTestArgs, TestFixture} from '@playwright/test' -import {queryNames} from './common' -import type {FixtureQueries as Queries} from './typedefs' +import {getDocument, queries as unscopedQueries} from '..' +import {queryNames} from '../common' +import type {FixtureQueries as Queries} from '../typedefs' -import {getDocument, queries as unscopedQueries} from '.' - -interface TestingLibraryFixtures { - queries: Queries -} - -const fixture: TestFixture = async ({page}, use) => { +const queriesFixture: TestFixture = async ({page}, use) => { const queries = {} as Queries queryNames.forEach(name => { @@ -27,8 +22,5 @@ const fixture: TestFixture = async ({page}, use) => await use(queries) } -const fixtures = {queries: fixture} - -export {configure} from '.' -export {fixture, fixtures} -export type {Queries, TestingLibraryFixtures} +export {queriesFixture} +export type {Queries} diff --git a/lib/fixture/helpers.ts b/lib/fixture/helpers.ts new file mode 100644 index 0000000..cd4fef4 --- /dev/null +++ b/lib/fixture/helpers.ts @@ -0,0 +1,17 @@ +const replacer = (_: string, value: unknown) => { + if (value instanceof RegExp) return `__REGEXP ${value.toString()}` + + return value +} + +const reviver = (_: string, value: string) => { + if (value.toString().includes('__REGEXP ')) { + const match = /\/(.*)\/(.*)?/.exec(value.split('__REGEXP ')[1]) + + return new RegExp(match![1], match![2] || '') + } + + return value +} + +export {replacer, reviver} diff --git a/lib/fixture/index.ts b/lib/fixture/index.ts new file mode 100644 index 0000000..e5e54fd --- /dev/null +++ b/lib/fixture/index.ts @@ -0,0 +1,49 @@ +import {Fixtures} from '@playwright/test' + +import type {Queries as ElementHandleQueries} from './element-handle' +import {queriesFixture as elementHandleQueriesFixture} from './element-handle' +import { + Queries as LocatorQueries, + installTestingLibraryFixture, + queriesFixture as locatorQueriesFixture, + options, + queriesFor, + registerSelectorsFixture, + screenFixture, + withinFixture, +} from './locator' +import type {Config, Screen} from './types' +import {Within} from './types' + +const elementHandleFixtures: Fixtures = {queries: elementHandleQueriesFixture} +const locatorFixtures: Fixtures = { + queries: locatorQueriesFixture, + screen: screenFixture, + within: withinFixture, + registerSelectors: registerSelectorsFixture, + installTestingLibrary: installTestingLibraryFixture, + ...options, +} + +interface ElementHandleFixtures { + queries: ElementHandleQueries +} + +interface LocatorFixtures extends Partial { + queries: LocatorQueries + screen: Screen + within: Within + registerSelectors: void + installTestingLibrary: void +} + +export {configure} from '..' + +export type {ElementHandleFixtures as TestingLibraryFixtures, LocatorFixtures} +export { + locatorFixtures, + locatorQueriesFixture, + elementHandleQueriesFixture as fixture, + elementHandleFixtures as fixtures, + queriesFor, +} diff --git a/lib/fixture/locator/fixtures.ts b/lib/fixture/locator/fixtures.ts new file mode 100644 index 0000000..701d055 --- /dev/null +++ b/lib/fixture/locator/fixtures.ts @@ -0,0 +1,138 @@ +import type {Locator, PlaywrightTestArgs, TestFixture} from '@playwright/test' +import {Page, selectors} from '@playwright/test' + +import type { + Config, + LocatorQueries as Queries, + Screen, + SelectorEngine, + SynchronousQuery, + Within, + WithinReturn, +} from '../types' + +import {buildTestingLibraryScript, queryToSelector} from './helpers' +import {isAllQuery, queriesFor, screenFor, synchronousQueryNames} from './queries' + +type TestArguments = PlaywrightTestArgs & Config + +const defaultConfig: Config = { + asyncUtilExpectedState: 'visible', + asyncUtilTimeout: 1000, + testIdAttribute: 'data-testid', +} + +const options = Object.fromEntries( + Object.entries(defaultConfig).map(([key, value]) => [key, [value, {option: true}] as const]), +) + +const queriesFixture: TestFixture = async ( + {page, asyncUtilExpectedState, asyncUtilTimeout}, + use, +) => use(queriesFor(page, {asyncUtilExpectedState, asyncUtilTimeout})) + +const screenFixture: TestFixture = async ( + {page, asyncUtilExpectedState, asyncUtilTimeout}, + use, +) => { + const {proxy, revoke} = screenFor(page, {asyncUtilExpectedState, asyncUtilTimeout}) + + await use(proxy) + + revoke() +} + +const withinFixture: TestFixture = async ( + {asyncUtilExpectedState, asyncUtilTimeout}, + use, +) => + use((root: Root) => + 'goto' in root + ? screenFor(root, {asyncUtilExpectedState, asyncUtilTimeout}).proxy + : (queriesFor(root, {asyncUtilExpectedState, asyncUtilTimeout}) as WithinReturn), + ) + +declare const queryName: SynchronousQuery + +const engine: () => SelectorEngine = () => ({ + query(root, selector) { + const args = JSON.parse(selector, window.__testingLibraryReviver) as unknown as Parameters< + Queries[typeof queryName] + > + + if (isAllQuery(queryName)) + throw new Error( + `PlaywrightTestingLibrary: the plural '${queryName}' was used to create this Locator`, + ) + + // @ts-expect-error + const result = window.TestingLibraryDom[queryName](root, ...args) + + return result + }, + queryAll(root, selector) { + const testingLibrary = window.TestingLibraryDom + const args = JSON.parse(selector, window.__testingLibraryReviver) as unknown as Parameters< + Queries[typeof queryName] + > + + // @ts-expect-error + const result = testingLibrary[queryName](root, ...args) + + if (!result) return [] + + return Array.isArray(result) ? result : [result] + }, +}) + +const registerSelectorsFixture: [ + TestFixture, + {scope: 'worker'; auto?: boolean}, +] = [ + async ({}, use) => { + try { + await Promise.all( + synchronousQueryNames.map(async name => + selectors.register( + queryToSelector(name), + `(${engine.toString().replace(/queryName/g, `"${name}"`)})()`, + ), + ), + ) + } catch (error) { + // eslint-disable-next-line no-console + console.error( + 'PlaywrightTestingLibrary: failed to register Testing Library functions\n', + error, + ) + } + await use() + }, + {scope: 'worker', auto: true}, +] + +const installTestingLibraryFixture: [ + TestFixture, + {scope: 'test'; auto?: boolean}, +] = [ + async ({context, asyncUtilExpectedState, asyncUtilTimeout, testIdAttribute}, use) => { + await context.addInitScript( + await buildTestingLibraryScript({ + config: {asyncUtilExpectedState, asyncUtilTimeout, testIdAttribute}, + }), + ) + + await use() + }, + {scope: 'test', auto: true}, +] + +export { + installTestingLibraryFixture, + options, + queriesFixture, + registerSelectorsFixture, + screenFixture, + withinFixture, +} +export type {Queries} diff --git a/lib/fixture/locator/helpers.ts b/lib/fixture/locator/helpers.ts new file mode 100644 index 0000000..5e4914a --- /dev/null +++ b/lib/fixture/locator/helpers.ts @@ -0,0 +1,50 @@ +import {promises as fs} from 'fs' + +import {configureTestingLibraryScript} from '../../common' +import {reviver} from '../helpers' +import type {Config, Selector, SynchronousQuery} from '../types' + +const queryToSelector = (query: SynchronousQuery) => + query.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() as Selector + +const buildTestingLibraryScript = async ({config}: {config: Config}) => { + const testingLibraryDom = await fs.readFile( + require.resolve('@testing-library/dom/dist/@testing-library/dom.umd.js'), + 'utf8', + ) + + const configuredTestingLibraryDom = configureTestingLibraryScript(testingLibraryDom, config) + + return ` + ${configuredTestingLibraryDom} + + window.__testingLibraryReviver = ${reviver.toString()}; + ` +} + +/** + * Alternative version of `Array.prototype.includes` that allows testing for + * the existence of an item with a type that is a _superset_ of the type of the + * items in the array. + * + * This allows us to use it to check whether an item of type `string` exists in + * an array of string literals (e.g: `['foo', 'bar'] as const`) without TypeScript + * complaining. It will, however, throw a compiler error if you try to pass an item + * of type `number`. + * + * @example + * const things = ['foo', 'bar'] as const; + * + * // error + * const hasThing = (t: string) => things.includes(t); + * + * // compiles + * const hasThing = (t: string) => includes(things, t); + * + * @param array array to search + * @param item item to search for + */ +const includes = (array: ReadonlyArray, item: U): item is T => + array.includes(item as T) + +export {buildTestingLibraryScript, includes, queryToSelector} diff --git a/lib/fixture/locator/index.ts b/lib/fixture/locator/index.ts new file mode 100644 index 0000000..c07e4ac --- /dev/null +++ b/lib/fixture/locator/index.ts @@ -0,0 +1,10 @@ +export { + installTestingLibraryFixture, + options, + queriesFixture, + registerSelectorsFixture, + screenFixture, + withinFixture, +} from './fixtures' +export type {Queries} from './fixtures' +export {queriesFor} from './queries' diff --git a/lib/fixture/locator/queries.ts b/lib/fixture/locator/queries.ts new file mode 100644 index 0000000..a2fbeaf --- /dev/null +++ b/lib/fixture/locator/queries.ts @@ -0,0 +1,122 @@ +import type {Locator, Page} from '@playwright/test' +import {errors} from '@playwright/test' +import {queries} from '@testing-library/dom' + +import {replacer} from '../helpers' +import type { + AllQuery, + Config, + FindQuery, + GetQuery, + LocatorQueries as Queries, + Query, + QueryQuery, + Screen, + SynchronousQuery, +} from '../types' + +import {includes, queryToSelector} from './helpers' + +const isAllQuery = (query: Query): query is AllQuery => query.includes('All') + +const isFindQuery = (query: Query): query is FindQuery => query.startsWith('find') +const isNotFindQuery = (query: Query): query is Exclude => + !query.startsWith('find') + +const allQueryNames = Object.keys(queries) as Query[] +const synchronousQueryNames = allQueryNames.filter(isNotFindQuery) + +const findQueryToGetQuery = (query: FindQuery) => query.replace(/^find/, 'get') as GetQuery +const findQueryToQueryQuery = (query: FindQuery) => query.replace(/^find/, 'query') as QueryQuery + +const createFindQuery = + ( + pageOrLocator: Page | Locator, + query: FindQuery, + {asyncUtilTimeout, asyncUtilExpectedState}: Partial = {}, + ) => + async (...[id, options, waitForElementOptions]: Parameters) => { + const synchronousOptions = ([id, options] as const).filter(Boolean) + + const locator = pageOrLocator.locator( + `${queryToSelector(findQueryToQueryQuery(query))}=${JSON.stringify( + synchronousOptions, + replacer, + )}`, + ) + + const {state: expectedState = asyncUtilExpectedState, timeout = asyncUtilTimeout} = + waitForElementOptions ?? {} + + try { + await locator.first().waitFor({state: expectedState, timeout}) + } catch (error) { + // In the case of a `waitFor` timeout from Playwright, we want to + // surface the appropriate error from Testing Library, so run the + // query one more time as `get*` knowing that it will fail with the + // error that we want the user to see instead of the `TimeoutError` + if (error instanceof errors.TimeoutError) { + const timeoutLocator = pageOrLocator + .locator( + `${queryToSelector(findQueryToGetQuery(query))}=${JSON.stringify( + synchronousOptions, + replacer, + )}`, + ) + .first() + + // Handle case where element is attached, but hidden, and the expected + // state is set to `visible`. In this case, dereferencing the + // `Locator` instance won't throw a `get*` query error, so just + // surface the original Playwright timeout error + if (expectedState === 'visible' && !(await timeoutLocator.isVisible())) { + throw error + } + + // In all other cases, dereferencing the `Locator` instance here should + // cause the above `get*` query to throw an error in Testing Library + return timeoutLocator.waitFor({state: expectedState, timeout}) + } + + throw error + } + + return locator + } + +/** + * Given a `Page` or `Locator` instance, return an object of Testing Library + * query methods that return a `Locator` instance for the queried element + * + * @internal this API is not currently intended for public usage and may be + * removed or changed outside of semantic release versioning. If possible, you + * should use the `locatorFixtures` with **@playwright/test** instead. + * @see {@link locatorFixtures} + * + * @param pageOrLocator `Page` or `Locator` instance to use as the query root + * @param config Testing Library configuration to apply to queries + * + * @returns object containing scoped Testing Library query methods + */ +const queriesFor = (pageOrLocator: Page | Locator, config?: Partial) => + allQueryNames.reduce( + (rest, query) => ({ + ...rest, + [query]: isFindQuery(query) + ? createFindQuery(pageOrLocator, query, config) + : (...args: Parameters) => + pageOrLocator.locator(`${queryToSelector(query)}=${JSON.stringify(args, replacer)}`), + }), + {} as Queries, + ) + +const screenFor = (page: Page, config: Partial) => + Proxy.revocable(page, { + get(target, property, receiver) { + return includes(allQueryNames, property) + ? queriesFor(page, config)[property] + : Reflect.get(target, property, receiver) + }, + }) as {proxy: Screen; revoke: () => void} + +export {allQueryNames, isAllQuery, isNotFindQuery, queriesFor, screenFor, synchronousQueryNames} diff --git a/lib/fixture/types.ts b/lib/fixture/types.ts new file mode 100644 index 0000000..c367e4b --- /dev/null +++ b/lib/fixture/types.ts @@ -0,0 +1,85 @@ +import {Locator, Page} from '@playwright/test' +import type * as TestingLibraryDom from '@testing-library/dom' +import {queries} from '@testing-library/dom' + +import type {Config as CommonConfig} from '../common' + +import {reviver} from './helpers' + +/** + * This type was copied across from Playwright + * + * @see {@link https://github.com/microsoft/playwright/blob/82ff85b106e31ffd7b3702aef260c9c460cfb10c/packages/playwright-core/src/client/types.ts#L108-L117} + */ +export type SelectorEngine = { + /** + * Returns the first element matching given selector in the root's subtree. + */ + query(root: HTMLElement, selector: string): HTMLElement | null + /** + * Returns all elements matching given selector in the root's subtree. + */ + queryAll(root: HTMLElement, selector: string): HTMLElement[] +} + +type Queries = typeof queries +type WaitForState = Exclude[0], undefined>['state'] +type AsyncUtilExpectedState = Extract + +type ConvertQuery = Query extends ( + el: HTMLElement, + ...rest: infer Rest +) => HTMLElement | (HTMLElement[] | null) | (HTMLElement | null) + ? (...args: Rest) => Locator + : Query extends ( + el: HTMLElement, + id: infer Id, + options: infer Options, + waitForOptions: infer WaitForOptions, + ) => Promise + ? ( + id: Id, + options?: Options, + waitForOptions?: WaitForOptions & {state?: AsyncUtilExpectedState}, + ) => Promise + : never + +type KebabCase = S extends `${infer C}${infer T}` + ? T extends Uncapitalize + ? `${Uncapitalize}${KebabCase}` + : `${Uncapitalize}-${KebabCase}` + : S + +export type LocatorQueries = {[K in keyof Queries]: ConvertQuery} + +export type WithinReturn = Root extends Page ? Screen : LocatorQueries +export type Screen = LocatorQueries & Page +export type Within = (locator: Root) => WithinReturn + +export type Query = keyof Queries + +export type AllQuery = Extract +export type FindQuery = Extract +export type GetQuery = Extract +export type QueryQuery = Extract +export type SynchronousQuery = Exclude + +export type Selector = KebabCase + +export interface Config extends CommonConfig { + asyncUtilExpectedState: AsyncUtilExpectedState +} +export interface ConfigFn { + (existingConfig: Config): Partial +} + +export type ConfigDelta = ConfigFn | Partial +export type Configure = (configDelta: ConfigDelta) => void +export type ConfigureLocator = (configDelta: ConfigDelta) => Config + +declare global { + interface Window { + TestingLibraryDom: typeof TestingLibraryDom + __testingLibraryReviver: typeof reviver + } +} diff --git a/lib/index.ts b/lib/index.ts index bbd2725..45f444c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -6,8 +6,8 @@ import * as path from 'path' import {JSHandle, Page} from 'playwright' import waitForExpect from 'wait-for-expect' -import {queryNames} from './common' -import {ConfigurationOptions, ElementHandle, Queries, ScopedQueries} from './typedefs' +import {Config, configureTestingLibraryScript, queryNames} from './common' +import {ElementHandle, Queries, ScopedQueries} from './typedefs' const domLibraryAsString = readFileSync( path.join(__dirname, '../dom-testing-library.js'), @@ -176,26 +176,28 @@ export function wait( export const waitFor = wait -export function configure(options: Partial): void { - if (!options) { +/** + * Configuration API for legacy queries that return `ElementHandle` instances. + * Only `testIdAttribute` and `asyncUtilTimeout` are currently supported. + + * @see {@link https://testing-library.com/docs/dom-testing-library/api-configuration} + * + * ⚠️ This API has no effect on the queries that return `Locator` instances. Use + * `test.use` instead to configure the `Locator` queries. + * + * @see {@link https://github.com/testing-library/playwright-testing-library/releases/tag/v4.4.0-beta.2} + * + * @param config + */ +export function configure(config: Partial): void { + if (!config) { return } - const {testIdAttribute, asyncUtilTimeout} = options - - if (testIdAttribute) { - delegateFnBodyToExecuteInPage = delegateFnBodyToExecuteInPageInitial.replace( - /testIdAttribute: (['|"])data-testid(['|"])/g, - `testIdAttribute: $1${testIdAttribute}$2`, - ) - } - - if (asyncUtilTimeout) { - delegateFnBodyToExecuteInPage = delegateFnBodyToExecuteInPageInitial.replace( - /asyncUtilTimeout: \d+/g, - `asyncUtilTimeout: ${asyncUtilTimeout}`, - ) - } + delegateFnBodyToExecuteInPage = configureTestingLibraryScript( + delegateFnBodyToExecuteInPageInitial, + config, + ) } export function getQueriesForElement( diff --git a/lib/typedefs.ts b/lib/typedefs.ts index 9fcc5c7..89a29cd 100644 --- a/lib/typedefs.ts +++ b/lib/typedefs.ts @@ -188,8 +188,3 @@ export interface Queries extends QueryMethods { getQueriesForElement(): ScopedQueries getNodeText(el: Element): Promise } - -export interface ConfigurationOptions { - testIdAttribute: string - asyncUtilTimeout: number -} diff --git a/package-lock.json b/package-lock.json index 454fcbb..7df5213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,11 +17,11 @@ "@playwright/test": "^1.25.0", "@rollup/plugin-commonjs": "^21.0.0", "@rollup/plugin-node-resolve": "^13.0.5", - "@rollup/plugin-replace": "^3.0.0", + "@rollup/plugin-replace": "^4.0.0", "@types/jest": "^27.0.2", "@types/jscodeshift": "^0.11.2", "generate-export-aliases": "^1.1.0", - "husky": "^7.0.2", + "husky": "^8.0.1", "jscodeshift": "^0.13.0", "npm-run-all": "^4.1.5", "playwright": "^1.25.0", @@ -2417,19 +2417,19 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", + "espree": "^9.3.2", + "globals": "^13.15.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { @@ -2437,9 +2437,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2476,18 +2476,22 @@ } }, "node_modules/@hover/javascript": { - "version": "6.82.0", - "resolved": "https://registry.npmjs.org/@hover/javascript/-/javascript-6.82.0.tgz", - "integrity": "sha512-k/MahVWgV2NLFfD1IZuklB4a7DmCRITAbvzb4WpKfNMaDTqJ4EBbK6jxyvPUP9UySdpJWTSb3WhngIs0h5SIiQ==", + "version": "6.90.0", + "resolved": "https://registry.npmjs.org/@hover/javascript/-/javascript-6.90.0.tgz", + "integrity": "sha512-FOMyktsyOI9S2axKtqgCAX1usnoRr6VxVvaOLomPqeWXYjzYmH7fcFRtsQl3ObOdNmqhVBwxIa+Sf5PoeVvLmQ==", "dev": true, "dependencies": { "@commitlint/cli": "^16.1.0", "@commitlint/config-conventional": "^16.2.1", "@commitlint/prompt": "^16.1.0", "@types/jest": "^27.0.2", + "@types/lodash.has": "^4.5.6", + "@types/mkdirp": "^1.0.2", "@types/node": ">=17.x", + "@types/rimraf": "^3.0.2", + "@types/which": "^2.0.1", "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.13.0", + "@typescript-eslint/parser": "^5.27.0", "arrify": "^2.0.1", "commitizen": "^4.2.4", "concurrently": "^7.0.0", @@ -2495,19 +2499,19 @@ "cross-env": "^7.0.3", "cross-spawn": "^7.0.1", "doctoc": "^2.1.0", - "eslint": "^8.8.0", + "eslint": "^8.16.0", "eslint-config-airbnb": "19.0.0", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-prettier": "^8.3.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^26.1.1", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.27.0", + "eslint-plugin-react": "^7.30.0", "eslint-plugin-react-hooks": "^4.3.0", - "glob": "^7.2.0", + "glob": "^8.0.3", "is-ci": "^3.0.1", - "jest": "^27.4.7", + "jest": "^27.5.1", "jest-github-actions-reporter": "^1.0.3", "jest-watch-typeahead": "^1.0.0", "lint-staged": "^12.3.4", @@ -2517,10 +2521,10 @@ "read-pkg-up": "^7.0.1", "rimraf": "^3.0.2", "ts-jest": "^27.1.3", - "tslib": "^2.3.1", + "tslib": "^2.4.0", "typescript": "^4", "which": "^2.0.2", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.0.1" }, "bin": { "hover-scripts": "dist/index.js" @@ -2531,10 +2535,50 @@ "yarn": ">=1" } }, + "node_modules/@hover/javascript/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@hover/javascript/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@hover/javascript/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -2545,6 +2589,16 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -3508,15 +3562,15 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", - "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", + "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", "dev": true, "dependencies": { "@rollup/pluginutils": "^3.1.0", "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", "deepmerge": "^4.2.2", + "is-builtin-module": "^3.1.0", "is-module": "^1.0.0", "resolve": "^1.19.0" }, @@ -3528,9 +3582,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.1.0.tgz", - "integrity": "sha512-pA3XRUrSKybVYqmH5TqWNZpGxF+VV+1GrYchKgCNIj2vsSOX7CVm2RCtx8p2nrC7xvkziYyK+lSi74T93MU3YA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz", + "integrity": "sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==", "dev": true, "dependencies": { "@rollup/pluginutils": "^3.1.0", @@ -3769,6 +3823,16 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -3843,9 +3907,9 @@ "dev": true }, "node_modules/@types/jscodeshift": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.3.tgz", - "integrity": "sha512-pM0JD9kWVDH9DQp5Y6td16924V3MwZHei8P3cTeuFhXpzpk0K+iWraBZz8wF61QkFs9fZeAQNX0q8SG0+TFm2w==", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.5.tgz", + "integrity": "sha512-7JV0qdblTeWFigevmwFUgROXX395F+MQx6v0YqPn8Bx0B4Sng6alEejz9PENzgLYpG+zL0O4tGdBzc4gKZH8XA==", "dev": true, "dependencies": { "ast-types": "^0.14.1", @@ -3864,12 +3928,42 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.184", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.184.tgz", + "integrity": "sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q==", + "dev": true + }, + "node_modules/@types/lodash.has": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@types/lodash.has/-/lodash.has-4.5.7.tgz", + "integrity": "sha512-nfbAzRbsZBdzSAkL9iiLy4SQk89uuFcXBFwZ7pf6oZhBgPvNys8BY5Twp/w8XvZKGt1o6cAa85wX4QhqO3uQ7A==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-0RJHq5FqDWo17kdHe+SMDJLfxmLaqHbWnqZ6gNKzDvStUlrmx/eKIY17+ifLS1yybo7X86aUshQMlittDOVNnw==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/mkdirp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz", + "integrity": "sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "17.0.23", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", @@ -3902,12 +3996,28 @@ "@types/node": "*" } }, + "node_modules/@types/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", + "dev": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, + "node_modules/@types/which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz", + "integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==", + "dev": true + }, "node_modules/@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -3970,15 +4080,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.19.0.tgz", - "integrity": "sha512-yhktJjMCJX8BSBczh1F/uY8wGRYrBeyn84kH6oyqdIJwTGKmzX5Qiq49LRQ0Jh0LXnWijEziSo6BRqny8nqLVQ==", + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.35.1.tgz", + "integrity": "sha512-XL2TBTSrh3yWAsMYpKseBYTVpvudNf69rPOWXWVBI08My2JVT5jR66eTt4IgQFHA/giiKJW5dUD4x/ZviCKyGg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.19.0", - "@typescript-eslint/types": "5.19.0", - "@typescript-eslint/typescript-estree": "5.19.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.35.1", + "@typescript-eslint/types": "5.35.1", + "@typescript-eslint/typescript-estree": "5.35.1", + "debug": "^4.3.4" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3996,6 +4106,95 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.35.1.tgz", + "integrity": "sha512-kCYRSAzIW9ByEIzmzGHE50NGAvAP3wFTaZevgWva7GpquDyFPFcmvVkFJGWJJktg/hLwmys/FZwqM9EKr2u24Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.35.1", + "@typescript-eslint/visitor-keys": "5.35.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.35.1.tgz", + "integrity": "sha512-FDaujtsH07VHzG0gQ6NDkVVhi1+rhq0qEvzHdJAQjysN+LHDCKDKCBRlZFFE0ec0jKxiv0hN63SNfExy0KrbQQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.35.1.tgz", + "integrity": "sha512-JUqE1+VRTGyoXlDWWjm6MdfpBYVq+hixytrv1oyjYIBEOZhBCwtpp5ZSvBt4wIA1MKWlnaC2UXl2XmYGC3BoQA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.35.1", + "@typescript-eslint/visitor-keys": "5.35.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.35.1.tgz", + "integrity": "sha512-cEB1DvBVo1bxbW/S5axbGPE6b7FIMAbo3w+AGq6zNDA7+NYJOIkKj/sInfTv4edxd4PxJSgdN4t6/pbvgA+n5g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.35.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.19.0.tgz", @@ -4142,9 +4341,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -4342,14 +4541,14 @@ "dev": true }, "node_modules/array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", "get-intrinsic": "^1.1.1", "is-string": "^1.0.7" }, @@ -4857,12 +5056,15 @@ "dev": true }, "node_modules/builtin-modules": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", - "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cache-base": { @@ -5984,15 +6186,19 @@ } }, "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dev": true, "dependencies": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-property": { @@ -6275,17 +6481,19 @@ } }, "node_modules/es-abstract": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.4.tgz", - "integrity": "sha512-flV8e5g9/xulChMG48Fygk1ptpo4lQRJ0eJYtxJFgi7pklLx7EFcOJ34jnvr8pbWlaFN/AT1cZpe0hiFel9Hqg==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", @@ -6297,9 +6505,10 @@ "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6435,13 +6644,14 @@ } }, "node_modules/eslint": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz", - "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz", + "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.2.1", - "@humanwhocodes/config-array": "^0.9.2", + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -6451,14 +6661,17 @@ "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", + "espree": "^9.3.3", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", "functional-red-black-tree": "^1.0.1", "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -6467,7 +6680,7 @@ "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "regexpp": "^3.2.0", @@ -6527,24 +6740,24 @@ } }, "node_modules/eslint-config-airbnb-typescript": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-16.2.0.tgz", - "integrity": "sha512-OUaMPZpTOZGKd5tXOjJ9PRU4iYNW/Z5DoHIynjsVK/FpkWdiY5+nxQW6TiJAlLwVI1l53xUOrnlZWtVBVQzuWA==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz", + "integrity": "sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g==", "dev": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^7.32.0 || ^8.2.0", "eslint-plugin-import": "^2.25.3" } }, "node_modules/eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -6810,25 +7023,25 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", - "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", + "version": "7.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.0.tgz", + "integrity": "sha512-BWriBttYYCnfb4RO9SB91Og8uA9CPcBMl5UlCOCtuYW1UjhN3QypzEcEHky4ZIRZDKjbO2Blh9BjP8E7W/b1SA==", "dev": true, "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flatmap": "^1.2.5", + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", "doctrine": "^2.1.0", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.5", "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.0", + "object.hasown": "^1.1.1", "object.values": "^1.1.5", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.3", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.6" + "string.prototype.matchall": "^4.0.7" }, "engines": { "node": ">=4" @@ -7007,9 +7220,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -7067,17 +7280,20 @@ } }, "node_modules/espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", "dev": true, "dependencies": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -7803,12 +8019,39 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/generate-export-aliases": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/generate-export-aliases/-/generate-export-aliases-1.1.0.tgz", @@ -8040,6 +8283,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -8062,9 +8311,9 @@ } }, "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8078,6 +8327,18 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -8259,15 +8520,15 @@ } }, "node_modules/husky": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", - "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", + "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", "dev": true, "bin": { "husky": "lib/bin.js" }, "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -8477,21 +8738,25 @@ "dev": true }, "node_modules/is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8506,6 +8771,21 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "node_modules/is-builtin-module": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz", + "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -8682,10 +8962,13 @@ } }, "node_modules/is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -12889,13 +13172,13 @@ } }, "node_modules/object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", "dev": true, "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13793,13 +14076,14 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -14273,9 +14557,9 @@ } }, "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "dev": true }, "node_modules/side-channel": { @@ -14886,26 +15170,28 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15473,9 +15759,9 @@ } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, "node_modules/tsutils": { @@ -15564,14 +15850,14 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { @@ -17958,26 +18244,26 @@ } }, "@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", + "espree": "^9.3.2", + "globals": "^13.15.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { "globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -17998,18 +18284,22 @@ } }, "@hover/javascript": { - "version": "6.82.0", - "resolved": "https://registry.npmjs.org/@hover/javascript/-/javascript-6.82.0.tgz", - "integrity": "sha512-k/MahVWgV2NLFfD1IZuklB4a7DmCRITAbvzb4WpKfNMaDTqJ4EBbK6jxyvPUP9UySdpJWTSb3WhngIs0h5SIiQ==", + "version": "6.90.0", + "resolved": "https://registry.npmjs.org/@hover/javascript/-/javascript-6.90.0.tgz", + "integrity": "sha512-FOMyktsyOI9S2axKtqgCAX1usnoRr6VxVvaOLomPqeWXYjzYmH7fcFRtsQl3ObOdNmqhVBwxIa+Sf5PoeVvLmQ==", "dev": true, "requires": { "@commitlint/cli": "^16.1.0", "@commitlint/config-conventional": "^16.2.1", "@commitlint/prompt": "^16.1.0", "@types/jest": "^27.0.2", + "@types/lodash.has": "^4.5.6", + "@types/mkdirp": "^1.0.2", "@types/node": ">=17.x", + "@types/rimraf": "^3.0.2", + "@types/which": "^2.0.1", "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.13.0", + "@typescript-eslint/parser": "^5.27.0", "arrify": "^2.0.1", "commitizen": "^4.2.4", "concurrently": "^7.0.0", @@ -18017,19 +18307,19 @@ "cross-env": "^7.0.3", "cross-spawn": "^7.0.1", "doctoc": "^2.1.0", - "eslint": "^8.8.0", + "eslint": "^8.16.0", "eslint-config-airbnb": "19.0.0", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-prettier": "^8.3.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^26.1.1", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.27.0", + "eslint-plugin-react": "^7.30.0", "eslint-plugin-react-hooks": "^4.3.0", - "glob": "^7.2.0", + "glob": "^8.0.3", "is-ci": "^3.0.1", - "jest": "^27.4.7", + "jest": "^27.5.1", "jest-github-actions-reporter": "^1.0.3", "jest-watch-typeahead": "^1.0.0", "lint-staged": "^12.3.4", @@ -18039,16 +18329,49 @@ "read-pkg-up": "^7.0.1", "rimraf": "^3.0.2", "ts-jest": "^27.1.3", - "tslib": "^2.3.1", + "tslib": "^2.4.0", "typescript": "^4", "which": "^2.0.2", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -18056,6 +18379,12 @@ "minimatch": "^3.0.4" } }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true + }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -18781,23 +19110,23 @@ } }, "@rollup/plugin-node-resolve": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", - "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", + "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", "dev": true, "requires": { "@rollup/pluginutils": "^3.1.0", "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", "deepmerge": "^4.2.2", + "is-builtin-module": "^3.1.0", "is-module": "^1.0.0", "resolve": "^1.19.0" } }, "@rollup/plugin-replace": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.1.0.tgz", - "integrity": "sha512-pA3XRUrSKybVYqmH5TqWNZpGxF+VV+1GrYchKgCNIj2vsSOX7CVm2RCtx8p2nrC7xvkziYyK+lSi74T93MU3YA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz", + "integrity": "sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==", "dev": true, "requires": { "@rollup/pluginutils": "^3.1.0", @@ -19004,6 +19333,16 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -19071,9 +19410,9 @@ } }, "@types/jscodeshift": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.3.tgz", - "integrity": "sha512-pM0JD9kWVDH9DQp5Y6td16924V3MwZHei8P3cTeuFhXpzpk0K+iWraBZz8wF61QkFs9fZeAQNX0q8SG0+TFm2w==", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.5.tgz", + "integrity": "sha512-7JV0qdblTeWFigevmwFUgROXX395F+MQx6v0YqPn8Bx0B4Sng6alEejz9PENzgLYpG+zL0O4tGdBzc4gKZH8XA==", "dev": true, "requires": { "ast-types": "^0.14.1", @@ -19092,12 +19431,42 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.184", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.184.tgz", + "integrity": "sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q==", + "dev": true + }, + "@types/lodash.has": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@types/lodash.has/-/lodash.has-4.5.7.tgz", + "integrity": "sha512-nfbAzRbsZBdzSAkL9iiLy4SQk89uuFcXBFwZ7pf6oZhBgPvNys8BY5Twp/w8XvZKGt1o6cAa85wX4QhqO3uQ7A==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-0RJHq5FqDWo17kdHe+SMDJLfxmLaqHbWnqZ6gNKzDvStUlrmx/eKIY17+ifLS1yybo7X86aUshQMlittDOVNnw==", + "dev": true + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "@types/mkdirp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz", + "integrity": "sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "17.0.23", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", @@ -19130,12 +19499,28 @@ "@types/node": "*" } }, + "@types/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", + "dev": true, + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, "@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, + "@types/which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz", + "integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==", + "dev": true + }, "@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -19178,15 +19563,67 @@ } }, "@typescript-eslint/parser": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.19.0.tgz", - "integrity": "sha512-yhktJjMCJX8BSBczh1F/uY8wGRYrBeyn84kH6oyqdIJwTGKmzX5Qiq49LRQ0Jh0LXnWijEziSo6BRqny8nqLVQ==", + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.35.1.tgz", + "integrity": "sha512-XL2TBTSrh3yWAsMYpKseBYTVpvudNf69rPOWXWVBI08My2JVT5jR66eTt4IgQFHA/giiKJW5dUD4x/ZviCKyGg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.19.0", - "@typescript-eslint/types": "5.19.0", - "@typescript-eslint/typescript-estree": "5.19.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.35.1", + "@typescript-eslint/types": "5.35.1", + "@typescript-eslint/typescript-estree": "5.35.1", + "debug": "^4.3.4" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.35.1.tgz", + "integrity": "sha512-kCYRSAzIW9ByEIzmzGHE50NGAvAP3wFTaZevgWva7GpquDyFPFcmvVkFJGWJJktg/hLwmys/FZwqM9EKr2u24Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.35.1", + "@typescript-eslint/visitor-keys": "5.35.1" + } + }, + "@typescript-eslint/types": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.35.1.tgz", + "integrity": "sha512-FDaujtsH07VHzG0gQ6NDkVVhi1+rhq0qEvzHdJAQjysN+LHDCKDKCBRlZFFE0ec0jKxiv0hN63SNfExy0KrbQQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.35.1.tgz", + "integrity": "sha512-JUqE1+VRTGyoXlDWWjm6MdfpBYVq+hixytrv1oyjYIBEOZhBCwtpp5ZSvBt4wIA1MKWlnaC2UXl2XmYGC3BoQA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.35.1", + "@typescript-eslint/visitor-keys": "5.35.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.35.1.tgz", + "integrity": "sha512-cEB1DvBVo1bxbW/S5axbGPE6b7FIMAbo3w+AGq6zNDA7+NYJOIkKj/sInfTv4edxd4PxJSgdN4t6/pbvgA+n5g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.35.1", + "eslint-visitor-keys": "^3.3.0" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "@typescript-eslint/scope-manager": { @@ -19273,9 +19710,9 @@ "dev": true }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true }, "acorn-globals": { @@ -19424,14 +19861,14 @@ "dev": true }, "array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", "get-intrinsic": "^1.1.1", "is-string": "^1.0.7" } @@ -19813,9 +20250,9 @@ "dev": true }, "builtin-modules": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", - "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true }, "cache-base": { @@ -20675,12 +21112,13 @@ "dev": true }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "define-property": { @@ -20891,17 +21329,19 @@ } }, "es-abstract": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.4.tgz", - "integrity": "sha512-flV8e5g9/xulChMG48Fygk1ptpo4lQRJ0eJYtxJFgi7pklLx7EFcOJ34jnvr8pbWlaFN/AT1cZpe0hiFel9Hqg==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", @@ -20913,9 +21353,10 @@ "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" } }, "es-shim-unscopables": { @@ -21011,13 +21452,14 @@ } }, "eslint": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz", - "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz", + "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.2.1", - "@humanwhocodes/config-array": "^0.9.2", + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -21027,14 +21469,17 @@ "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", + "espree": "^9.3.3", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", "functional-red-black-tree": "^1.0.1", "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -21043,7 +21488,7 @@ "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "regexpp": "^3.2.0", @@ -21104,9 +21549,9 @@ } }, "globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -21165,18 +21610,18 @@ } }, "eslint-config-airbnb-typescript": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-16.2.0.tgz", - "integrity": "sha512-OUaMPZpTOZGKd5tXOjJ9PRU4iYNW/Z5DoHIynjsVK/FpkWdiY5+nxQW6TiJAlLwVI1l53xUOrnlZWtVBVQzuWA==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz", + "integrity": "sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g==", "dev": true, "requires": { "eslint-config-airbnb-base": "^15.0.0" } }, "eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", "dev": true, "requires": {} }, @@ -21376,25 +21821,25 @@ } }, "eslint-plugin-react": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", - "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", + "version": "7.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.0.tgz", + "integrity": "sha512-BWriBttYYCnfb4RO9SB91Og8uA9CPcBMl5UlCOCtuYW1UjhN3QypzEcEHky4ZIRZDKjbO2Blh9BjP8E7W/b1SA==", "dev": true, "requires": { - "array-includes": "^3.1.4", - "array.prototype.flatmap": "^1.2.5", + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", "doctrine": "^2.1.0", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.5", "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.0", + "object.hasown": "^1.1.1", "object.values": "^1.1.5", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.3", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.6" + "string.prototype.matchall": "^4.0.7" }, "dependencies": { "doctrine": { @@ -21467,13 +21912,13 @@ "dev": true }, "espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", "dev": true, "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.3.0" } }, @@ -22044,12 +22489,30 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "generate-export-aliases": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/generate-export-aliases/-/generate-export-aliases-1.1.0.tgz", @@ -22217,6 +22680,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -22233,9 +22702,9 @@ } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, "has-flag": { @@ -22243,6 +22712,15 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -22383,9 +22861,9 @@ "dev": true }, "husky": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", - "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", + "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", "dev": true }, "iconv-lite": { @@ -22546,18 +23024,22 @@ "dev": true }, "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } }, "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-buffer": { @@ -22566,6 +23048,15 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-builtin-module": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz", + "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + } + }, "is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -22683,10 +23174,13 @@ "dev": true }, "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-obj": { "version": "2.0.0", @@ -25845,13 +26339,13 @@ } }, "object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "object.pick": { @@ -26508,13 +27002,14 @@ } }, "regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpp": { @@ -26868,9 +27363,9 @@ "dev": true }, "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "dev": true }, "side-channel": { @@ -27364,23 +27859,25 @@ } }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "strip-ansi": { @@ -27790,9 +28287,9 @@ } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, "tsutils": { @@ -27855,14 +28352,14 @@ "dev": true }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, diff --git a/package.json b/package.json index d7dec9e..e8a26c3 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,12 @@ "prepare:playwright-test:package": "jscodeshift -t ./playwright-test/rename-imports.ts --extensions=ts --parser=ts ./lib", "prepublishOnly": "npm run build", "start:standalone": "hover-scripts test", - "test": "run-s build:testing-library test:*", + "test": "run-s build:testing-library test:standalone test:fixture", + "test:legacy": "run-s build:testing-library test:standalone test:fixture:legacy", "test:fixture": "playwright test", + "test:fixture:legacy": "playwright test test/fixture/element-handles.test.ts", "test:standalone": "hover-scripts test --no-watch", - "test:types": "tsc --noEmit", - "validate": "run-s test" + "test:types": "tsc --noEmit" }, "repository": { "type": "git", @@ -56,11 +57,11 @@ "@playwright/test": "^1.25.0", "@rollup/plugin-commonjs": "^21.0.0", "@rollup/plugin-node-resolve": "^13.0.5", - "@rollup/plugin-replace": "^3.0.0", + "@rollup/plugin-replace": "^4.0.0", "@types/jest": "^27.0.2", "@types/jscodeshift": "^0.11.2", "generate-export-aliases": "^1.1.0", - "husky": "^7.0.2", + "husky": "^8.0.1", "jscodeshift": "^0.13.0", "npm-run-all": "^4.1.5", "playwright": "^1.25.0", diff --git a/test/fixture/configure.test.ts b/test/fixture/configure.test.ts new file mode 100644 index 0000000..dba69eb --- /dev/null +++ b/test/fixture/configure.test.ts @@ -0,0 +1,48 @@ +import * as path from 'path' + +import * as playwright from '@playwright/test' + +import { + LocatorFixtures as TestingLibraryFixtures, + locatorFixtures as fixtures, +} from '../../lib/fixture' + +const test = playwright.test.extend(fixtures) + +const {expect} = test + +test.use({testIdAttribute: 'data-new-id'}) + +test.describe('global configuration', () => { + test.beforeEach(async ({page}) => { + await page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) + }) + + test('queries with test ID configured in module scope', async ({queries}) => { + const defaultTestIdLocator = queries.queryByTestId('testid-text-input') + const customTestIdLocator = queries.queryByTestId('first-level-header') + + await expect(defaultTestIdLocator).not.toBeVisible() + await expect(customTestIdLocator).toBeVisible() + }) + + test.describe('overridding global configuration', () => { + test.use({testIdAttribute: 'data-id'}) + + test('overrides test ID configured in module scope', async ({queries}) => { + const globalTestIdLocator = queries.queryByTestId('first-level-header') + const overriddenTestIdLocator = queries.queryByTestId('second-level-header') + + await expect(globalTestIdLocator).not.toBeVisible() + await expect(overriddenTestIdLocator).toBeVisible() + }) + }) + + test("page override doesn't modify global configuration", async ({queries}) => { + const defaultTestIdLocator = queries.queryByTestId('testid-text-input') + const customTestIdLocator = queries.queryByTestId('first-level-header') + + await expect(defaultTestIdLocator).not.toBeVisible() + await expect(customTestIdLocator).toBeVisible() + }) +}) diff --git a/test/fixture/element-handles.test.ts b/test/fixture/element-handles.test.ts new file mode 100644 index 0000000..c674e99 --- /dev/null +++ b/test/fixture/element-handles.test.ts @@ -0,0 +1,218 @@ +import * as path from 'path' + +import * as playwright from '@playwright/test' + +import {getDocument, getQueriesForElement, within} from '../../lib' +import {TestingLibraryFixtures, configure, fixtures} from '../../lib/fixture' + +const test = playwright.test.extend(fixtures) + +const {expect} = test + +test.describe('lib/fixture.ts', () => { + test.describe('standard page', () => { + test.beforeEach(async ({page}) => { + await page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) + }) + + test.afterEach(async ({page}) => page.close()) + + test('should handle the query* methods', async ({queries: {queryByText}}) => { + const element = await queryByText('Hello h1') + + expect(element).toBeTruthy() + expect(await element.textContent()).toEqual('Hello h1') + }) + + test('should use the new v3 methods', async ({queries: {queryByRole}}) => { + const element = await queryByRole('presentation') + + expect(element).toBeTruthy() + expect(await element.textContent()).toContain('Layout table') + }) + + test('should handle regex matching', async ({queries: {queryByText}}) => { + const element = await queryByText(/HeLlO h(1|7)/i) + + expect(element).toBeTruthy() + expect(await element.textContent()).toEqual('Hello h1') + }) + + test('should handle the get* methods', async ({queries: {getByTestId}, page}) => { + const element = await getByTestId('testid-text-input') + + expect(await page.evaluate(el => el.outerHTML, element)).toMatch( + ``, + ) + }) + + test('attaches `getNodeText`', async ({queries}) => { + const element = await queries.getByText('Hello h1') + + expect(await queries.getNodeText(element)).toEqual('Hello h1') + }) + + test('handles page navigations', async ({queries: {getByText}, page}) => { + await page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) + + const element = await getByText('Hello h1') + + expect(await element.textContent()).toEqual('Hello h1') + }) + + test('should handle the get* method failures', async ({queries}) => { + const {getByTitle} = queries + // Use the scoped element so the pretty HTML snapshot is smaller + + await expect(async () => getByTitle('missing')).rejects.toThrow() + }) + + test('should handle the LabelText methods', async ({queries, page}) => { + const {getByLabelText} = queries + const element = await getByLabelText('Label A') + /* istanbul ignore next */ + expect(await page.evaluate(el => el.outerHTML, element)).toMatch( + ``, + ) + }) + + test('should handle the queryAll* methods', async ({queries, page}) => { + const {queryAllByText} = queries + const elements = await queryAllByText(/Hello/) + expect(elements).toHaveLength(3) + + const text = await Promise.all([ + page.evaluate(el => el.textContent, elements[0]), + page.evaluate(el => el.textContent, elements[1]), + page.evaluate(el => el.textContent, elements[2]), + ]) + + expect(text).toEqual(['Hello h1', 'Hello h2', 'Hello h3']) + }) + + test('should handle the queryAll* methods with a selector', async ({queries, page}) => { + const {queryAllByText} = queries + const elements = await queryAllByText(/Hello/, {selector: 'h2'}) + expect(elements).toHaveLength(1) + + const text = await page.evaluate(el => el.textContent, elements[0]) + + expect(text).toEqual('Hello h2') + }) + + test('should handle the getBy* methods with a selector', async ({queries, page}) => { + const {getByText} = queries + const element = await getByText(/Hello/, {selector: 'h2'}) + + const text = await page.evaluate(el => el.textContent, element) + + expect(text).toEqual('Hello h2') + }) + + test('should handle the getBy* methods with a regex name', async ({queries, page}) => { + const {getByRole} = queries + const element = await getByRole('button', {name: /getBy.*Test/}) + + const text = await page.evaluate(el => el.textContent, element) + + expect(text).toEqual('getByRole Test') + }) + + test('supports `hidden` option when querying by role', async ({queries: {queryAllByRole}}) => { + const elements = await queryAllByRole('img') + const hiddenElements = await queryAllByRole('img', {hidden: true}) + + expect(elements).toHaveLength(1) + expect(hiddenElements).toHaveLength(2) + }) + + test.describe('querying by role with `level` option', () => { + test('retrieves the correct elements when querying all by role', async ({ + queries: {queryAllByRole}, + }) => { + const elements = await queryAllByRole('heading') + const levelOneElements = await queryAllByRole('heading', {level: 3}) + + expect(elements).toHaveLength(3) + expect(levelOneElements).toHaveLength(1) + }) + + test('does not throw when querying for a specific element', async ({ + queries: {getByRole}, + }) => { + await expect(getByRole('heading', {level: 3})).resolves.not.toThrow() + }) + }) + + test('should get text content', async ({page}) => { + const document = await getDocument(page) + const $h3 = await document.$('#scoped h3') + + expect(await $h3.textContent()).toEqual('Hello h3') + }) + + test('scoping queries with `within`', async ({queries: {getByTestId}}) => { + // eslint-disable-next-line @typescript-eslint/unbound-method + const {queryByText} = within(await getByTestId('scoped')) + + expect(await queryByText('Hello h1')).toBeFalsy() + expect(await queryByText('Hello h3')).toBeTruthy() + }) + + test('scoping queries with `getQueriesForElement`', async ({queries: {getByTestId}}) => { + // eslint-disable-next-line @typescript-eslint/unbound-method + const {queryByText} = getQueriesForElement(await getByTestId('scoped')) + + expect(await queryByText('Hello h1')).toBeFalsy() + expect(await queryByText('Hello h3')).toBeTruthy() + }) + + test.describe('configuration', () => { + test.afterEach(() => { + configure({testIdAttribute: 'data-testid'}) // cleanup + }) + + test('should support custom data-testid attribute name', async ({queries}) => { + configure({testIdAttribute: 'data-id'}) + + const element = await queries.getByTestId('second-level-header') + + expect(await queries.getNodeText(element)).toEqual('Hello h2') + }) + + test('should support subsequent changing the data-testid attribute names', async ({ + queries, + }) => { + configure({testIdAttribute: 'data-id'}) + configure({testIdAttribute: 'data-new-id'}) + + const element = await queries.getByTestId('first-level-header') + + expect(await queries.getNodeText(element)).toEqual('Hello h1') + }) + }) + }) + + test.describe('deferred page', () => { + test.beforeEach(async ({page}) => { + await page.goto(`file://${path.join(__dirname, '../fixtures/late-page.html')}`) + }) + + test.afterEach(async ({page}) => page.close()) + + test('should handle the findBy* methods', async ({queries}) => { + const {findByText} = queries + expect(await findByText('Loaded!', {}, {timeout: 3000})).toBeTruthy() + }) + + test('should handle the findByAll* methods', async ({queries}) => { + const {findAllByText} = queries + const elements = await findAllByText(/Hello/, {}, {timeout: 3000}) + expect(elements).toHaveLength(2) + + const text = await Promise.all([elements[0].textContent(), elements[1].textContent()]) + + expect(text).toEqual(['Hello h1', 'Hello h2']) + }) + }) +}) diff --git a/test/fixture/fixture.test.ts b/test/fixture/fixture.test.ts deleted file mode 100644 index 0cee28f..0000000 --- a/test/fixture/fixture.test.ts +++ /dev/null @@ -1,211 +0,0 @@ -import * as path from 'path' - -import * as playwright from '@playwright/test' - -import {getDocument, getQueriesForElement, within} from '../../lib' -import {TestingLibraryFixtures, configure, fixtures} from '../../lib/fixture' - -const test = playwright.test.extend(fixtures) - -const {expect} = test - -test.describe('lib/fixture.ts', () => { - test.beforeEach(async ({page}) => { - await page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) - }) - - test('should handle the query* methods', async ({queries: {queryByText}}) => { - const element = await queryByText('Hello h1') - - expect(element).toBeTruthy() - expect(await element.textContent()).toEqual('Hello h1') - }) - - test('should use the new v3 methods', async ({queries: {queryByRole}}) => { - const element = await queryByRole('presentation') - - expect(element).toBeTruthy() - expect(await element.textContent()).toContain('Layout table') - }) - - test('should handle regex matching', async ({queries: {queryByText}}) => { - const element = await queryByText(/HeLlO h(1|7)/i) - - expect(element).toBeTruthy() - expect(await element.textContent()).toEqual('Hello h1') - }) - - test('should handle the get* methods', async ({queries: {getByTestId}, page}) => { - const element = await getByTestId('testid-text-input') - - expect(await page.evaluate(el => el.outerHTML, element)).toMatch( - ``, - ) - }) - - test('attaches `getNodeText`', async ({queries}) => { - const element = await queries.getByText('Hello h1') - - expect(await queries.getNodeText(element)).toEqual('Hello h1') - }) - - test('handles page navigations', async ({queries: {getByText}, page}) => { - await page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) - - const element = await getByText('Hello h1') - - expect(await element.textContent()).toEqual('Hello h1') - }) - - test('should handle the get* method failures', async ({queries}) => { - const {getByTitle} = queries - // Use the scoped element so the pretty HTML snapshot is smaller - - await expect(async () => getByTitle('missing')).rejects.toThrow() - }) - - test('should handle the LabelText methods', async ({queries, page}) => { - const {getByLabelText} = queries - const element = await getByLabelText('Label A') - /* istanbul ignore next */ - expect(await page.evaluate(el => el.outerHTML, element)).toMatch( - ``, - ) - }) - - test('should handle the queryAll* methods', async ({queries, page}) => { - const {queryAllByText} = queries - const elements = await queryAllByText(/Hello/) - expect(elements).toHaveLength(3) - - const text = await Promise.all([ - page.evaluate(el => el.textContent, elements[0]), - page.evaluate(el => el.textContent, elements[1]), - page.evaluate(el => el.textContent, elements[2]), - ]) - - expect(text).toEqual(['Hello h1', 'Hello h2', 'Hello h3']) - }) - - test('should handle the queryAll* methods with a selector', async ({queries, page}) => { - const {queryAllByText} = queries - const elements = await queryAllByText(/Hello/, {selector: 'h2'}) - expect(elements).toHaveLength(1) - - const text = await page.evaluate(el => el.textContent, elements[0]) - - expect(text).toEqual('Hello h2') - }) - - test('should handle the getBy* methods with a selector', async ({queries, page}) => { - const {getByText} = queries - const element = await getByText(/Hello/, {selector: 'h2'}) - - const text = await page.evaluate(el => el.textContent, element) - - expect(text).toEqual('Hello h2') - }) - - test('should handle the getBy* methods with a regex name', async ({queries, page}) => { - const {getByRole} = queries - const element = await getByRole('button', {name: /getBy.*Test/}) - - const text = await page.evaluate(el => el.textContent, element) - - expect(text).toEqual('getByRole Test') - }) - - test('supports `hidden` option when querying by role', async ({queries: {queryAllByRole}}) => { - const elements = await queryAllByRole('img') - const hiddenElements = await queryAllByRole('img', {hidden: true}) - - expect(elements).toHaveLength(1) - expect(hiddenElements).toHaveLength(2) - }) - - test.describe('querying by role with `level` option', () => { - test('retrieves the correct elements when querying all by role', async ({ - queries: {queryAllByRole}, - }) => { - const elements = await queryAllByRole('heading') - const levelOneElements = await queryAllByRole('heading', {level: 3}) - - expect(elements).toHaveLength(3) - expect(levelOneElements).toHaveLength(1) - }) - - test('does not throw when querying for a specific element', async ({queries: {getByRole}}) => { - expect.assertions(1) - - await expect(getByRole('heading', {level: 3})).resolves.not.toThrow() - }) - }) - - test('should get text content', async ({page}) => { - const document = await getDocument(page) - const $h3 = await document.$('#scoped h3') - - expect(await $h3.textContent()).toEqual('Hello h3') - }) - - test('scoping queries with `within`', async ({queries: {getByTestId}}) => { - // eslint-disable-next-line @typescript-eslint/unbound-method - const {queryByText} = within(await getByTestId('scoped')) - - expect(await queryByText('Hello h1')).toBeFalsy() - expect(await queryByText('Hello h3')).toBeTruthy() - }) - - test('scoping queries with `getQueriesForElement`', async ({queries: {getByTestId}}) => { - // eslint-disable-next-line @typescript-eslint/unbound-method - const {queryByText} = getQueriesForElement(await getByTestId('scoped')) - - expect(await queryByText('Hello h1')).toBeFalsy() - expect(await queryByText('Hello h3')).toBeTruthy() - }) - - test.describe('configuration', () => { - test.afterEach(() => { - configure({testIdAttribute: 'data-testid'}) // cleanup - }) - - test('should support custom data-testid attribute name', async ({queries}) => { - configure({testIdAttribute: 'data-id'}) - - const element = await queries.getByTestId('second-level-header') - - expect(await queries.getNodeText(element)).toEqual('Hello h2') - }) - - test('should support subsequent changing the data-testid attribute names', async ({ - queries, - }) => { - configure({testIdAttribute: 'data-id'}) - configure({testIdAttribute: 'data-new-id'}) - - const element = await queries.getByTestId('first-level-header') - - expect(await queries.getNodeText(element)).toEqual('Hello h1') - }) - }) - test.describe('deferred page', () => { - test.beforeEach(async ({page}) => { - await page.goto(`file://${path.join(__dirname, '../fixtures/late-page.html')}`) - }) - - test('should handle the findBy* methods', async ({queries}) => { - const {findByText} = queries - expect(await findByText('Loaded!', {}, {timeout: 7000})).toBeTruthy() - }) - - test('should handle the findByAll* methods', async ({queries}) => { - const {findAllByText} = queries - const elements = await findAllByText(/Hello/, {}, {timeout: 7000}) - expect(elements).toHaveLength(2) - - const text = await Promise.all([elements[0].textContent(), elements[1].textContent()]) - - expect(text).toEqual(['Hello h1', 'Hello h2']) - }) - }) -}) diff --git a/test/fixture/locators.test.ts b/test/fixture/locators.test.ts new file mode 100644 index 0000000..6666003 --- /dev/null +++ b/test/fixture/locators.test.ts @@ -0,0 +1,349 @@ +import * as path from 'path' + +import * as playwright from '@playwright/test' + +import { + LocatorFixtures as TestingLibraryFixtures, + locatorFixtures as fixtures, +} from '../../lib/fixture' + +const test = playwright.test.extend(fixtures) + +const {expect} = test + +test.describe('lib/fixture.ts (locators)', () => { + test.describe('standard page', () => { + test.beforeEach(async ({page}) => { + await page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) + }) + + test.afterEach(async ({page}) => page.close()) + + test('should handle the query* methods', async ({queries: {queryByText}}) => { + const locator = queryByText('Hello h1') + + expect(locator).toBeTruthy() + expect(await locator.textContent()).toEqual('Hello h1') + }) + + test('should use the new v3 methods', async ({queries: {queryByRole}}) => { + const locator = queryByRole('presentation') + + expect(locator).toBeTruthy() + expect(await locator.textContent()).toContain('Layout table') + }) + + test('should handle regex matching', async ({queries: {queryByText}}) => { + const locator = queryByText(/HeLlO h(1|7)/i) + + expect(locator).toBeTruthy() + expect(await locator.textContent()).toEqual('Hello h1') + }) + + test('should handle the get* methods', async ({queries: {getByTestId}}) => { + const locator = getByTestId('testid-text-input') + + expect(await locator.evaluate(el => el.outerHTML)).toMatch( + ``, + ) + }) + + test('handles page navigations', async ({queries: {getByText}, page}) => { + await page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) + + const locator = getByText('Hello h1') + + expect(await locator.textContent()).toEqual('Hello h1') + }) + + test('should handle the get* method failures', async ({queries}) => { + const {getByTitle} = queries + // Use the scoped element so the pretty HTML snapshot is smaller + + await expect(async () => getByTitle('missing').textContent()).rejects.toThrow() + }) + + test('should handle the LabelText methods', async ({queries}) => { + const {getByLabelText} = queries + const locator = getByLabelText('Label A') + + /* istanbul ignore next */ + expect(await locator.evaluate(el => el.outerHTML)).toMatch( + ``, + ) + }) + + test('should handle the queryAll* methods', async ({queries}) => { + const {queryAllByText} = queries + const locator = queryAllByText(/Hello/) + + expect(await locator.count()).toEqual(3) + + const text = await Promise.all([ + locator.nth(0).textContent(), + locator.nth(1).textContent(), + locator.nth(2).textContent(), + ]) + + expect(text).toEqual(['Hello h1', 'Hello h2', 'Hello h3']) + }) + + test('should handle the queryAll* methods with a selector', async ({queries}) => { + const {queryAllByText} = queries + const locator = queryAllByText(/Hello/, {selector: 'h2'}) + + expect(await locator.count()).toEqual(1) + + expect(await locator.textContent()).toEqual('Hello h2') + }) + + test('should handle the getBy* methods with a selector', async ({queries}) => { + const {getByText} = queries + const locator = getByText(/Hello/, {selector: 'h2'}) + + expect(await locator.textContent()).toEqual('Hello h2') + }) + + test('should handle the getBy* methods with a regex name', async ({queries}) => { + const {getByRole} = queries + const element = getByRole('button', {name: /getBy.*Test/}) + + expect(await element.textContent()).toEqual('getByRole Test') + }) + + test('supports `hidden` option when querying by role', async ({queries: {queryAllByRole}}) => { + const elements = queryAllByRole('img') + const hiddenElements = queryAllByRole('img', {hidden: true}) + + expect(await elements.count()).toEqual(1) + expect(await hiddenElements.count()).toEqual(2) + }) + + test.describe('querying by role with `level` option', () => { + test('retrieves the correct elements when querying all by role', async ({ + queries: {queryAllByRole}, + }) => { + const locator = queryAllByRole('heading') + const levelOneLocator = queryAllByRole('heading', {level: 3}) + + expect(await locator.count()).toEqual(3) + expect(await levelOneLocator.count()).toEqual(1) + }) + + test('does not throw when querying for a specific element', async ({ + queries: {getByRole}, + }) => { + await expect(getByRole('heading', {level: 3}).textContent()).resolves.not.toThrow() + }) + }) + + test('scopes to container with `within`', async ({queries: {queryByRole}, within}) => { + const form = queryByRole('form', {name: 'User'}) + + const {queryByLabelText} = within(form) + + const outerLocator = queryByLabelText('Name') + const innerLocator = queryByLabelText('Username') + + expect(await outerLocator.count()).toBe(0) + expect(await innerLocator.count()).toBe(1) + }) + + test('when `Page` instance is provided, `within` returns a `Screen`', async ({ + page, + within, + }) => { + const screen = within(page) + + await screen.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`) + + const form = screen.queryByRole('form', {name: 'User'}) + + const {queryByLabelText} = within(form) + + const outerLocator = queryByLabelText('Name') + const innerLocator = queryByLabelText('Username') + + expect(await outerLocator.count()).toBe(0) + expect(await innerLocator.count()).toBe(1) + }) + + test.describe('configuration', () => { + test.describe('custom data-testid', () => { + test.use({testIdAttribute: 'data-id'}) + + test('supports custom data-testid attribute name', async ({queries}) => { + const locator = queries.getByTestId('second-level-header') + + expect(await locator.textContent()).toEqual('Hello h2') + }) + }) + + test.describe('nested configuration', () => { + test.use({testIdAttribute: 'data-new-id'}) + + test('supports nested data-testid attribute names', async ({queries}) => { + const locator = queries.getByTestId('first-level-header') + + expect(await locator.textContent()).toEqual('Hello h1') + }) + }) + }) + + test('screen fixture responds to Page and Query methods', async ({screen}) => { + const locator = screen.getByRole('button', {name: /getBy.*Test/}) + expect(await locator.textContent()).toEqual('getByRole Test') + + await screen.goto(`file://${path.join(__dirname, '../fixtures/late-page.html')}`) + + const delayedLocator = await screen.findByText('Loaded!', undefined, {timeout: 3000}) + expect(await delayedLocator.textContent()).toEqual('Loaded!') + }) + }) + + test.describe('deferred page', () => { + test.beforeEach(async ({page}) => { + await page.goto(`file://${path.join(__dirname, '../fixtures/late-page.html')}`) + }) + + test.afterEach(async ({page}) => page.close()) + + test('should handle the findBy* methods', async ({queries}) => { + const locator = await queries.findByText('Loaded!', undefined, {timeout: 3000}) + + expect(await locator.textContent()).toEqual('Loaded!') + }) + + test('should handle the findAllBy* methods', async ({queries}) => { + const locator = await queries.findAllByText(/Hello/, undefined, {timeout: 3000}) + + const text = await Promise.all([locator.nth(0).textContent(), locator.nth(1).textContent()]) + + expect(text).toEqual(['Hello h1', 'Hello h2']) + }) + + test('throws Testing Library error when locator times out', async ({queries}) => { + const query = async () => queries.findByText(/Loaded!/, undefined, {timeout: 500}) + + await expect(query).rejects.toThrowError( + expect.objectContaining({ + message: expect.stringContaining('TestingLibraryElementError'), + }), + ) + }) + + test('throws Playwright error when locator times out for visible state (but is attached)', async ({ + queries, + }) => { + const query = async () => + queries.findByText(/Hidden/, undefined, {state: 'visible', timeout: 500}) + + await expect(query).rejects.toThrowError( + expect.objectContaining({ + message: expect.stringContaining('500'), + }), + ) + }) + + test('throws Testing Library error when locator times out for attached state', async ({ + queries, + }) => { + const query = async () => + queries.findByText(/Loaded!/, undefined, {state: 'attached', timeout: 500}) + + await expect(query).rejects.toThrowError( + expect.objectContaining({ + message: expect.stringContaining('TestingLibraryElementError'), + }), + ) + }) + + test('throws Testing Library error when multi-element locator times out', async ({queries}) => { + const query = async () => queries.findAllByText(/Hello/, undefined, {timeout: 500}) + + await expect(query).rejects.toThrowError( + expect.objectContaining({ + message: expect.stringContaining('TestingLibraryElementError'), + }), + ) + }) + + test.describe('configuring asynchronous queries via `use`', () => { + test.use({asyncUtilTimeout: 3000}) + + test('reads timeout configuration from `use` configuration', async ({queries, page}) => { + // Ensure this test fails if we don't set `timeout` correctly in the `waitFor` in our find query + page.setDefaultTimeout(4000) + + const locator = await queries.findByText('Loaded!') + + expect(await locator.textContent()).toEqual('Loaded!') + }) + + test.describe('with custom test ID attribute from `use`', () => { + test.use({testIdAttribute: 'data-customid'}) + + test('reads test ID configuration from `use` configuration', async ({queries, page}) => { + // Ensure this test fails if we don't set `timeout` correctly in the `waitFor` in our find query + page.setDefaultTimeout(4000) + + const locator = await queries.findByTestId('loaded') + + expect(await locator.textContent()).toEqual('Loaded!') + }) + }) + }) + + test('waits for hidden element to be visible when `visible` is passed for state', async ({ + queries, + }) => { + await expect(queries.getByText('Hidden')).toBeHidden() + + const locator = await queries.findByText('Hidden', undefined, { + timeout: 3000, + state: 'visible', + }) + + expect(await locator.textContent()).toEqual('Hidden') + }) + + test.describe('configuring asynchronous queries with `visible` state', () => { + test.use({asyncUtilExpectedState: 'visible'}) + + test('waits for hidden element to be visible', async ({queries}) => { + await expect(queries.getByText('Hidden')).toBeHidden() + + const locator = await queries.findByText('Hidden', undefined, {timeout: 3000}) + + expect(await locator.textContent()).toEqual('Hidden') + }) + }) + + test('waits for hidden element to be attached when `attached` is passed for state', async ({ + queries, + }) => { + await expect(queries.queryByText('Attached')).toHaveCount(0) + + const locator = await queries.findByText('Attached', undefined, { + timeout: 3000, + state: 'attached', + }) + + expect(await locator.textContent()).toEqual('Attached') + await expect(locator).toBeHidden() + }) + + test.describe('configuring asynchronous queries with `attached` state', () => { + test.use({asyncUtilExpectedState: 'attached'}) + + test('waits for hidden element to be attached', async ({queries}) => { + await expect(queries.queryByText('Attached')).toHaveCount(0) + + const locator = await queries.findByText('Attached', undefined, {timeout: 3000}) + + expect(await locator.textContent()).toEqual('Attached') + await expect(locator).toBeHidden() + }) + }) + }) +}) diff --git a/test/fixtures/late-page.html b/test/fixtures/late-page.html index 87f5474..ea0eae2 100644 --- a/test/fixtures/late-page.html +++ b/test/fixtures/late-page.html @@ -2,10 +2,12 @@ Loading... + diff --git a/test/fixtures/page.html b/test/fixtures/page.html index a014c3a..c95039f 100644 --- a/test/fixtures/page.html +++ b/test/fixtures/page.html @@ -21,10 +21,22 @@

Hello h3

aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" + width="128px" viewBox="0 0 512 512" > + +
+ + +
+ + +
+ + +
diff --git a/test/standalone/index.test.ts b/test/standalone/index.test.ts index 37ae2af..e1609e3 100644 --- a/test/standalone/index.test.ts +++ b/test/standalone/index.test.ts @@ -179,7 +179,7 @@ describe('lib/index.ts', () => { afterEach(async () => page.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`)) it('supports configuring timeout for findBy* queries', async () => { - configure({asyncUtilTimeout: 9000}) + configure({asyncUtilTimeout: 3000}) const element = await queries.findByText(await getDocument(page), 'Loaded!')