Skip to content

Commit 035ae6b

Browse files
authored
feat: adds find all selectors to test utils (#3024)
1 parent 2028cba commit 035ae6b

File tree

11 files changed

+5349
-1380
lines changed

11 files changed

+5349
-1380
lines changed

build-tools/tasks/test-utils.js

+79-23
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { pascalCase } = require('change-case');
77
const { default: convertToSelectorUtil } = require('@cloudscape-design/test-utils-converter');
88
const { through, task } = require('../utils/gulp-utils');
99
const { writeFile, listPublicItems } = require('../utils/files');
10+
const { pluralizeComponentName } = require('../utils/pluralize');
1011
const themes = require('../utils/themes');
1112

1213
function toWrapper(componentClass) {
@@ -15,24 +16,92 @@ function toWrapper(componentClass) {
1516

1617
const testUtilsSrcDir = path.resolve('src/test-utils');
1718
const configs = {
19+
common: {
20+
buildFinder: ({ componentName, componentNamePlural }) => `
21+
ElementWrapper.prototype.find${componentName} = function(selector) {
22+
const rootSelector = \`.$\{${toWrapper(componentName)}.rootSelector}\`;
23+
// casting to 'any' is needed to avoid this issue with generics
24+
// https://github.com/microsoft/TypeScript/issues/29132
25+
return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${toWrapper(componentName)});
26+
};
27+
28+
ElementWrapper.prototype.findAll${componentNamePlural} = function(selector) {
29+
return this.findAllComponents(${toWrapper(componentName)}, selector);
30+
};`,
31+
},
1832
dom: {
1933
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); }`,
20-
buildFinderInterface: ({ componentName }) =>
21-
`find${componentName}(selector?: string): ${toWrapper(componentName)} | null;`,
34+
buildFinderInterface: ({ componentName, componentNamePlural }) => `
35+
/**
36+
* Returns the wrapper of the first ${componentName} that matches the specified CSS selector.
37+
* If no CSS selector is specified, returns the wrapper of the first ${componentName}.
38+
* If no matching ${componentName} is found, returns \`null\`.
39+
*
40+
* @param {string} [selector] CSS Selector
41+
* @returns {${toWrapper(componentName)} | null}
42+
*/
43+
find${componentName}(selector?: string): ${toWrapper(componentName)} | null;
44+
45+
/**
46+
* Returns an array of ${componentName} wrapper that matches the specified CSS selector.
47+
* If no CSS selector is specified, returns all of the ${componentNamePlural} inside the current wrapper.
48+
* If no matching ${componentName} is found, returns an empty array.
49+
*
50+
* @param {string} [selector] CSS Selector
51+
* @returns {Array<${toWrapper(componentName)}>}
52+
*/
53+
findAll${componentNamePlural}(selector?: string): Array<${toWrapper(componentName)}>;`,
2254
},
2355
selectors: {
2456
defaultExport: `export default function wrapper(root: string = 'body') { return new ElementWrapper(root); }`,
25-
buildFinderInterface: ({ componentName }) =>
26-
`find${componentName}(selector?: string): ${toWrapper(componentName)};`,
57+
buildFinderInterface: ({ componentName, componentNamePlural }) => `
58+
/**
59+
* Returns a wrapper that matches the ${componentNamePlural} with the specified CSS selector.
60+
* If no CSS selector is specified, returns a wrapper that matches ${componentNamePlural}.
61+
*
62+
* @param {string} [selector] CSS Selector
63+
* @returns {${toWrapper(componentName)}}
64+
*/
65+
find${componentName}(selector?: string): ${toWrapper(componentName)};
66+
67+
/**
68+
* Returns a multi-element wrapper that matches ${componentNamePlural} with the specified CSS selector.
69+
* If no CSS selector is specified, returns a multi-element wrapper that matches ${componentNamePlural}.
70+
*
71+
* @param {string} [selector] CSS Selector
72+
* @returns {MultiElementWrapper<${toWrapper(componentName)}>}
73+
*/
74+
findAll${componentNamePlural}(selector?: string): MultiElementWrapper<${toWrapper(componentName)}>;`,
2775
},
2876
};
2977

78+
function generateFindersInterfaces({ testUtilMetaData, testUtilType, configs }) {
79+
const { buildFinderInterface } = configs[testUtilType];
80+
const findersInterfaces = testUtilMetaData.map(buildFinderInterface);
81+
82+
// we need to redeclare the interface in its original definition, extending a re-export will not work
83+
// https://github.com/microsoft/TypeScript/issues/12607
84+
const interfaces = `declare module '@cloudscape-design/test-utils-core/dist/${testUtilType}' {
85+
interface ElementWrapper {
86+
${findersInterfaces.join('\n')}
87+
}
88+
}`;
89+
90+
return interfaces;
91+
}
92+
93+
function generateFindersImplementations({ testUtilMetaData, configs }) {
94+
const { buildFinder } = configs.common;
95+
const findersImplementations = testUtilMetaData.map(buildFinder);
96+
97+
return findersImplementations.join('\n');
98+
}
99+
30100
function generateIndexFileContent(testUtilType, testUtilMetaData) {
31101
const config = configs[testUtilType];
32102
if (config === undefined) {
33103
throw new Error('Unknown test util type');
34104
}
35-
const { defaultExport, buildFinderInterface } = config;
36105

37106
return [
38107
// language=TypeScript
@@ -47,24 +116,9 @@ function generateIndexFileContent(testUtilType, testUtilMetaData) {
47116
export { ${componentName}Wrapper };
48117
`;
49118
}),
50-
// we need to redeclare the interface in its original definition, extending a re-export will not work
51-
// https://github.com/microsoft/TypeScript/issues/12607
52-
`declare module '@cloudscape-design/test-utils-core/dist/${testUtilType}' {
53-
interface ElementWrapper {
54-
${testUtilMetaData.map(metaData => buildFinderInterface(metaData)).join('\n')}
55-
}
56-
}`,
57-
...testUtilMetaData.map(({ componentName }) => {
58-
const wrapperName = toWrapper(componentName);
59-
// language=TypeScript
60-
return `ElementWrapper.prototype.find${componentName} = function(selector) {
61-
const rootSelector = \`.$\{${wrapperName}.rootSelector}\`;
62-
// casting to 'any' is needed to avoid this issue with generics
63-
// https://github.com/microsoft/TypeScript/issues/29132
64-
return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${wrapperName});
65-
};`;
66-
}),
67-
defaultExport,
119+
generateFindersInterfaces({ testUtilMetaData, testUtilType, configs }),
120+
generateFindersImplementations({ testUtilMetaData, configs }),
121+
config.defaultExport,
68122
].join('\n');
69123
}
70124

