@@ -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,58 +139,46 @@ 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 } ';` ,
53164 `import '@cloudscape-design/components/test-utils/${ testUtilType } ';` ,
54165 `import { appendSelector } from '@cloudscape-design/test-utils-core/utils';` ,
55166 `export { ElementWrapper };` ,
56- ...components . map ( ( componentName ) => {
57- const componentImport = `./${ componentName } /index` ;
167+ ...testUtilMetaData . map ( ( metaData ) => {
168+ const { componentName, relPathTestUtilFile } = metaData ;
169+
58170 return `
59- import ${ pascalCase ( componentName ) } Wrapper from '${ componentImport } ';
60- export { ${ pascalCase ( componentName ) } Wrapper };
171+ import ${ toWrapper ( componentName ) } from '${ relPathTestUtilFile } ';
172+ export { ${ componentName } Wrapper };
61173 ` ;
62174 } ) ,
63- // we need to redeclare the interface in its original definition, extending a re-export will not work
64- // https://github.com/microsoft/TypeScript/issues/12607
65- `declare module '@cloudscape-design/test-utils-core/dist/${ testUtilType } ' {
66- interface ElementWrapper {
67- ${ components . map ( ( componentName ) => buildFinderInterface ( componentName ) ) . join ( "\n" ) }
68- }
69- }` ,
70- ...components . map ( ( componentName ) => {
71- // language=TypeScript
72- return `ElementWrapper.prototype.find${ pascalCase ( componentName ) } = function(selector) {
73- const rootSelector = \`.$\{${ pascalCase ( componentName ) } Wrapper.rootSelector}\`;
74- // casting to 'any' is needed to avoid this issue with generics
75- // https://github.com/microsoft/TypeScript/issues/29132
76- return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ${ pascalCase (
77- componentName ,
78- ) } Wrapper);
79- };` ;
80- } ) ,
81- `export { createWrapper as default } from '@cloudscape-design/test-utils-core/${ testUtilType } ';` ,
175+ generateFindersInterfaces ( { testUtilMetaData, testUtilType, configs } ) ,
176+ generateFindersImplementations ( { testUtilMetaData, configs } ) ,
177+ config . defaultExport ,
82178 ] . join ( "\n" ) ;
83179}
84180
85181function compileTypescript ( ) {
86182 const config = path . resolve ( "src/test-utils/tsconfig.json" ) ;
87- execaSync ( "tsc" , [ "-p" , config , "--sourceMap" ] , { stdio : "inherit" } ) ;
183+ execaSync ( "tsc" , [ "-p" , config , "--sourceMap" , "--inlineSources" ] , { stdio : "inherit" } ) ;
88184}
0 commit comments