@@ -7,12 +7,120 @@ import path from "node:path";
77
88import { default as convertToSelectorUtil } from "@cloudscape-design/test-utils-converter" ;
99
10+ import { pluralizeComponentName } from "./pluralize.js" ;
1011import { pascalCase , writeSourceFile } from "./utils.js" ;
1112
1213const components = globbySync ( [ "src/test-utils/dom/**/index.ts" , "!src/test-utils/dom/index.ts" ] ) . map ( ( fileName ) =>
1314 fileName . replace ( "src/test-utils/dom/" , "" ) . replace ( "/index.ts" , "" ) ,
1415) ;
1516
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+
16124generateSelectorUtils ( ) ;
17125generateDomIndexFile ( ) ;
18126generateSelectorsIndexFile ( ) ;
@@ -31,53 +139,42 @@ function generateSelectorUtils() {
31139function generateDomIndexFile ( ) {
32140 const content = generateIndexFileContent ( {
33141 testUtilType : "dom" ,
34- buildFinderInterface : ( componentName ) =>
35- `find${ pascalCase ( componentName ) } (selector?: string): ${ pascalCase ( componentName ) } Wrapper | null;` ,
142+ testUtilMetaData : generateTestUtilMetaData ( ) ,
36143 } ) ;
37144 writeSourceFile ( "./src/test-utils/dom/index.ts" , content ) ;
38145}
39146
40147function generateSelectorsIndexFile ( ) {
41148 const content = generateIndexFileContent ( {
42149 testUtilType : "selectors" ,
43- buildFinderInterface : ( componentName ) =>
44- `find${ pascalCase ( componentName ) } (selector?: string): ${ pascalCase ( componentName ) } Wrapper;` ,
150+ testUtilMetaData : generateTestUtilMetaData ( ) ,
45151 } ) ;
46152 writeSourceFile ( "./src/test-utils/selectors/index.ts" , content ) ;
47153}
48154
49- 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+
50161 return [
51162 // language=TypeScript
52163 `import { ElementWrapper } from '@cloudscape-design/test-utils-core/${ testUtilType } ';` ,
164+ `import '@cloudscape-design/components/test-utils/${ testUtilType } ';` ,
53165 `import { appendSelector } from '@cloudscape-design/test-utils-core/utils';` ,
54166 `export { ElementWrapper };` ,
55- ...components . map ( ( componentName ) => {
56- const componentImport = `./${ componentName } /index` ;
167+ ...testUtilMetaData . map ( ( metaData ) => {
168+ const { componentName, relPathTestUtilFile } = metaData ;
169+
57170 return `
58- import ${ pascalCase ( componentName ) } Wrapper from '${ componentImport } ';
59- export { ${ pascalCase ( componentName ) } Wrapper };
171+ import ${ toWrapper ( componentName ) } from '${ relPathTestUtilFile } ';
172+ export { ${ componentName } Wrapper };
60173 ` ;
61174 } ) ,
62- // we need to redeclare the interface in its original definition, extending a re-export will not work
63- // https://github.com/microsoft/TypeScript/issues/12607
64- `declare module '@cloudscape-design/test-utils-core/dist/${ testUtilType } ' {
65- interface ElementWrapper {
66- ${ components . map ( ( componentName ) => buildFinderInterface ( componentName ) ) . join ( "\n" ) }
67- }
68- }` ,
69- ...components . map ( ( componentName ) => {
70- // language=TypeScript
71- return `ElementWrapper.prototype.find${ pascalCase ( componentName ) } = function(selector) {
72- const rootSelector = \`.$\{${ pascalCase ( componentName ) } Wrapper.rootSelector}\`;
73- // casting to 'any' is needed to avoid this issue with generics
74- // https://github.com/microsoft/TypeScript/issues/29132
75- return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${ pascalCase (
76- componentName ,
77- ) } Wrapper);
78- };` ;
79- } ) ,
80- `export { createWrapper as default } from '@cloudscape-design/test-utils-core/${ testUtilType } ';` ,
175+ generateFindersInterfaces ( { testUtilMetaData, testUtilType, configs } ) ,
176+ generateFindersImplementations ( { testUtilMetaData, configs } ) ,
177+ config . defaultExport ,
81178 ] . join ( "\n" ) ;
82179}
83180
0 commit comments