diff --git a/scripts/pluralize.js b/scripts/pluralize.js new file mode 100644 index 0000000..1dc4100 --- /dev/null +++ b/scripts/pluralize.js @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const pluralizationMap = { + Avatar: "Avatars", + ChatBubble: "ChatBubbles", + LoadingBar: "LoadingBars", +}; + +function pluralizeComponentName(componentName) { + if (!(componentName in pluralizationMap)) { + throw new Error(`Could not find the plural case for ${componentName}.`); + } + + return pluralizationMap[componentName]; +} + +export { pluralizeComponentName }; diff --git a/scripts/test-utils.js b/scripts/test-utils.js index ddf7fda..ad25b95 100644 --- a/scripts/test-utils.js +++ b/scripts/test-utils.js @@ -7,82 +7,168 @@ import path from "node:path"; import { default as convertToSelectorUtil } from "@cloudscape-design/test-utils-converter"; +import { pluralizeComponentName } from "./pluralize.js"; import { pascalCase, writeSourceFile } from "./utils.js"; const components = globbySync(["src/test-utils/dom/**/index.ts", "!src/test-utils/dom/index.ts"]).map((fileName) => fileName.replace("src/test-utils/dom/", "").replace("/index.ts", ""), ); -generateSelectorUtils(); -generateDomIndexFile(); -generateSelectorsIndexFile(); -compileTypescript(); - -function generateSelectorUtils() { - components.forEach((componentName) => { - const domFileName = `./src/test-utils/dom/${componentName}/index.ts`; - const domFileContent = fs.readFileSync(domFileName, "utf-8"); - const selectorsFileName = `./src/test-utils/selectors/${componentName}/index.ts`; - const selectorsFileContent = convertToSelectorUtil.default(domFileContent); - writeSourceFile(selectorsFileName, selectorsFileContent); - }); +function toWrapper(componentClass) { + return `${componentClass}Wrapper`; } -function generateDomIndexFile() { - const content = generateIndexFileContent({ - testUtilType: "dom", - buildFinderInterface: (componentName) => - `find${pascalCase(componentName)}(selector?: string): ${pascalCase(componentName)}Wrapper | null;`, - }); - writeSourceFile("./src/test-utils/dom/index.ts", content); +const testUtilsSrcDir = path.resolve("src/test-utils"); +const configs = { + common: { + buildFinder: ({ componentName, componentNamePlural }) => ` + ElementWrapper.prototype.find${componentName} = function(selector) { + const rootSelector = \`.$\{${toWrapper(componentName)}.rootSelector}\`; + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${toWrapper(componentName)}); + }; + + ElementWrapper.prototype.findAll${componentNamePlural} = function(selector) { + return this.findAllComponents(${toWrapper(componentName)}, selector); + };`, + }, + dom: { + defaultExport: `export default function wrapper(root: Element = document.body) { if (document && document.body && !document.body.contains(root)) { console.warn('[AwsUi] [test-utils] provided element is not part of the document body, interactions may work incorrectly')}; return new ElementWrapper(root); }`, + buildFinderInterface: ({ componentName, componentNamePlural }) => ` + /** + * Returns the wrapper of the first ${componentName} that matches the specified CSS selector. + * If no CSS selector is specified, returns the wrapper of the first ${componentName}. + * If no matching ${componentName} is found, returns \`null\`. + * + * @param {string} [selector] CSS Selector + * @returns {${toWrapper(componentName)} | null} + */ + find${componentName}(selector?: string): ${toWrapper(componentName)} | null; + + /** + * Returns an array of ${componentName} wrapper that matches the specified CSS selector. + * If no CSS selector is specified, returns all of the ${componentNamePlural} inside the current wrapper. + * If no matching ${componentName} is found, returns an empty array. + * + * @param {string} [selector] CSS Selector + * @returns {Array<${toWrapper(componentName)}>} + */ + findAll${componentNamePlural}(selector?: string): Array<${toWrapper(componentName)}>;`, + }, + selectors: { + defaultExport: `export default function wrapper(root: string = 'body') { return new ElementWrapper(root); }`, + buildFinderInterface: ({ componentName, componentNamePlural }) => ` + /** + * Returns a wrapper that matches the ${componentNamePlural} with the specified CSS selector. + * If no CSS selector is specified, returns a wrapper that matches ${componentNamePlural}. + * + * @param {string} [selector] CSS Selector + * @returns {${toWrapper(componentName)}} + */ + find${componentName}(selector?: string): ${toWrapper(componentName)}; + + /** + * Returns a multi-element wrapper that matches ${componentNamePlural} with the specified CSS selector. + * If no CSS selector is specified, returns a multi-element wrapper that matches ${componentNamePlural}. + * + * @param {string} [selector] CSS Selector + * @returns {MultiElementWrapper<${toWrapper(componentName)}>} + */ + findAll${componentNamePlural}(selector?: string): MultiElementWrapper<${toWrapper(componentName)}>;`, + }, +}; + +function generateFindersInterfaces({ testUtilMetaData, testUtilType, configs }) { + const { buildFinderInterface } = configs[testUtilType]; + const findersInterfaces = testUtilMetaData.map(buildFinderInterface); + + // we need to redeclare the interface in its original definition, extending a re-export will not work + // https://github.com/microsoft/TypeScript/issues/12607 + const interfaces = `declare module '@cloudscape-design/test-utils-core/dist/${testUtilType}' { + interface ElementWrapper { + ${findersInterfaces.join("\n")} + } + }`; + + return interfaces; } -function generateSelectorsIndexFile() { - const content = generateIndexFileContent({ - testUtilType: "selectors", - buildFinderInterface: (componentName) => - `find${pascalCase(componentName)}(selector?: string): ${pascalCase(componentName)}Wrapper;`, - }); - writeSourceFile("./src/test-utils/selectors/index.ts", content); +function generateFindersImplementations({ testUtilMetaData, configs }) { + const { buildFinder } = configs.common; + const findersImplementations = testUtilMetaData.map(buildFinder); + return findersImplementations.join("\n"); } -function generateIndexFileContent({ testUtilType, buildFinderInterface }) { +function generateIndexFileContent(testUtilType, testUtilMetaData) { + const config = configs[testUtilType]; + if (config === undefined) { + throw new Error("Unknown test util type"); + } + return [ // language=TypeScript `import { ElementWrapper } from '@cloudscape-design/test-utils-core/${testUtilType}';`, `import '@cloudscape-design/components/test-utils/${testUtilType}';`, `import { appendSelector } from '@cloudscape-design/test-utils-core/utils';`, `export { ElementWrapper };`, - ...components.map((componentName) => { - const componentImport = `./${componentName}/index`; + ...testUtilMetaData.map((metaData) => { + const { componentName, relPathtestUtilFile } = metaData; + return ` - import ${pascalCase(componentName)}Wrapper from '${componentImport}'; - export { ${pascalCase(componentName)}Wrapper }; + import ${toWrapper(componentName)} from '${relPathtestUtilFile}'; + export { ${componentName}Wrapper }; `; }), - // we need to redeclare the interface in its original definition, extending a re-export will not work - // https://github.com/microsoft/TypeScript/issues/12607 - `declare module '@cloudscape-design/test-utils-core/dist/${testUtilType}' { - interface ElementWrapper { - ${components.map((componentName) => buildFinderInterface(componentName)).join("\n")} - } - }`, - ...components.map((componentName) => { - // language=TypeScript - return `ElementWrapper.prototype.find${pascalCase(componentName)} = function(selector) { - const rootSelector = \`.$\{${pascalCase(componentName)}Wrapper.rootSelector}\`; - // casting to 'any' is needed to avoid this issue with generics - // https://github.com/microsoft/TypeScript/issues/29132 - return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${pascalCase( - componentName, - )}Wrapper); - };`; - }), - `export { createWrapper as default } from '@cloudscape-design/test-utils-core/${testUtilType}';`, + generateFindersInterfaces({ testUtilMetaData, testUtilType, configs }), + generateFindersImplementations({ testUtilMetaData, configs }), + config.defaultExport, ].join("\n"); } +function generateTestUtilMetaData() { + const metaData = components.reduce((allMetaData, componentFolderName) => { + const absPathComponentFolder = path.resolve(testUtilsSrcDir, componentFolderName); + const relPathtestUtilFile = `./${path.relative(testUtilsSrcDir, absPathComponentFolder)}`; + + const componentNameKebab = componentFolderName; + const componentName = pascalCase(componentNameKebab); + const componentNamePlural = pluralizeComponentName(componentName); + + const componentMetaData = { + componentName, + componentNamePlural, + relPathtestUtilFile, + }; + + return allMetaData.concat(componentMetaData); + }, []); + + return metaData; +} + function compileTypescript() { const config = path.resolve("src/test-utils/tsconfig.json"); - execaSync("tsc", ["-p", config, "--sourceMap"], { stdio: "inherit" }); + execaSync("tsc", ["-p", config, "--sourceMap", "--inlineSources"], { stdio: "inherit" }); } + +function generateSelectorUtils() { + components.forEach((componentName) => { + const domFileName = `./src/test-utils/dom/${componentName}/index.ts`; + const domFileContent = fs.readFileSync(domFileName, "utf-8"); + const selectorsFileName = `./src/test-utils/selectors/${componentName}/index.ts`; + const selectorsFileContent = convertToSelectorUtil.default(domFileContent); + writeSourceFile(selectorsFileName, selectorsFileContent); + }); +} + +function generateIndexFile(testUtilType) { + const testUtilMetaData = generateTestUtilMetaData(testUtilType); + const content = generateIndexFileContent(testUtilType, testUtilMetaData); + writeSourceFile(`./src/test-utils/${testUtilType}/index.ts`, content); +} + +generateSelectorUtils(); +generateIndexFile("dom"); +generateIndexFile("selectors"); +compileTypescript(); diff --git a/src/__tests__/__snapshots__/test-utils-wrappers.test.tsx.snap b/src/__tests__/__snapshots__/test-utils-wrappers.test.tsx.snap new file mode 100644 index 0000000..f7ff663 --- /dev/null +++ b/src/__tests__/__snapshots__/test-utils-wrappers.test.tsx.snap @@ -0,0 +1,229 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Generate test utils ElementWrapper > 'dom' ElementWrapper matches the snapshot 1`] = ` +"import { ElementWrapper } from '@cloudscape-design/test-utils-core/dom'; +import '@cloudscape-design/components/test-utils/dom'; +import { appendSelector } from '@cloudscape-design/test-utils-core/utils'; +export { ElementWrapper }; + + import AvatarWrapper from './avatar'; + export { AvatarWrapper }; + + + import ChatBubbleWrapper from './chat-bubble'; + export { ChatBubbleWrapper }; + + + import LoadingBarWrapper from './loading-bar'; + export { LoadingBarWrapper }; + +declare module '@cloudscape-design/test-utils-core/dist/dom' { + interface ElementWrapper { + + /** + * Returns the wrapper of the first Avatar that matches the specified CSS selector. + * If no CSS selector is specified, returns the wrapper of the first Avatar. + * If no matching Avatar is found, returns \`null\`. + * + * @param {string} [selector] CSS Selector + * @returns {AvatarWrapper | null} + */ + findAvatar(selector?: string): AvatarWrapper | null; + + /** + * Returns an array of Avatar wrapper that matches the specified CSS selector. + * If no CSS selector is specified, returns all of the Avatars inside the current wrapper. + * If no matching Avatar is found, returns an empty array. + * + * @param {string} [selector] CSS Selector + * @returns {Array} + */ + findAllAvatars(selector?: string): Array; + + /** + * Returns the wrapper of the first ChatBubble that matches the specified CSS selector. + * If no CSS selector is specified, returns the wrapper of the first ChatBubble. + * If no matching ChatBubble is found, returns \`null\`. + * + * @param {string} [selector] CSS Selector + * @returns {ChatBubbleWrapper | null} + */ + findChatBubble(selector?: string): ChatBubbleWrapper | null; + + /** + * Returns an array of ChatBubble wrapper that matches the specified CSS selector. + * If no CSS selector is specified, returns all of the ChatBubbles inside the current wrapper. + * If no matching ChatBubble is found, returns an empty array. + * + * @param {string} [selector] CSS Selector + * @returns {Array} + */ + findAllChatBubbles(selector?: string): Array; + + /** + * Returns the wrapper of the first LoadingBar that matches the specified CSS selector. + * If no CSS selector is specified, returns the wrapper of the first LoadingBar. + * If no matching LoadingBar is found, returns \`null\`. + * + * @param {string} [selector] CSS Selector + * @returns {LoadingBarWrapper | null} + */ + findLoadingBar(selector?: string): LoadingBarWrapper | null; + + /** + * Returns an array of LoadingBar wrapper that matches the specified CSS selector. + * If no CSS selector is specified, returns all of the LoadingBars inside the current wrapper. + * If no matching LoadingBar is found, returns an empty array. + * + * @param {string} [selector] CSS Selector + * @returns {Array} + */ + findAllLoadingBars(selector?: string): Array; + } + } + + ElementWrapper.prototype.findAvatar = function(selector) { + const rootSelector = \`.\${AvatarWrapper.rootSelector}\`; + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, AvatarWrapper); + }; + + ElementWrapper.prototype.findAllAvatars = function(selector) { + return this.findAllComponents(AvatarWrapper, selector); + }; + + ElementWrapper.prototype.findChatBubble = function(selector) { + const rootSelector = \`.\${ChatBubbleWrapper.rootSelector}\`; + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ChatBubbleWrapper); + }; + + ElementWrapper.prototype.findAllChatBubbles = function(selector) { + return this.findAllComponents(ChatBubbleWrapper, selector); + }; + + ElementWrapper.prototype.findLoadingBar = function(selector) { + const rootSelector = \`.\${LoadingBarWrapper.rootSelector}\`; + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, LoadingBarWrapper); + }; + + ElementWrapper.prototype.findAllLoadingBars = function(selector) { + return this.findAllComponents(LoadingBarWrapper, selector); + }; +export default function wrapper(root: Element = document.body) { if (document && document.body && !document.body.contains(root)) { console.warn('[AwsUi] [test-utils] provided element is not part of the document body, interactions may work incorrectly')}; return new ElementWrapper(root); }" +`; + +exports[`Generate test utils ElementWrapper > 'selectors' ElementWrapper matches the snapshot 1`] = ` +"import { ElementWrapper } from '@cloudscape-design/test-utils-core/selectors'; +import '@cloudscape-design/components/test-utils/selectors'; +import { appendSelector } from '@cloudscape-design/test-utils-core/utils'; +export { ElementWrapper }; + + import AvatarWrapper from './avatar'; + export { AvatarWrapper }; + + + import ChatBubbleWrapper from './chat-bubble'; + export { ChatBubbleWrapper }; + + + import LoadingBarWrapper from './loading-bar'; + export { LoadingBarWrapper }; + +declare module '@cloudscape-design/test-utils-core/dist/selectors' { + interface ElementWrapper { + + /** + * Returns a wrapper that matches the Avatars with the specified CSS selector. + * If no CSS selector is specified, returns a wrapper that matches Avatars. + * + * @param {string} [selector] CSS Selector + * @returns {AvatarWrapper} + */ + findAvatar(selector?: string): AvatarWrapper; + + /** + * Returns a multi-element wrapper that matches Avatars with the specified CSS selector. + * If no CSS selector is specified, returns a multi-element wrapper that matches Avatars. + * + * @param {string} [selector] CSS Selector + * @returns {MultiElementWrapper} + */ + findAllAvatars(selector?: string): MultiElementWrapper; + + /** + * Returns a wrapper that matches the ChatBubbles with the specified CSS selector. + * If no CSS selector is specified, returns a wrapper that matches ChatBubbles. + * + * @param {string} [selector] CSS Selector + * @returns {ChatBubbleWrapper} + */ + findChatBubble(selector?: string): ChatBubbleWrapper; + + /** + * Returns a multi-element wrapper that matches ChatBubbles with the specified CSS selector. + * If no CSS selector is specified, returns a multi-element wrapper that matches ChatBubbles. + * + * @param {string} [selector] CSS Selector + * @returns {MultiElementWrapper} + */ + findAllChatBubbles(selector?: string): MultiElementWrapper; + + /** + * Returns a wrapper that matches the LoadingBars with the specified CSS selector. + * If no CSS selector is specified, returns a wrapper that matches LoadingBars. + * + * @param {string} [selector] CSS Selector + * @returns {LoadingBarWrapper} + */ + findLoadingBar(selector?: string): LoadingBarWrapper; + + /** + * Returns a multi-element wrapper that matches LoadingBars with the specified CSS selector. + * If no CSS selector is specified, returns a multi-element wrapper that matches LoadingBars. + * + * @param {string} [selector] CSS Selector + * @returns {MultiElementWrapper} + */ + findAllLoadingBars(selector?: string): MultiElementWrapper; + } + } + + ElementWrapper.prototype.findAvatar = function(selector) { + const rootSelector = \`.\${AvatarWrapper.rootSelector}\`; + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, AvatarWrapper); + }; + + ElementWrapper.prototype.findAllAvatars = function(selector) { + return this.findAllComponents(AvatarWrapper, selector); + }; + + ElementWrapper.prototype.findChatBubble = function(selector) { + const rootSelector = \`.\${ChatBubbleWrapper.rootSelector}\`; + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ChatBubbleWrapper); + }; + + ElementWrapper.prototype.findAllChatBubbles = function(selector) { + return this.findAllComponents(ChatBubbleWrapper, selector); + }; + + ElementWrapper.prototype.findLoadingBar = function(selector) { + const rootSelector = \`.\${LoadingBarWrapper.rootSelector}\`; + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, LoadingBarWrapper); + }; + + ElementWrapper.prototype.findAllLoadingBars = function(selector) { + return this.findAllComponents(LoadingBarWrapper, selector); + }; +export default function wrapper(root: string = 'body') { return new ElementWrapper(root); }" +`; diff --git a/src/__tests__/default-props.tsx b/src/__tests__/default-props.tsx index f6b664d..ece249f 100644 --- a/src/__tests__/default-props.tsx +++ b/src/__tests__/default-props.tsx @@ -1,10 +1,16 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import type { AvatarProps } from "../../lib/components"; +import type { AvatarProps, LoadingBarProps } from "../../lib/components"; const avatarProps: AvatarProps = { ariaLabel: "Avatar", }; + +const loadingBarProps: LoadingBarProps = { + variant: "gen-ai", +}; + export const defaultProps = { avatar: avatarProps, + "loading-bar": loadingBarProps, } as const; diff --git a/src/__tests__/test-utils-wrappers.test.tsx b/src/__tests__/test-utils-wrappers.test.tsx new file mode 100644 index 0000000..c0e3141 --- /dev/null +++ b/src/__tests__/test-utils-wrappers.test.tsx @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import fs from "fs"; +import path from "path"; +import { describe, expect, test } from "vitest"; + +describe("Generate test utils ElementWrapper", () => { + const importPaths = [ + { + type: "dom", + relativePath: "../test-utils/dom/index.ts", + }, + { + type: "selectors", + relativePath: "../test-utils/selectors/index.ts", + }, + ] as const; + + test.each(importPaths)("$type ElementWrapper matches the snapshot", ({ relativePath }) => { + const testUtilsPath = path.join(__dirname, relativePath); + const domWrapper = fs.readFileSync(testUtilsPath, "utf8"); + expect(domWrapper).toMatchSnapshot(); + }); +}); diff --git a/src/__tests__/test-utils.test.tsx b/src/__tests__/test-utils.test.tsx new file mode 100644 index 0000000..ec8836b --- /dev/null +++ b/src/__tests__/test-utils.test.tsx @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ComponentType } from "react"; +import { render } from "@testing-library/react"; +import { pascalCase } from "change-case"; +import { describe, expect, test } from "vitest"; + +import * as components from "../../lib/components"; +import createWrapperDom, { ElementWrapper as DomElementWrapper } from "../../lib/components/test-utils/dom"; +import createWrapperSelectors from "../../lib/components/test-utils/selectors"; +import { defaultProps } from "./default-props"; + +const RENDER_COMPONENTS_DEFAULT_PROPS: Record[] = [ + { + "data-testid": "first-item", + "data-name": "first item", + }, + { + "data-testid": "second-item", + "data-name": "second item", + }, +]; + +function renderComponents(componentName: string, props = RENDER_COMPONENTS_DEFAULT_PROPS) { + const Component = components[componentName as keyof typeof components] as ComponentType; + const componentDefaultProps = defaultProps[componentName as keyof typeof defaultProps]; + return render( +
+ {props.map(({ ...customProps }, index) => ( + + ))} +
, + ); +} + +function getComponentSelectors(componentName: string) { + const componentNamePascalCase = pascalCase(componentName); + const findAllRegex = new RegExp(`findAll${componentNamePascalCase}.*`); + + // The same set of selector functions are present in both dom and selectors. + // For this reason, looking into DOM is representative of both groups. + const wrapperPropsList = Object.keys(DomElementWrapper.prototype); + + // Every component has the same set of selector functions. + // For this reason, casting the function names into the Alert component. + const findName = `find${componentNamePascalCase}` as "findAvatar"; + const findAllName = wrapperPropsList.find((selector) => findAllRegex.test(selector)) as "findAllAvatars"; + + return { findName, findAllName }; +} + +describe.each(Object.keys(components))("ElementWrapper selectors for %s component", (componentName) => { + const { findName, findAllName } = getComponentSelectors(componentName); + + describe("dom wrapper", () => { + test(`${findName} returns the first ${componentName}`, () => { + const { container } = renderComponents(componentName); + const wrapper = createWrapperDom(container); + const element = wrapper[findName]()!.getElement(); + + expect(element).toHaveAttribute("data-name", "first item"); + }); + + test(`${findAllName} returns all of the ${componentName} components`, () => { + const { container } = renderComponents(componentName); + const wrapper = createWrapperDom(container); + const elementNameAttributes = wrapper[findAllName]().map((component) => + component!.getElement().getAttribute("data-name"), + ); + + expect(elementNameAttributes).toEqual(["first item", "second item"]); + }); + + test(`${findAllName} returns only the matching ${componentName} components, when a selector is specified`, () => { + const { container } = renderComponents(componentName, [ + { "data-type": "first-type", "data-name": "first item" }, + { "data-type": "second-type", "data-name": "second item" }, + { "data-type": "second-type", "data-name": "third item" }, + ]); + const wrapper = createWrapperDom(container); + const elementNameAttributes = wrapper[findAllName]("[data-type=second-type]").map((component) => + component!.getElement().getAttribute("data-name"), + ); + + expect(elementNameAttributes).toEqual(["second item", "third item"]); + }); + }); + + describe("selectors wrapper", () => { + test(`${findName} returns a selector that matches the ${componentName}`, () => { + const { container } = renderComponents(componentName); + const wrapper = createWrapperSelectors(); + const selector = wrapper[findName]().toSelector(); + const element = container.querySelector(selector); + + expect(element).toHaveAttribute("data-name", "first item"); + }); + + test(`${findAllName} returns a selector that matches the ${componentName} with nth-child index`, () => { + const { container } = renderComponents(componentName); + const wrapper = createWrapperSelectors(); + const selector = wrapper[findAllName]().get(2).toSelector(); + const element = container.querySelector(selector); + + expect(element).toHaveAttribute("data-name", "second item"); + }); + + test(`${findAllName} appends the specified selector to the default ${componentName} selectors`, () => { + const { container } = renderComponents(componentName, [ + { "data-type": "first-type", "data-name": "first item" }, + { "data-type": "second-type", "data-name": "second item" }, + ]); + const wrapper = createWrapperSelectors(); + const firstItemSelector = wrapper[findAllName]("[data-type=second-type]").get(1).toSelector(); + const secondItemSelector = wrapper[findAllName]("[data-type=second-type]").get(2).toSelector(); + const firstElement = container.querySelector(firstItemSelector); + const secondElement = container.querySelector(secondItemSelector); + + expect(firstElement).toBeFalsy(); + expect(secondElement).toBeTruthy(); + }); + }); +}); diff --git a/vite.config.unit.mjs b/vite.config.unit.mjs index 91e03ab..c198ded 100644 --- a/vite.config.unit.mjs +++ b/vite.config.unit.mjs @@ -18,7 +18,7 @@ export default defineConfig({ provider: "v8", reporter: ["clover", "lcov", "html", "json"], include: ["src/**", "lib/components/**"], - exclude: ["**/debug-tools/**", "**/__tests__/**", "**/*.d.ts"], + exclude: ["**/debug-tools/**", "**/__tests__/**", "**/*.d.ts", "test-utils/selectors"], }, }, });