@@ -77,9 +131,11 @@ function generateTestUtilMetaData(testUtilType) {
77131

78132
const componentNameKebab = componentFolderName;
79133
const componentName = pascalCase(componentNameKebab);
134+
const componentNamePlural = pluralizeComponentName(componentName);
80135

81136
const componentMetaData = {
82137
componentName,
138+
componentNamePlural,
83139
relPathtestUtilFile,
84140
};
85141

build-tools/utils/pluralize.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
const pluralizationMap = {
4+
Alert: 'Alerts',
5+
AnchorNavigation: 'AnchorNavigations',
6+
Annotation: 'Annotations',
7+
AppLayout: 'AppLayouts',
8+
AreaChart: 'AreaCharts',
9+
AttributeEditor: 'AttributeEditors',
10+
Autosuggest: 'Autosuggests',
11+
Badge: 'Badges',
12+
BarChart: 'BarCharts',
13+
Box: 'Boxes',
14+
BreadcrumbGroup: 'BreadcrumbGroups',
15+
Button: 'Buttons',
16+
ButtonDropdown: 'ButtonDropdowns',
17+
ButtonGroup: 'ButtonGroups',
18+
Calendar: 'Calendars',
19+
Cards: 'Cards',
20+
Checkbox: 'Checkboxes',
21+
CodeEditor: 'CodeEditors',
22+
CollectionPreferences: 'CollectionPreferences',
23+
ColumnLayout: 'ColumnLayouts',
24+
Container: 'Containers',
25+
ContentLayout: 'ContentLayouts',
26+
CopyToClipboard: 'CopyToClipboards',
27+
DateInput: 'DateInputs',
28+
DatePicker: 'DatePickers',
29+
DateRangePicker: 'DateRangePickers',
30+
Drawer: 'Drawers',
31+
ExpandableSection: 'ExpandableSections',
32+
FileDropzone: 'FileDropzones',
33+
FileInput: 'FileInputs',
34+
FileTokenGroup: 'FileTokenGroups',
35+
FileUpload: 'FileUploads',
36+
Flashbar: 'Flashbars',
37+
Form: 'Forms',
38+
FormField: 'FormFields',
39+
Grid: 'Grids',
40+
Header: 'Headers',
41+
HelpPanel: 'HelpPanels',
42+
Hotspot: 'Hotspots',
43+
Icon: 'Icons',
44+
Input: 'Inputs',
45+
KeyValuePairs: 'KeyValuePairs',
46+
LineChart: 'LineCharts',
47+
Link: 'Links',
48+
LiveRegion: 'LiveRegions',
49+
MixedLineBarChart: 'MixedLineBarCharts',
50+
Modal: 'Modals',
51+
Multiselect: 'Multiselects',
52+
Pagination: 'Paginations',
53+
PieChart: 'PieCharts',
54+
Popover: 'Popovers',
55+
ProgressBar: 'ProgressBars',
56+
PromptInput: 'PromptInputs',
57+
PropertyFilter: 'PropertyFilters',
58+
RadioGroup: 'RadioGroups',
59+
S3ResourceSelector: 'S3ResourceSelectors',
60+
SegmentedControl: 'SegmentedControls',
61+
Select: 'Selects',
62+
SideNavigation: 'SideNavigations',
63+
Slider: 'Sliders',
64+
SpaceBetween: 'SpaceBetweens',
65+
Spinner: 'Spinners',
66+
SplitPanel: 'SplitPanels',
67+
StatusIndicator: 'StatusIndicators',
68+
Steps: 'Steps',
69+
Table: 'Tables',
70+
Tabs: 'Tabs',
71+
TagEditor: 'TagEditors',
72+
TextContent: 'TextContents',
73+
TextFilter: 'TextFilters',
74+
Textarea: 'Textareas',
75+
Tiles: 'Tiles',
76+
TimeInput: 'TimeInputs',
77+
Toggle: 'Toggles',
78+
ToggleButton: 'ToggleButtons',
79+
TokenGroup: 'TokenGroups',
80+
TopNavigation: 'TopNavigations',
81+
TutorialPanel: 'TutorialPanels',
82+
Wizard: 'Wizards',
83+
};
84+
85+
function pluralizeComponentName(componentName) {
86+
if (!(componentName in pluralizationMap)) {
87+
throw new Error(`Could not find the plural case for ${componentName}.`);
88+
}
89+
90+
return pluralizationMap[componentName];
91+
}
92+
93+
module.exports = { pluralizeComponentName };

jest.unit.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = mergePresets(cloudscapePreset, {
2121
'environment.js$',
2222
'/internal\\/vendor/',
2323
'<rootDir>/pages',
24+
'test-utils/selectors',
2425
],
2526
coverageThreshold: {
2627
global: {

0 commit comments

Comments
 (0)