diff --git a/package-lock.json b/package-lock.json index 6c00957..5181628 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11399,21 +11399,21 @@ "dev": true }, "node_modules/react-keyed-flatten-children": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-keyed-flatten-children/-/react-keyed-flatten-children-1.3.0.tgz", - "integrity": "sha512-qB7A6n+NHU0x88qTZGAJw6dsqwI941jcRPBB640c/CyWqjPQQ+YUmXOuzPziuHb7iqplM3xksWAbGYwkQT0tXA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-keyed-flatten-children/-/react-keyed-flatten-children-2.2.1.tgz", + "integrity": "sha512-6yBLVO6suN8c/OcJk1mzIrUHdeEzf5rtRVBhxEXAHO49D7SlJ70cG4xrSJrBIAG7MMeQ+H/T151mM2dRDNnFaA==", "dev": true, "dependencies": { - "react-is": "^16.8.6" + "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=15.0.0" } }, "node_modules/react-keyed-flatten-children/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, "node_modules/react-refresh": { diff --git a/scripts/test-utils.js b/scripts/test-utils.js index f41a015..e2f78cc 100644 --- a/scripts/test-utils.js +++ b/scripts/test-utils.js @@ -2,183 +2,31 @@ // SPDX-License-Identifier: Apache-2.0 import { execaSync } from "execa"; import { globbySync } from "globby"; -import fs from "node:fs"; import path from "node:path"; -import { default as convertToSelectorUtil } from "@cloudscape-design/test-utils-converter"; +import { generateTestUtils } from "@cloudscape-design/test-utils-converter"; import { pluralizeComponentName } from "./pluralize.js"; -import { pascalCase, writeSourceFile } from "./utils.js"; +import { pascalCase } 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", ""), -); - -function toWrapper(componentClass) { - return `${componentClass}Wrapper`; -} - -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)}>;`, +const componentNames = globbySync(["src/test-utils/dom/**/index.ts", "!src/test-utils/dom/index.ts"]).map( + (filePath) => { + const fileNameKebabCase = filePath.replace("src/test-utils/dom/", "").replace("/index.ts", ""); + return pascalCase(fileNameKebabCase); }, -}; - -function generateTestUtilMetaData() { - const testUtilsSrcDir = path.resolve("src/test-utils"); - 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 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 generateFindersImplementations({ testUtilMetaData, configs }) { - const { buildFinder } = configs.common; - const findersImplementations = testUtilMetaData.map(buildFinder); - return findersImplementations.join("\n"); -} - -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 generateDomIndexFile() { - const content = generateIndexFileContent({ - testUtilType: "dom", - testUtilMetaData: generateTestUtilMetaData(), - }); - writeSourceFile("./src/test-utils/dom/index.ts", content); -} - -function generateSelectorsIndexFile() { - const content = generateIndexFileContent({ - testUtilType: "selectors", - testUtilMetaData: generateTestUtilMetaData(), - }); - writeSourceFile("./src/test-utils/selectors/index.ts", content); -} - -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 };`, - ...testUtilMetaData.map((metaData) => { - const { componentName, relPathTestUtilFile } = metaData; +); - return ` - import ${toWrapper(componentName)} from '${relPathTestUtilFile}'; - export { ${componentName}Wrapper }; - `; - }), - generateFindersInterfaces({ testUtilMetaData, testUtilType, configs }), - generateFindersImplementations({ testUtilMetaData, configs }), - config.defaultExport, - ].join("\n"); -} +generateTestUtils({ + testUtilsPath: path.resolve("src/test-utils"), + components: componentNames.map((name) => ({ + name, + pluralName: pluralizeComponentName(name), + })), +}); function compileTypescript() { const config = path.resolve("src/test-utils/tsconfig.json"); execaSync("tsc", ["-p", config, "--sourceMap", "--inlineSources"], { stdio: "inherit" }); } + +compileTypescript(); diff --git a/src/__tests__/__snapshots__/test-utils-wrappers.test.tsx.snap b/src/__tests__/__snapshots__/test-utils-wrappers.test.tsx.snap index 0e2e50d..a49e913 100644 --- a/src/__tests__/__snapshots__/test-utils-wrappers.test.tsx.snap +++ b/src/__tests__/__snapshots__/test-utils-wrappers.test.tsx.snap @@ -1,93 +1,118 @@ // 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'; +" +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ElementWrapper } from '@cloudscape-design/test-utils-core/dom'; import { appendSelector } from '@cloudscape-design/test-utils-core/utils'; + export { ElementWrapper }; - import CodeViewWrapper from './code-view'; - export { CodeViewWrapper }; - +import CodeViewWrapper from './code-view'; + + +export { CodeViewWrapper }; + declare module '@cloudscape-design/test-utils-core/dist/dom' { - interface ElementWrapper { - - /** - * Returns the wrapper of the first CodeView that matches the specified CSS selector. - * If no CSS selector is specified, returns the wrapper of the first CodeView. - * If no matching CodeView is found, returns \`null\`. - * - * @param {string} [selector] CSS Selector - * @returns {CodeViewWrapper | null} - */ - findCodeView(selector?: string): CodeViewWrapper | null; - - /** - * Returns an array of CodeView wrapper that matches the specified CSS selector. - * If no CSS selector is specified, returns all of the CodeViews inside the current wrapper. - * If no matching CodeView is found, returns an empty array. - * - * @param {string} [selector] CSS Selector - * @returns {Array} - */ - findAllCodeViews(selector?: string): Array; - } - } - - ElementWrapper.prototype.findCodeView = function(selector) { - const rootSelector = \`.\${CodeViewWrapper.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, CodeViewWrapper); - }; - - ElementWrapper.prototype.findAllCodeViews = function(selector) { - return this.findAllComponents(CodeViewWrapper, 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); }" + interface ElementWrapper { + +/** + * Returns the wrapper of the first CodeView that matches the specified CSS selector. + * If no CSS selector is specified, returns the wrapper of the first CodeView. + * If no matching CodeView is found, returns \`null\`. + * + * @param {string} [selector] CSS Selector + * @returns {CodeViewWrapper | null} + */ +findCodeView(selector?: string): CodeViewWrapper | null; + +/** + * Returns an array of CodeView wrapper that matches the specified CSS selector. + * If no CSS selector is specified, returns all of the CodeViews inside the current wrapper. + * If no matching CodeView is found, returns an empty array. + * + * @param {string} [selector] CSS Selector + * @returns {Array} + */ +findAllCodeViews(selector?: string): Array; + } +} + + +ElementWrapper.prototype.findCodeView = function(selector) { + const rootSelector = \`.\${CodeViewWrapper.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, CodeViewWrapper); +}; + +ElementWrapper.prototype.findAllCodeViews = function(selector) { + return this.findAllComponents(CodeViewWrapper, 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'; +" +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ElementWrapper } from '@cloudscape-design/test-utils-core/selectors'; import { appendSelector } from '@cloudscape-design/test-utils-core/utils'; + export { ElementWrapper }; - import CodeViewWrapper from './code-view'; - export { CodeViewWrapper }; - +import CodeViewWrapper from './code-view'; + + +export { CodeViewWrapper }; + declare module '@cloudscape-design/test-utils-core/dist/selectors' { - interface ElementWrapper { - - /** - * Returns a wrapper that matches the CodeViews with the specified CSS selector. - * If no CSS selector is specified, returns a wrapper that matches CodeViews. - * - * @param {string} [selector] CSS Selector - * @returns {CodeViewWrapper} - */ - findCodeView(selector?: string): CodeViewWrapper; - - /** - * Returns a multi-element wrapper that matches CodeViews with the specified CSS selector. - * If no CSS selector is specified, returns a multi-element wrapper that matches CodeViews. - * - * @param {string} [selector] CSS Selector - * @returns {MultiElementWrapper} - */ - findAllCodeViews(selector?: string): MultiElementWrapper; - } - } - - ElementWrapper.prototype.findCodeView = function(selector) { - const rootSelector = \`.\${CodeViewWrapper.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, CodeViewWrapper); - }; - - ElementWrapper.prototype.findAllCodeViews = function(selector) { - return this.findAllComponents(CodeViewWrapper, selector); - }; -export default function wrapper(root: string = 'body') { return new ElementWrapper(root); }" + interface ElementWrapper { + +/** + * Returns a wrapper that matches the CodeViews with the specified CSS selector. + * If no CSS selector is specified, returns a wrapper that matches CodeViews. + * + * @param {string} [selector] CSS Selector + * @returns {CodeViewWrapper} + */ +findCodeView(selector?: string): CodeViewWrapper; + +/** + * Returns a multi-element wrapper that matches CodeViews with the specified CSS selector. + * If no CSS selector is specified, returns a multi-element wrapper that matches CodeViews. + * + * @param {string} [selector] CSS Selector + * @returns {MultiElementWrapper} + */ +findAllCodeViews(selector?: string): MultiElementWrapper; + } +} + + +ElementWrapper.prototype.findCodeView = function(selector) { + const rootSelector = \`.\${CodeViewWrapper.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, CodeViewWrapper); +}; + +ElementWrapper.prototype.findAllCodeViews = function(selector) { + return this.findAllComponents(CodeViewWrapper, selector); +}; + + +export default function wrapper(root: string = 'body') { + return new ElementWrapper(root); +} +" `;