Skip to content

Commit 4a5fb2a

Browse files
authored
feat: Adds findAll finder to test utils (#330)
1 parent 97dee37 commit 4a5fb2a

File tree

5 files changed

+643
-31
lines changed

5 files changed

+643
-31
lines changed

scripts/pluralize.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
const pluralizationMap = {
4+
Board: "Boards",
5+
BoardItem: "BoardItems",
6+
ItemsPalette: "ItemsPalettes",
7+
PaletteItem: "PaletteItems",
8+
};
9+
10+
function pluralizeComponentName(componentName) {
11+
if (!(componentName in pluralizationMap)) {
12+
throw new Error(`Could not find the plural case for ${componentName}.`);
13+
}
14+
15+
return pluralizationMap[componentName];
16+
}
17+
18+
export { pluralizeComponentName };

scripts/test-utils.js

+130-31
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,126 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3+
import { execaSync } from "execa";
4+
import { globbySync } from "globby";
35
import fs from "node:fs";
46
import path from "node:path";
7+
58
import { default as convertToSelectorUtil } from "@cloudscape-design/test-utils-converter";
6-
import { execaSync } from "execa";
7-
import { globbySync } from "globby";
9+
10+
import { pluralizeComponentName } from "./pluralize.js";
811
import { pascalCase, writeSourceFile } from "./utils.js";
912

1013
const components = globbySync(["src/test-utils/dom/**/index.ts", "!src/test-utils/dom/index.ts"]).map((fileName) =>
1114
fileName.replace("src/test-utils/dom/", "").replace("/index.ts", ""),
1215
);
1316

17+
function toWrapper(componentClass) {
18+
return `${componentClass}Wrapper`;
19+
}
20+
21+
const configs = {
22+
common: {
23+
buildFinder: ({ componentName, componentNamePlural }) => `
24+
ElementWrapper.prototype.find${componentName} = function(selector) {
25+
const rootSelector = \`.$\{${toWrapper(componentName)}.rootSelector}\`;
26+
// casting to 'any' is needed to avoid this issue with generics
27+
// https://github.com/microsoft/TypeScript/issues/29132
28+
return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${toWrapper(componentName)});
29+
};
30+
31+
ElementWrapper.prototype.findAll${componentNamePlural} = function(selector) {
32+
return this.findAllComponents(${toWrapper(componentName)}, selector);
33+
};`,
34+
},
35+
dom: {
36+
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); }`,
37+
buildFinderInterface: ({ componentName, componentNamePlural }) => `
38+
/**
39+
* Returns the wrapper of the first ${componentName} that matches the specified CSS selector.
40+
* If no CSS selector is specified, returns the wrapper of the first ${componentName}.
41+
* If no matching ${componentName} is found, returns \`null\`.
42+
*
43+
* @param {string} [selector] CSS Selector
44+
* @returns {${toWrapper(componentName)} | null}
45+
*/
46+
find${componentName}(selector?: string): ${toWrapper(componentName)} | null;
47+
48+
/**
49+
* Returns an array of ${componentName} wrapper that matches the specified CSS selector.
50+
* If no CSS selector is specified, returns all of the ${componentNamePlural} inside the current wrapper.
51+
* If no matching ${componentName} is found, returns an empty array.
52+
*
53+
* @param {string} [selector] CSS Selector
54+
* @returns {Array<${toWrapper(componentName)}>}
55+
*/
56+
findAll${componentNamePlural}(selector?: string): Array<${toWrapper(componentName)}>;`,
57+
},
58+
selectors: {
59+
defaultExport: `export default function wrapper(root: string = 'body') { return new ElementWrapper(root); }`,
60+
buildFinderInterface: ({ componentName, componentNamePlural }) => `
61+
/**
62+
* Returns a wrapper that matches the ${componentNamePlural} with the specified CSS selector.
63+
* If no CSS selector is specified, returns a wrapper that matches ${componentNamePlural}.
64+
*
65+
* @param {string} [selector] CSS Selector
66+
* @returns {${toWrapper(componentName)}}
67+
*/
68+
find${componentName}(selector?: string): ${toWrapper(componentName)};
69+
70+
/**
71+
* Returns a multi-element wrapper that matches ${componentNamePlural} with the specified CSS selector.
72+
* If no CSS selector is specified, returns a multi-element wrapper that matches ${componentNamePlural}.
73+
*
74+
* @param {string} [selector] CSS Selector
75+
* @returns {MultiElementWrapper<${toWrapper(componentName)}>}
76+
*/
77+
findAll${componentNamePlural}(selector?: string): MultiElementWrapper<${toWrapper(componentName)}>;`,
78+
},
79+
};
80+
81+
function generateTestUtilMetaData() {
82+
const testUtilsSrcDir = path.resolve("src/test-utils");
83+
const metaData = components.reduce((allMetaData, componentFolderName) => {
84+
const absPathComponentFolder = path.resolve(testUtilsSrcDir, componentFolderName);
85+
const relPathTestUtilFile = `./${path.relative(testUtilsSrcDir, absPathComponentFolder)}`;
86+
87+
const componentNameKebab = componentFolderName;
88+
const componentName = pascalCase(componentNameKebab);
89+
const componentNamePlural = pluralizeComponentName(componentName);
90+
91+
const componentMetaData = {
92+
componentName,
93+
componentNamePlural,
94+
relPathTestUtilFile,
95+
};
96+
97+
return allMetaData.concat(componentMetaData);
98+
}, []);
99+
100+
return metaData;
101+
}
102+
103+
function generateFindersInterfaces({ testUtilMetaData, testUtilType, configs }) {
104+
const { buildFinderInterface } = configs[testUtilType];
105+
const findersInterfaces = testUtilMetaData.map(buildFinderInterface);
106+
107+
// we need to redeclare the interface in its original definition, extending a re-export will not work
108+
// https://github.com/microsoft/TypeScript/issues/12607
109+
const interfaces = `declare module '@cloudscape-design/test-utils-core/dist/${testUtilType}' {
110+
interface ElementWrapper {
111+
${findersInterfaces.join("\n")}
112+
}
113+
}`;
114+
115+
return interfaces;
116+
}
117+
118+
function generateFindersImplementations({ testUtilMetaData, configs }) {
119+
const { buildFinder } = configs.common;
120+
const findersImplementations = testUtilMetaData.map(buildFinder);
121+
return findersImplementations.join("\n");
122+
}
123+
14124
generateSelectorUtils();
15125
generateDomIndexFile();
16126
generateSelectorsIndexFile();
@@ -29,57 +139,46 @@ function generateSelectorUtils() {
29139
function generateDomIndexFile() {
30140
const content = generateIndexFileContent({
31141
testUtilType: "dom",
32-
buildFinderInterface: (componentName) =>
33-
`find${pascalCase(componentName)}(selector?: string): ${pascalCase(componentName)}Wrapper | null;`,
142+
testUtilMetaData: generateTestUtilMetaData(),
34143
});
35144
writeSourceFile("./src/test-utils/dom/index.ts", content);
36145
}
37146

38147
function generateSelectorsIndexFile() {
39148
const content = generateIndexFileContent({
40149
testUtilType: "selectors",
41-
buildFinderInterface: (componentName) =>
42-
`find${pascalCase(componentName)}(selector?: string): ${pascalCase(componentName)}Wrapper;`,
150+
testUtilMetaData: generateTestUtilMetaData(),
43151
});
44152
writeSourceFile("./src/test-utils/selectors/index.ts", content);
45153
}
46154

47-
function generateIndexFileContent({ testUtilType, buildFinderInterface }) {
155+
function generateIndexFileContent({ testUtilType, testUtilMetaData }) {
156+
const config = configs[testUtilType];
157+
if (config === undefined) {
158+
throw new Error("Unknown test util type");
159+
}
160+
48161
return [
49162
// language=TypeScript
50163
`import { ElementWrapper } from '@cloudscape-design/test-utils-core/${testUtilType}';`,
164+
`import '@cloudscape-design/components/test-utils/${testUtilType}';`,
51165
`import { appendSelector } from '@cloudscape-design/test-utils-core/utils';`,
52166
`export { ElementWrapper };`,
53-
...components.map((componentName) => {
54-
const componentImport = `./${componentName}/index`;
167+
...testUtilMetaData.map((metaData) => {
168+
const { componentName, relPathTestUtilFile } = metaData;
169+
55170
return `
56-
import ${pascalCase(componentName)}Wrapper from '${componentImport}';
57-
export { ${pascalCase(componentName)}Wrapper };
171+
import ${toWrapper(componentName)} from '${relPathTestUtilFile}';
172+
export { ${componentName}Wrapper };
58173
`;
59174
}),
60-
// we need to redeclare the interface in its original definition, extending a re-export will not work
61-
// https://github.com/microsoft/TypeScript/issues/12607
62-
`declare module '@cloudscape-design/test-utils-core/dist/${testUtilType}' {
63-
interface ElementWrapper {
64-
${components.map((componentName) => buildFinderInterface(componentName)).join("\n")}
65-
}
66-
}`,
67-
...components.map((componentName) => {
68-
// language=TypeScript
69-
return `ElementWrapper.prototype.find${pascalCase(componentName)} = function(selector) {
70-
const rootSelector = \`.$\{${pascalCase(componentName)}Wrapper.rootSelector}\`;
71-
// casting to 'any' is needed to avoid this issue with generics
72-
// https://github.com/microsoft/TypeScript/issues/29132
73-
return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${pascalCase(
74-
componentName,
75-
)}Wrapper);
76-
};`;
77-
}),
78-
`export { createWrapper as default } from '@cloudscape-design/test-utils-core/${testUtilType}';`,
175+
generateFindersInterfaces({ testUtilMetaData, testUtilType, configs }),
176+
generateFindersImplementations({ testUtilMetaData, configs }),
177+
config.defaultExport,
79178
].join("\n");
80179
}
81180

82181
function compileTypescript() {
83182
const config = path.resolve("src/test-utils/tsconfig.json");
84-
execaSync("tsc", ["-p", config, "--sourceMap"], { stdio: "inherit" });
183+
execaSync("tsc", ["-p", config, "--sourceMap", "--inlineSources"], { stdio: "inherit" });
85184
}

0 commit comments

Comments
 (0)