import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
createFile,
killPorts,
newProject,
readFile,
runCLI,
runCLIAsync,
runCypressTests,
uniq,
updateFile,
updateJson,
updateProjectConfig,
} from '@nrwl/e2e/utils';
import { readFileSync } from 'fs-extra';
import { join } from 'path';
describe('React Applications', () => {
let proj: string;
beforeEach(() => (proj = newProject()));
afterEach(() => cleanupProject());
it('should be able to generate a react app + lib (with CSR and SSR)', async () => {
const appName = uniq('app');
const libName = uniq('lib');
const libWithNoComponents = uniq('lib');
const logoSvg = readFileSync(join(__dirname, 'logo.svg')).toString();
runCLI(
`generate @nrwl/react:app ${appName} --style=css --bundler=webpack --no-interactive`
);
runCLI(
`generate @nrwl/react:lib ${libName} --style=css --no-interactive --unit-test-runner=jest`
);
runCLI(
`generate @nrwl/react:lib ${libWithNoComponents} --no-interactive --no-component --unit-test-runner=jest`
);
// Libs should not include package.json by default
checkFilesDoNotExist(`libs/${libName}/package.json`);
const mainPath = `apps/${appName}/src/main.tsx`;
updateFile(
mainPath,
`
import '@${proj}/${libWithNoComponents}';
import '@${proj}/${libName}';
${readFile(mainPath)}
`
);
updateFile(`apps/${appName}/src/app/logo.svg`, logoSvg);
updateFile(
`apps/${appName}/src/app/app.tsx`,
`
import { ReactComponent as Logo } from './logo.svg';
import logo from './logo.svg';
import NxWelcome from './nx-welcome';
export function App() {
return (
<>
>
);
}
export default App;
`
);
// Make sure global stylesheets are properly processed.
const stylesPath = `apps/${appName}/src/styles.css`;
updateFile(
stylesPath,
`
.foobar {
background-image: url('/bg.png');
}
`
);
const libTestResults = await runCLIAsync(`test ${libName}`);
expect(libTestResults.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
await testGeneratedApp(appName, {
checkSourceMap: true,
checkStyles: true,
checkLinter: true,
checkE2E: true,
});
// Set up SSR and check app
runCLI(`generate @nrwl/react:setup-ssr ${appName}`);
checkFilesExist(`apps/${appName}/src/main.server.tsx`);
checkFilesExist(`apps/${appName}/server.ts`);
await testGeneratedApp(appName, {
checkSourceMap: false,
checkStyles: false,
checkLinter: false,
checkE2E: true,
});
}, 500000);
it('should be able to use JS and JSX', async () => {
const appName = uniq('app');
const libName = uniq('lib');
const plainJsLib = uniq('jslib');
runCLI(
`generate @nrwl/react:app ${appName} --bundler=webpack --no-interactive --js`
);
runCLI(
`generate @nrwl/react:lib ${libName} --no-interactive --js --unit-test-runner=none`
);
// Make sure plain JS libs can be imported as well.
// There was an issue previously: https://github.com/nrwl/nx/issues/10990
runCLI(
`generate @nrwl/js:lib ${plainJsLib} --js --unit-test-runner=none --bundler=none --compiler=tsc --no-interactive`
);
const mainPath = `apps/${appName}/src/main.js`;
updateFile(
mainPath,
`import '@${proj}/${libName}';\nimport '@${proj}/${plainJsLib}';\n${readFile(
mainPath
)}`
);
await testGeneratedApp(appName, {
checkStyles: true,
checkLinter: false,
checkE2E: false,
});
}, 250_000);
it('should be able to use Vite to build and test apps', async () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(
`generate @nrwl/react:app ${appName} --bundler=vite --no-interactive`
);
runCLI(
`generate @nrwl/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest`
);
// Library generated with Vite
checkFilesExist(`libs/${libName}/vite.config.ts`);
const mainPath = `apps/${appName}/src/main.tsx`;
updateFile(
mainPath,
`
import '@${proj}/${libName}';
${readFile(mainPath)}
`
);
runCLI(`build ${appName}`);
checkFilesExist(`dist/apps/${appName}/index.html`);
const e2eResults = runCLI(`e2e ${appName}-e2e --no-watch`);
expect(e2eResults).toContain('All specs passed!');
expect(await killPorts()).toBeTruthy();
}, 250_000);
async function testGeneratedApp(
appName,
opts: {
checkStyles: boolean;
checkLinter: boolean;
checkE2E: boolean;
checkSourceMap?: boolean;
}
) {
if (opts.checkLinter) {
const lintResults = runCLI(`lint ${appName}`);
expect(lintResults).toContain('All files pass linting.');
}
runCLI(
`build ${appName} --outputHashing none ${
opts.checkSourceMap ? '--sourceMap' : ''
}`
);
const filesToCheck = [
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/main.js`,
];
if (opts.checkSourceMap) {
filesToCheck.push(`dist/apps/${appName}/main.js.map`);
}
if (opts.checkStyles) {
filesToCheck.push(`dist/apps/${appName}/styles.css`);
}
checkFilesExist(...filesToCheck);
if (opts.checkStyles) {
expect(readFile(`dist/apps/${appName}/index.html`)).toContain(
''
);
}
const testResults = await runCLIAsync(`test ${appName}`);
expect(testResults.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
if (opts.checkE2E && runCypressTests()) {
const e2eResults = runCLI(`e2e ${appName}-e2e --no-watch`);
expect(e2eResults).toContain('All specs passed!');
expect(await killPorts()).toBeTruthy();
}
}
});
describe('React Applications: --style option', () => {
// Only create workspace once
beforeAll(() => newProject());
it.each`
style
${'css'}
${'scss'}
${'less'}
${'styl'}
`('should support global and css modules', ({ style }) => {
const appName = uniq('app');
runCLI(
`generate @nrwl/react:app ${appName} --style=${style} --bundler=webpack --no-interactive`
);
// make sure stylePreprocessorOptions works
updateProjectConfig(appName, (config) => {
config.targets.build.options.stylePreprocessorOptions = {
includePaths: ['libs/shared/lib'],
};
return config;
});
updateFile(
`apps/${appName}/src/styles.${style}`,
`@import 'base.${style}';`
);
updateFile(
`apps/${appName}/src/app/app.module.${style}`,
(s) => `@import 'base.${style}';\n${s}`
);
updateFile(
`libs/shared/lib/base.${style}`,
`body { font-family: "Comic Sans MS"; }`
);
runCLI(`build ${appName} --outputHashing none`);
expect(readFile(`dist/apps/${appName}/styles.css`)).toMatch(
/Comic Sans MS/
);
});
});
describe('React Applications and Libs with PostCSS', () => {
let proj: string;
beforeAll(() => (proj = newProject()));
it('should support single path or auto-loading of PostCSS config files', async () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(`g @nrwl/react:app ${appName} --bundler=webpack --no-interactive`);
runCLI(
`g @nrwl/react:lib ${libName} --no-interactive --unit-test-runner=none`
);
const mainPath = `apps/${appName}/src/main.tsx`;
updateFile(
mainPath,
`import '@${proj}/${libName}';\n${readFile(mainPath)}`
);
createFile(
`apps/${appName}/postcss.config.js`,
`
console.log('HELLO FROM APP'); // need this output for e2e test
module.exports = {};
`
);
createFile(
`libs/${libName}/postcss.config.js`,
`
console.log('HELLO FROM LIB'); // need this output for e2e test
module.exports = {};
`
);
let buildResults = await runCLIAsync(`build ${appName}`);
expect(buildResults.combinedOutput).toMatch(/HELLO FROM APP/);
expect(buildResults.combinedOutput).toMatch(/HELLO FROM LIB/);
// Only load app PostCSS config
updateJson(`apps/${appName}/project.json`, (json) => {
json.targets.build.options.postcssConfig = `apps/${appName}/postcss.config.js`;
return json;
});
buildResults = await runCLIAsync(`build ${appName}`);
expect(buildResults.combinedOutput).toMatch(/HELLO FROM APP/);
expect(buildResults.combinedOutput).not.toMatch(/HELLO FROM LIB/);
}, 250_000);
});