Skip to content

Commit d32d57c

Browse files
build(react): add a script that scaffolds out RTL tests (#11941)
* build(react): add script to scaffold out tests * build(react): add script to scaffold out tests * chore(react): add new lines between tests in script * chore(react): change file name so it's not treated as a test * chore(react): add completion log * fix(react): make script work for skeleton components * chore(react): update word usage * refactor(react): add test to template Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 8824161 commit d32d57c

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed

packages/react/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"browserify-zlib": "^0.2.0",
9696
"browserslist-config-carbon": "^11.0.0",
9797
"css-loader": "^6.5.1",
98+
"enquirer": "^2.3.6",
9899
"fast-glob": "^3.2.7",
99100
"fs-extra": "^10.0.0",
100101
"html-webpack-plugin": "^5.5.0",
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
'use strict';
2+
3+
const prettier = require('prettier');
4+
const CarbonComponents = require('@carbon/react');
5+
const enquirer = require('enquirer');
6+
const fs = require('fs-extra');
7+
const path = require('path');
8+
9+
// Writes test file. Takes in the component's props, name, and whether or not it is a subcomponent (i.e. DataTable or UIShell inner components)
10+
function writeTestFile(props, componentName, isSubComponent) {
11+
let propTests = '';
12+
props.forEach((prop) => {
13+
let test;
14+
if (prop === 'children') {
15+
test = `it('should render children as expected', () => {
16+
render(<${componentName}>add appropriate children</${componentName}>)
17+
18+
expect();
19+
});\n\n`;
20+
} else if (prop === 'className') {
21+
test = `it('should support a custom \`className\` prop on the outermost element', () => {
22+
const { container } = render(<${componentName} className="custom-class" />)
23+
24+
expect(container.firstChild).toHaveClass('custom-class');
25+
});\n\n`;
26+
} else if (
27+
prop === 'onClick' ||
28+
prop === 'onKeyDown' ||
29+
prop === 'onBlur' ||
30+
prop === 'onMouseEnter' ||
31+
prop === 'onMouseLeave' ||
32+
prop === 'onFocus'
33+
) {
34+
test = `it('should call ${prop} when expected', () => {
35+
const ${prop} = jest.fn();
36+
render(<${componentName} ${prop}={${prop}} />)
37+
38+
// perform action to call ${prop}
39+
40+
expect($prop).toHaveBeenCalled();
41+
});\n\n`;
42+
} else {
43+
test = `it('should respect ${prop} prop', () => {
44+
render(<${componentName} ${prop} />);
45+
46+
expect();
47+
});\n\n`;
48+
}
49+
50+
propTests = propTests + test;
51+
});
52+
53+
const testFile = isSubComponent
54+
? `import React from 'react';
55+
import ${componentName} from '../${componentName}';
56+
import userEvent from '@testing-library/user-event';
57+
import { render, screen } from '@testing-library/react';
58+
59+
describe('${componentName}', () => {
60+
describe('renders as expected - Component API', () => {
61+
it('should spread extra props onto outermost element', () => {
62+
const { container } = render(<${componentName} data-testid="test-id" />)
63+
64+
expect(container.firstChild).toHaveAttribute('data-testid', 'test-id');
65+
})
66+
67+
${propTests}
68+
});
69+
70+
describe('behaves as expected', () => {
71+
// Add tests for relevant component behavior. For more information, visit https://github.com/carbon-design-system/carbon/issues/10184#issuecomment-992978122
72+
})
73+
});
74+
`
75+
: `
76+
import React from 'react';
77+
import ${componentName} from './${componentName}';
78+
import userEvent from '@testing-library/user-event';
79+
import { render, screen } from '@testing-library/react';
80+
81+
describe('${componentName}', () => {
82+
describe('renders as expected - Component API', () => {
83+
it('should spread extra props onto outermost element', () => {
84+
const { container } = render(<${componentName} data-testid="test-id" />)
85+
86+
expect(container.firstChild).toHaveAttribute('data-testid', 'test-id');
87+
})
88+
89+
${propTests}
90+
});
91+
92+
describe('behaves as expected', () => {
93+
// Add tests for relevant component behavior. For more information, visit https://github.com/carbon-design-system/carbon/issues/10184#issuecomment-992978122
94+
})
95+
});
96+
`;
97+
98+
return prettier.format(testFile, { parser: 'babel', singleQuote: true });
99+
}
100+
101+
async function main() {
102+
// Carbon components to generate list of choices
103+
const components = Object.keys(CarbonComponents);
104+
105+
// prompts user to select component to write tests for
106+
const response = await enquirer.prompt({
107+
type: 'autocomplete',
108+
name: 'component',
109+
message: 'Which component are you writing tests for?',
110+
limit: 10,
111+
choices: components,
112+
});
113+
114+
const componentName = response.component;
115+
116+
// Gets component props
117+
const propTypes = CarbonComponents[componentName].propTypes;
118+
if (!propTypes) {
119+
console.error('This component might not be suited for test generation :(');
120+
return;
121+
}
122+
123+
const props = Object.keys(propTypes);
124+
125+
const files = await fs.readdir(path.join(__dirname, '../src/components'));
126+
127+
// Generate path to the component's tests
128+
let pathToComponent = '';
129+
let isSubComponent = false;
130+
131+
for await (const file of files) {
132+
let skeletonComponent;
133+
if (componentName.includes('Skeleton')) {
134+
skeletonComponent = componentName.slice(
135+
0,
136+
componentName.length - 'Skeleton'.length
137+
);
138+
}
139+
140+
const subFiles = await fs.readdir(
141+
path.join(__dirname, `../src/components/${file}`)
142+
);
143+
144+
const skeletonFound = subFiles.find(
145+
(subFile) => subFile === `${skeletonComponent}.Skeleton.js`
146+
);
147+
148+
let found;
149+
if (!skeletonFound) {
150+
found = subFiles.find((subFile) => subFile === `${componentName}.js`);
151+
}
152+
153+
const testFolderExists = await fs.pathExists(
154+
path.join(__dirname, `../src/components/${file}/__tests__/`)
155+
);
156+
157+
// if true, component is a "base" component (i.e Button, CheckBox, etc) otherwise it is a sub-component (i.e Table, Header, TableToolBar, etc)
158+
const componentPathExists = await fs.pathExists(
159+
path.join(
160+
__dirname,
161+
`../src/components/${componentName}/${componentName}.js`
162+
)
163+
);
164+
165+
if (!componentPathExists) {
166+
isSubComponent = true;
167+
}
168+
169+
if (skeletonFound && testFolderExists) {
170+
pathToComponent = path.join(
171+
__dirname,
172+
`../src/components/${skeletonComponent}/__tests__/${componentName}-test__copy.js`
173+
);
174+
break;
175+
}
176+
177+
if (skeletonFound && !testFolderExists) {
178+
pathToComponent = path.join(
179+
__dirname,
180+
`../src/components/${skeletonComponent}/${componentName}-test__copy.js`
181+
);
182+
break;
183+
}
184+
185+
if (found && testFolderExists) {
186+
pathToComponent = path.join(
187+
__dirname,
188+
`../src/components/${file}/__tests__/${componentName}-test__copy.js`
189+
);
190+
break;
191+
}
192+
193+
if (found && !testFolderExists) {
194+
pathToComponent = path.join(
195+
__dirname,
196+
`../src/components/${file}/${componentName}-test__copy.js`
197+
);
198+
break;
199+
}
200+
}
201+
202+
const testFile = writeTestFile(props, componentName, isSubComponent);
203+
204+
await fs.writeFile(pathToComponent, testFile);
205+
206+
console.log(`Test file created for ${componentName}! 🎉`);
207+
}
208+
209+
main().catch((error) => {
210+
console.log(error);
211+
process.exit(1);
212+
});

yarn.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,7 @@ __metadata:
21542154
copy-to-clipboard: ^3.3.1
21552155
css-loader: ^6.5.1
21562156
downshift: 5.2.1
2157+
enquirer: ^2.3.6
21572158
fast-glob: ^3.2.7
21582159
flatpickr: 4.6.9
21592160
fs-extra: ^10.0.0

0 commit comments

Comments
 (0)