fix(nextjs): improve e2e test by reusing the project (#18827)
This commit is contained in:
parent
ed89a21e97
commit
34a727926b
@ -36,7 +36,8 @@ pnpm-lock.yaml @nrwl/nx-core-reviewers
|
|||||||
/e2e/react-core/** @nrwl/nx-react-reviewers
|
/e2e/react-core/** @nrwl/nx-react-reviewers
|
||||||
/e2e/react-extensions/** @nrwl/nx-react-reviewers
|
/e2e/react-extensions/** @nrwl/nx-react-reviewers
|
||||||
/packages/next/** @nrwl/nx-react-reviewers
|
/packages/next/** @nrwl/nx-react-reviewers
|
||||||
/e2e/next/** @nrwl/nx-react-reviewers
|
/e2e/next-core/** @nrwl/nx-react-reviewers
|
||||||
|
/e2e/next-extensions/** @nrwl/nx-react-reviewers
|
||||||
/packages/react/plugins/component-testing/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
/packages/react/plugins/component-testing/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
||||||
/packages/react/src/generators/cypress-component-configuration/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
/packages/react/src/generators/cypress-component-configuration/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
||||||
/packages/react/src/generators/component-test/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
/packages/react/src/generators/component-test/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
||||||
|
|||||||
@ -8,6 +8,6 @@ export default {
|
|||||||
globals: {},
|
globals: {},
|
||||||
globalSetup: '../utils/global-setup.ts',
|
globalSetup: '../utils/global-setup.ts',
|
||||||
globalTeardown: '../utils/global-teardown.ts',
|
globalTeardown: '../utils/global-teardown.ts',
|
||||||
displayName: 'e2e-next',
|
displayName: 'e2e-next-core',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
};
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "e2e-next",
|
"name": "e2e-next-core",
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
"sourceRoot": "e2e/next",
|
"sourceRoot": "e2e/next-core/src",
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"targets": {
|
"targets": {
|
||||||
"e2e": {}
|
"e2e": {}
|
||||||
@ -11,13 +11,9 @@ import { checkApp } from './utils';
|
|||||||
describe('Next.js App Router', () => {
|
describe('Next.js App Router', () => {
|
||||||
let proj: string;
|
let proj: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeAll(() => (proj = newProject()));
|
||||||
proj = newProject();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterAll(() => cleanupProject());
|
||||||
cleanupProject();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to generate and build app with default App Router', async () => {
|
it('should be able to generate and build app with default App Router', async () => {
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
43
e2e/next-core/src/next-lock-file.test.ts
Normal file
43
e2e/next-core/src/next-lock-file.test.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { detectPackageManager, joinPathFragments } from '@nx/devkit';
|
||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
cleanupProject,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
newProject,
|
||||||
|
packageManagerLockFile,
|
||||||
|
runCLI,
|
||||||
|
runCommand,
|
||||||
|
tmpProjPath,
|
||||||
|
uniq,
|
||||||
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
|
describe('Next.js Lock File', () => {
|
||||||
|
let proj: string;
|
||||||
|
let originalEnv: string;
|
||||||
|
let packageManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
proj = newProject();
|
||||||
|
packageManager = detectPackageManager(tmpProjPath());
|
||||||
|
originalEnv = process.env.NODE_ENV;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.NODE_ENV = originalEnv;
|
||||||
|
cleanupProject();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build and install pruned lock file', () => {
|
||||||
|
const appName = uniq('app');
|
||||||
|
runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${appName} --generateLockfile=true`);
|
||||||
|
expect(result).not.toMatch(/Graph is not consistent/);
|
||||||
|
checkFilesExist(
|
||||||
|
`dist/apps/${appName}/${packageManagerLockFile[packageManager]}`
|
||||||
|
);
|
||||||
|
runCommand(`${getPackageManagerCommand().ciInstall}`, {
|
||||||
|
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', appName),
|
||||||
|
});
|
||||||
|
}, 1_000_000);
|
||||||
|
});
|
||||||
205
e2e/next-core/src/next-structure.test.ts
Normal file
205
e2e/next-core/src/next-structure.test.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import { removeSync, mkdirSync } from 'fs-extra';
|
||||||
|
import { capitalize } from '@nx/devkit/src/utils/string-utils';
|
||||||
|
import { checkApp } from './utils';
|
||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
cleanupProject,
|
||||||
|
isNotWindows,
|
||||||
|
killPort,
|
||||||
|
newProject,
|
||||||
|
readFile,
|
||||||
|
runCLI,
|
||||||
|
runCommandUntil,
|
||||||
|
tmpProjPath,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
updateProjectConfig,
|
||||||
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
|
describe('Next.js Apps Libs', () => {
|
||||||
|
let proj: string;
|
||||||
|
let originalEnv: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
proj = newProject();
|
||||||
|
originalEnv = process.env.NODE_ENV;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.NODE_ENV = originalEnv;
|
||||||
|
cleanupProject();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate app + libs', async () => {
|
||||||
|
// Remove apps/libs folder and use packages.
|
||||||
|
// Allows us to test other integrated monorepo setup that had a regression.
|
||||||
|
// See: https://github.com/nrwl/nx/issues/16658
|
||||||
|
removeSync(`${tmpProjPath()}/libs`);
|
||||||
|
removeSync(`${tmpProjPath()}/apps`);
|
||||||
|
mkdirSync(`${tmpProjPath()}/packages`);
|
||||||
|
|
||||||
|
const appName = uniq('app');
|
||||||
|
const nextLib = uniq('nextlib');
|
||||||
|
const jsLib = uniq('tslib');
|
||||||
|
const buildableLib = uniq('buildablelib');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false`
|
||||||
|
);
|
||||||
|
runCLI(`generate @nx/next:lib ${nextLib} --no-interactive`);
|
||||||
|
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`);
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/js:lib ${buildableLib} --no-interactive --bundler=vite`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create file in public that should be copied to dist
|
||||||
|
updateFile(`packages/${appName}/public/a/b.txt`, `Hello World!`);
|
||||||
|
|
||||||
|
// Additional assets that should be copied to dist
|
||||||
|
const sharedLib = uniq('sharedLib');
|
||||||
|
await updateProjectConfig(appName, (json) => {
|
||||||
|
json.targets.build.options.assets = [
|
||||||
|
{
|
||||||
|
glob: '**/*',
|
||||||
|
input: `packages/${sharedLib}/src/assets`,
|
||||||
|
output: 'shared/ui',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
updateFile(`packages/${sharedLib}/src/assets/hello.txt`, 'Hello World!');
|
||||||
|
|
||||||
|
// create a css file in node_modules so that it can be imported in a lib
|
||||||
|
// to test that it works as expected
|
||||||
|
updateFile(
|
||||||
|
'node_modules/@nx/next/test-styles.css',
|
||||||
|
'h1 { background-color: red; }'
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`packages/${jsLib}/src/lib/${jsLib}.ts`,
|
||||||
|
`
|
||||||
|
export function jsLib(): string {
|
||||||
|
return 'Hello Nx';
|
||||||
|
};
|
||||||
|
|
||||||
|
// testing whether async-await code in Node / Next.js api routes works as expected
|
||||||
|
export async function jsLibAsync() {
|
||||||
|
return await Promise.resolve('hell0');
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`packages/${buildableLib}/src/lib/${buildableLib}.ts`,
|
||||||
|
`
|
||||||
|
export function buildableLib(): string {
|
||||||
|
return 'Hello Buildable';
|
||||||
|
};
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const mainPath = `packages/${appName}/pages/index.tsx`;
|
||||||
|
const content = readFile(mainPath);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`packages/${appName}/pages/api/hello.ts`,
|
||||||
|
`
|
||||||
|
import { jsLibAsync } from '@${proj}/${jsLib}';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export default async function handler(_: any, res: any) {
|
||||||
|
const value = await jsLibAsync();
|
||||||
|
res.send(value);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
mainPath,
|
||||||
|
`
|
||||||
|
import { jsLib } from '@${proj}/${jsLib}';
|
||||||
|
import { buildableLib } from '@${proj}/${buildableLib}';
|
||||||
|
/* eslint-disable */
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
const TestComponent = dynamic(
|
||||||
|
() => import('@${proj}/${nextLib}').then(d => d.${capitalize(
|
||||||
|
nextLib
|
||||||
|
)})
|
||||||
|
);
|
||||||
|
${content.replace(
|
||||||
|
`</h2>`,
|
||||||
|
`</h2>
|
||||||
|
<div>
|
||||||
|
{jsLib()}
|
||||||
|
{buildableLib()}
|
||||||
|
<TestComponent />
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const e2eTestPath = `packages/${appName}-e2e/src/e2e/app.cy.ts`;
|
||||||
|
const e2eContent = readFile(e2eTestPath);
|
||||||
|
updateFile(
|
||||||
|
e2eTestPath,
|
||||||
|
`
|
||||||
|
${
|
||||||
|
e2eContent +
|
||||||
|
`
|
||||||
|
it('should successfully call async API route', () => {
|
||||||
|
cy.request('/api/hello').its('body').should('include', 'hell0');
|
||||||
|
});
|
||||||
|
`
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await checkApp(appName, {
|
||||||
|
checkUnitTest: true,
|
||||||
|
checkLint: true,
|
||||||
|
checkE2E: isNotWindows(),
|
||||||
|
checkExport: false,
|
||||||
|
appsDir: 'packages',
|
||||||
|
});
|
||||||
|
|
||||||
|
// public and shared assets should both be copied to dist
|
||||||
|
checkFilesExist(
|
||||||
|
`dist/packages/${appName}/public/a/b.txt`,
|
||||||
|
`dist/packages/${appName}/public/shared/ui/hello.txt`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that compiled next config does not contain bad imports
|
||||||
|
const nextConfigPath = `dist/packages/${appName}/next.config.js`;
|
||||||
|
expect(nextConfigPath).not.toContain(`require("../`); // missing relative paths
|
||||||
|
expect(nextConfigPath).not.toContain(`require("nx/`); // dev-only packages
|
||||||
|
expect(nextConfigPath).not.toContain(`require("@nx/`); // dev-only packages
|
||||||
|
|
||||||
|
// Check that `nx serve <app> --prod` works with previous production build (e.g. `nx build <app>`).
|
||||||
|
const prodServePort = 4001;
|
||||||
|
const prodServeProcess = await runCommandUntil(
|
||||||
|
`run ${appName}:serve --prod --port=${prodServePort}`,
|
||||||
|
(output) => {
|
||||||
|
return output.includes(`localhost:${prodServePort}`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the output is self-contained (i.e. can run with its own package.json + node_modules)
|
||||||
|
const selfContainedPort = 3000;
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/workspace:run-commands serve-prod --project ${appName} --cwd=dist/packages/${appName} --command="npx next start --port=${selfContainedPort}"`
|
||||||
|
);
|
||||||
|
const selfContainedProcess = await runCommandUntil(
|
||||||
|
`run ${appName}:serve-prod`,
|
||||||
|
(output) => {
|
||||||
|
return output.includes(`localhost:${selfContainedPort}`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
prodServeProcess.kill();
|
||||||
|
selfContainedProcess.kill();
|
||||||
|
await killPort(prodServePort);
|
||||||
|
await killPort(selfContainedPort);
|
||||||
|
}, 600_000);
|
||||||
|
});
|
||||||
98
e2e/next-core/src/next-webpack.test.ts
Normal file
98
e2e/next-core/src/next-webpack.test.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
cleanupProject,
|
||||||
|
newProject,
|
||||||
|
rmDist,
|
||||||
|
runCLI,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
updateProjectConfig,
|
||||||
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
|
describe('Next.js Webpack', () => {
|
||||||
|
let proj: string;
|
||||||
|
let originalEnv: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
proj = newProject();
|
||||||
|
originalEnv = process.env.NODE_ENV;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.NODE_ENV = originalEnv;
|
||||||
|
cleanupProject();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support custom webpack and run-commands using withNx', async () => {
|
||||||
|
const appName = uniq('app');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`apps/${appName}/next.config.js`,
|
||||||
|
`
|
||||||
|
const { withNx } = require('@nx/next');
|
||||||
|
const nextConfig = {
|
||||||
|
nx: {
|
||||||
|
svgr: false,
|
||||||
|
},
|
||||||
|
webpack: (config, context) => {
|
||||||
|
// Make sure SVGR plugin is disabled if nx.svgr === false (see above)
|
||||||
|
const found = config.module.rules.find(r => {
|
||||||
|
if (!r.test || !r.test.test('test.svg')) return false;
|
||||||
|
if (!r.oneOf || !r.oneOf.use) return false;
|
||||||
|
return r.oneOf.use.some(rr => /svgr/.test(rr.loader));
|
||||||
|
});
|
||||||
|
if (found) throw new Error('Found SVGR plugin');
|
||||||
|
|
||||||
|
console.log('NODE_ENV is', process.env.NODE_ENV);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = withNx(nextConfig);
|
||||||
|
`
|
||||||
|
);
|
||||||
|
// deleting `NODE_ENV` value, so that it's `undefined`, and not `"test"`
|
||||||
|
// by the time it reaches the build executor.
|
||||||
|
// this simulates existing behaviour of running a next.js build executor via Nx
|
||||||
|
delete process.env.NODE_ENV;
|
||||||
|
const result = runCLI(`build ${appName}`);
|
||||||
|
|
||||||
|
checkFilesExist(`dist/apps/${appName}/next.config.js`);
|
||||||
|
expect(result).toContain('NODE_ENV is production');
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`apps/${appName}/next.config.js`,
|
||||||
|
`
|
||||||
|
const { withNx } = require('@nx/next');
|
||||||
|
// Not including "nx" entry should still work.
|
||||||
|
const nextConfig = {};
|
||||||
|
|
||||||
|
module.exports = withNx(nextConfig);
|
||||||
|
`
|
||||||
|
);
|
||||||
|
rmDist();
|
||||||
|
runCLI(`build ${appName}`);
|
||||||
|
checkFilesExist(`dist/apps/${appName}/next.config.js`);
|
||||||
|
|
||||||
|
// Make sure withNx works with run-commands.
|
||||||
|
await updateProjectConfig(appName, (json) => {
|
||||||
|
json.targets.build = {
|
||||||
|
command: 'npx next build',
|
||||||
|
outputs: [`apps/${appName}/.next`],
|
||||||
|
options: {
|
||||||
|
cwd: `apps/${appName}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
expect(() => {
|
||||||
|
runCLI(`build ${appName}`);
|
||||||
|
}).not.toThrow();
|
||||||
|
checkFilesExist(`apps/${appName}/.next/build-manifest.json`);
|
||||||
|
}, 300_000);
|
||||||
|
});
|
||||||
@ -1,217 +1,35 @@
|
|||||||
import { detectPackageManager, joinPathFragments } from '@nx/devkit';
|
|
||||||
import { capitalize } from '@nx/devkit/src/utils/string-utils';
|
|
||||||
import {
|
import {
|
||||||
checkFilesDoNotExist,
|
checkFilesDoNotExist,
|
||||||
checkFilesExist,
|
checkFilesExist,
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
getPackageManagerCommand,
|
|
||||||
isNotWindows,
|
|
||||||
killPort,
|
killPort,
|
||||||
killPorts,
|
killPorts,
|
||||||
newProject,
|
newProject,
|
||||||
packageManagerLockFile,
|
|
||||||
readFile,
|
readFile,
|
||||||
rmDist,
|
|
||||||
runCLI,
|
runCLI,
|
||||||
runCommand,
|
|
||||||
runCommandUntil,
|
runCommandUntil,
|
||||||
tmpProjPath,
|
|
||||||
uniq,
|
uniq,
|
||||||
updateFile,
|
updateFile,
|
||||||
updateProjectConfig,
|
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import { checkApp } from './utils';
|
import { checkApp } from './utils';
|
||||||
import { removeSync, mkdirSync } from 'fs-extra';
|
|
||||||
|
|
||||||
describe('Next.js Applications', () => {
|
describe('Next.js Applications', () => {
|
||||||
let proj: string;
|
let proj: string;
|
||||||
let originalEnv: string;
|
let originalEnv: string;
|
||||||
let packageManager;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeAll(() => {
|
||||||
proj = newProject();
|
proj = newProject();
|
||||||
packageManager = detectPackageManager(tmpProjPath());
|
});
|
||||||
|
beforeEach(() => {
|
||||||
originalEnv = process.env.NODE_ENV;
|
originalEnv = process.env.NODE_ENV;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
process.env.NODE_ENV = originalEnv;
|
process.env.NODE_ENV = originalEnv;
|
||||||
cleanupProject();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate app + libs', async () => {
|
afterAll(() => cleanupProject());
|
||||||
// Remove apps/libs folder and use packages.
|
|
||||||
// Allows us to test other integrated monorepo setup that had a regression.
|
|
||||||
// See: https://github.com/nrwl/nx/issues/16658
|
|
||||||
removeSync(`${tmpProjPath()}/libs`);
|
|
||||||
removeSync(`${tmpProjPath()}/apps`);
|
|
||||||
mkdirSync(`${tmpProjPath()}/packages`);
|
|
||||||
|
|
||||||
const appName = uniq('app');
|
|
||||||
const nextLib = uniq('nextlib');
|
|
||||||
const jsLib = uniq('tslib');
|
|
||||||
const buildableLib = uniq('buildablelib');
|
|
||||||
|
|
||||||
runCLI(
|
|
||||||
`generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false`
|
|
||||||
);
|
|
||||||
runCLI(`generate @nx/next:lib ${nextLib} --no-interactive`);
|
|
||||||
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`);
|
|
||||||
runCLI(
|
|
||||||
`generate @nx/js:lib ${buildableLib} --no-interactive --bundler=vite`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create file in public that should be copied to dist
|
|
||||||
updateFile(`packages/${appName}/public/a/b.txt`, `Hello World!`);
|
|
||||||
|
|
||||||
// Additional assets that should be copied to dist
|
|
||||||
const sharedLib = uniq('sharedLib');
|
|
||||||
await updateProjectConfig(appName, (json) => {
|
|
||||||
json.targets.build.options.assets = [
|
|
||||||
{
|
|
||||||
glob: '**/*',
|
|
||||||
input: `packages/${sharedLib}/src/assets`,
|
|
||||||
output: 'shared/ui',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
updateFile(`packages/${sharedLib}/src/assets/hello.txt`, 'Hello World!');
|
|
||||||
|
|
||||||
// create a css file in node_modules so that it can be imported in a lib
|
|
||||||
// to test that it works as expected
|
|
||||||
updateFile(
|
|
||||||
'node_modules/@nx/next/test-styles.css',
|
|
||||||
'h1 { background-color: red; }'
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`packages/${jsLib}/src/lib/${jsLib}.ts`,
|
|
||||||
`
|
|
||||||
export function jsLib(): string {
|
|
||||||
return 'Hello Nx';
|
|
||||||
};
|
|
||||||
|
|
||||||
// testing whether async-await code in Node / Next.js api routes works as expected
|
|
||||||
export async function jsLibAsync() {
|
|
||||||
return await Promise.resolve('hell0');
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`packages/${buildableLib}/src/lib/${buildableLib}.ts`,
|
|
||||||
`
|
|
||||||
export function buildableLib(): string {
|
|
||||||
return 'Hello Buildable';
|
|
||||||
};
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
const mainPath = `packages/${appName}/pages/index.tsx`;
|
|
||||||
const content = readFile(mainPath);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`packages/${appName}/pages/api/hello.ts`,
|
|
||||||
`
|
|
||||||
import { jsLibAsync } from '@${proj}/${jsLib}';
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export default async function handler(_: any, res: any) {
|
|
||||||
const value = await jsLibAsync();
|
|
||||||
res.send(value);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
mainPath,
|
|
||||||
`
|
|
||||||
import { jsLib } from '@${proj}/${jsLib}';
|
|
||||||
import { buildableLib } from '@${proj}/${buildableLib}';
|
|
||||||
/* eslint-disable */
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
|
|
||||||
const TestComponent = dynamic(
|
|
||||||
() => import('@${proj}/${nextLib}').then(d => d.${capitalize(
|
|
||||||
nextLib
|
|
||||||
)})
|
|
||||||
);
|
|
||||||
${content.replace(
|
|
||||||
`</h2>`,
|
|
||||||
`</h2>
|
|
||||||
<div>
|
|
||||||
{jsLib()}
|
|
||||||
{buildableLib()}
|
|
||||||
<TestComponent />
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const e2eTestPath = `packages/${appName}-e2e/src/e2e/app.cy.ts`;
|
|
||||||
const e2eContent = readFile(e2eTestPath);
|
|
||||||
updateFile(
|
|
||||||
e2eTestPath,
|
|
||||||
`
|
|
||||||
${
|
|
||||||
e2eContent +
|
|
||||||
`
|
|
||||||
it('should successfully call async API route', () => {
|
|
||||||
cy.request('/api/hello').its('body').should('include', 'hell0');
|
|
||||||
});
|
|
||||||
`
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
await checkApp(appName, {
|
|
||||||
checkUnitTest: true,
|
|
||||||
checkLint: true,
|
|
||||||
checkE2E: isNotWindows(),
|
|
||||||
checkExport: false,
|
|
||||||
appsDir: 'packages',
|
|
||||||
});
|
|
||||||
|
|
||||||
// public and shared assets should both be copied to dist
|
|
||||||
checkFilesExist(
|
|
||||||
`dist/packages/${appName}/public/a/b.txt`,
|
|
||||||
`dist/packages/${appName}/public/shared/ui/hello.txt`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check that compiled next config does not contain bad imports
|
|
||||||
const nextConfigPath = `dist/packages/${appName}/next.config.js`;
|
|
||||||
expect(nextConfigPath).not.toContain(`require("../`); // missing relative paths
|
|
||||||
expect(nextConfigPath).not.toContain(`require("nx/`); // dev-only packages
|
|
||||||
expect(nextConfigPath).not.toContain(`require("@nx/`); // dev-only packages
|
|
||||||
|
|
||||||
// Check that `nx serve <app> --prod` works with previous production build (e.g. `nx build <app>`).
|
|
||||||
const prodServePort = 4000;
|
|
||||||
const prodServeProcess = await runCommandUntil(
|
|
||||||
`run ${appName}:serve --prod --port=${prodServePort}`,
|
|
||||||
(output) => {
|
|
||||||
return output.includes(`localhost:${prodServePort}`);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check that the output is self-contained (i.e. can run with its own package.json + node_modules)
|
|
||||||
const selfContainedPort = 3000;
|
|
||||||
runCLI(
|
|
||||||
`generate @nx/workspace:run-commands serve-prod --project ${appName} --cwd=dist/packages/${appName} --command="npx next start --port=${selfContainedPort}"`
|
|
||||||
);
|
|
||||||
const selfContainedProcess = await runCommandUntil(
|
|
||||||
`run ${appName}:serve-prod`,
|
|
||||||
(output) => {
|
|
||||||
return output.includes(`localhost:${selfContainedPort}`);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
prodServeProcess.kill();
|
|
||||||
selfContainedProcess.kill();
|
|
||||||
await killPort(prodServePort);
|
|
||||||
await killPort(selfContainedPort);
|
|
||||||
}, 600_000);
|
|
||||||
|
|
||||||
it('should support generating projects with the new name and root format', () => {
|
it('should support generating projects with the new name and root format', () => {
|
||||||
const appName = uniq('app1');
|
const appName = uniq('app1');
|
||||||
@ -254,20 +72,6 @@ describe('Next.js Applications', () => {
|
|||||||
);
|
);
|
||||||
}, 600_000);
|
}, 600_000);
|
||||||
|
|
||||||
it('should build and install pruned lock file', () => {
|
|
||||||
const appName = uniq('app');
|
|
||||||
runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`);
|
|
||||||
|
|
||||||
const result = runCLI(`build ${appName} --generateLockfile=true`);
|
|
||||||
expect(result).not.toMatch(/Graph is not consistent/);
|
|
||||||
checkFilesExist(
|
|
||||||
`dist/apps/${appName}/${packageManagerLockFile[packageManager]}`
|
|
||||||
);
|
|
||||||
runCommand(`${getPackageManagerCommand().ciInstall}`, {
|
|
||||||
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', appName),
|
|
||||||
});
|
|
||||||
}, 1_000_000);
|
|
||||||
|
|
||||||
it('should build app and .next artifacts at the outputPath if provided by the CLI', () => {
|
it('should build app and .next artifacts at the outputPath if provided by the CLI', () => {
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`);
|
runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`);
|
||||||
@ -348,82 +152,10 @@ describe('Next.js Applications', () => {
|
|||||||
expect(apiData).toContain(`Welcome`);
|
expect(apiData).toContain(`Welcome`);
|
||||||
expect(pageData).toContain(`test value from a file`);
|
expect(pageData).toContain(`test value from a file`);
|
||||||
|
|
||||||
|
await killPort(port);
|
||||||
await killPorts();
|
await killPorts();
|
||||||
}, 300_000);
|
}, 300_000);
|
||||||
|
|
||||||
it('should support custom webpack and run-commands using withNx', async () => {
|
|
||||||
const appName = uniq('app');
|
|
||||||
|
|
||||||
runCLI(
|
|
||||||
`generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false`
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`apps/${appName}/next.config.js`,
|
|
||||||
`
|
|
||||||
const { withNx } = require('@nx/next');
|
|
||||||
const nextConfig = {
|
|
||||||
nx: {
|
|
||||||
svgr: false,
|
|
||||||
},
|
|
||||||
webpack: (config, context) => {
|
|
||||||
// Make sure SVGR plugin is disabled if nx.svgr === false (see above)
|
|
||||||
const found = config.module.rules.find(r => {
|
|
||||||
if (!r.test || !r.test.test('test.svg')) return false;
|
|
||||||
if (!r.oneOf || !r.oneOf.use) return false;
|
|
||||||
return r.oneOf.use.some(rr => /svgr/.test(rr.loader));
|
|
||||||
});
|
|
||||||
if (found) throw new Error('Found SVGR plugin');
|
|
||||||
|
|
||||||
console.log('NODE_ENV is', process.env.NODE_ENV);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = withNx(nextConfig);
|
|
||||||
`
|
|
||||||
);
|
|
||||||
// deleting `NODE_ENV` value, so that it's `undefined`, and not `"test"`
|
|
||||||
// by the time it reaches the build executor.
|
|
||||||
// this simulates existing behaviour of running a next.js build executor via Nx
|
|
||||||
delete process.env.NODE_ENV;
|
|
||||||
const result = runCLI(`build ${appName}`);
|
|
||||||
|
|
||||||
checkFilesExist(`dist/apps/${appName}/next.config.js`);
|
|
||||||
expect(result).toContain('NODE_ENV is production');
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`apps/${appName}/next.config.js`,
|
|
||||||
`
|
|
||||||
const { withNx } = require('@nx/next');
|
|
||||||
// Not including "nx" entry should still work.
|
|
||||||
const nextConfig = {};
|
|
||||||
|
|
||||||
module.exports = withNx(nextConfig);
|
|
||||||
`
|
|
||||||
);
|
|
||||||
rmDist();
|
|
||||||
runCLI(`build ${appName}`);
|
|
||||||
checkFilesExist(`dist/apps/${appName}/next.config.js`);
|
|
||||||
|
|
||||||
// Make sure withNx works with run-commands.
|
|
||||||
await updateProjectConfig(appName, (json) => {
|
|
||||||
json.targets.build = {
|
|
||||||
command: 'npx next build',
|
|
||||||
outputs: [`apps/${appName}/.next`],
|
|
||||||
options: {
|
|
||||||
cwd: `apps/${appName}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
expect(() => {
|
|
||||||
runCLI(`build ${appName}`);
|
|
||||||
}).not.toThrow();
|
|
||||||
checkFilesExist(`apps/${appName}/.next/build-manifest.json`);
|
|
||||||
}, 300_000);
|
|
||||||
|
|
||||||
it('should build in dev mode without errors', async () => {
|
it('should build in dev mode without errors', async () => {
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
|
|
||||||
@ -563,6 +295,7 @@ describe('Next.js Applications', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
selfContainedProcess.kill();
|
selfContainedProcess.kill();
|
||||||
|
await killPort(port);
|
||||||
await killPorts();
|
await killPorts();
|
||||||
}, 300_000);
|
}, 300_000);
|
||||||
});
|
});
|
||||||
13
e2e/next-extensions/jest.config.ts
Normal file
13
e2e/next-extensions/jest.config.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'e2e-next-extensions',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||||
|
maxWorkers: 1,
|
||||||
|
globals: {},
|
||||||
|
globalSetup: '../utils/global-setup.ts',
|
||||||
|
globalTeardown: '../utils/global-teardown.ts',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
};
|
||||||
10
e2e/next-extensions/project.json
Normal file
10
e2e/next-extensions/project.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "e2e-next-extensions",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "e2e/next-extensions/src",
|
||||||
|
"projectType": "application",
|
||||||
|
"targets": {
|
||||||
|
"e2e": {}
|
||||||
|
},
|
||||||
|
"implicitDependencies": ["next"]
|
||||||
|
}
|
||||||
@ -10,13 +10,9 @@ import { checkApp } from './utils';
|
|||||||
describe('Next.js Experimental Features', () => {
|
describe('Next.js Experimental Features', () => {
|
||||||
let proj: string;
|
let proj: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeAll(() => (proj = newProject()));
|
||||||
proj = newProject();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterAll(() => cleanupProject());
|
||||||
cleanupProject();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to define server actions in workspace libs', async () => {
|
it('should be able to define server actions in workspace libs', async () => {
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
@ -9,17 +9,12 @@ import {
|
|||||||
updateJson,
|
updateJson,
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
describe('Next.js Applications', () => {
|
describe('Next.js Storybook', () => {
|
||||||
let proj: string;
|
let proj: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeAll(() => (proj = newProject({ name: 'proj', packageManager: 'npm' })));
|
||||||
proj = newProject({
|
|
||||||
name: 'proj',
|
|
||||||
packageManager: 'npm',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => cleanupProject());
|
afterAll(() => cleanupProject());
|
||||||
|
|
||||||
it('should run a Next.js based Storybook setup', async () => {
|
it('should run a Next.js based Storybook setup', async () => {
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { cleanupProject, newProject, runCLI, uniq } from '@nx/e2e/utils';
|
import { cleanupProject, newProject, runCLI, uniq } from '@nx/e2e/utils';
|
||||||
import { checkApp } from './utils';
|
import { checkApp } from './utils';
|
||||||
|
|
||||||
describe('Next.js apps', () => {
|
describe('Next.js Styles', () => {
|
||||||
let originalEnv: string;
|
let originalEnv: string;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
55
e2e/next-extensions/src/utils.ts
Normal file
55
e2e/next-extensions/src/utils.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
killPorts,
|
||||||
|
readJson,
|
||||||
|
runCLI,
|
||||||
|
runCLIAsync,
|
||||||
|
runE2ETests,
|
||||||
|
} from '../../utils';
|
||||||
|
|
||||||
|
export async function checkApp(
|
||||||
|
appName: string,
|
||||||
|
opts: {
|
||||||
|
checkUnitTest: boolean;
|
||||||
|
checkLint: boolean;
|
||||||
|
checkE2E: boolean;
|
||||||
|
checkExport: boolean;
|
||||||
|
appsDir?: string;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const appsDir = opts.appsDir ?? 'apps';
|
||||||
|
|
||||||
|
if (opts.checkLint) {
|
||||||
|
const lintResults = runCLI(`lint ${appName}`);
|
||||||
|
expect(lintResults).toContain('All files pass linting.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.checkUnitTest) {
|
||||||
|
const testResults = await runCLIAsync(`test ${appName}`);
|
||||||
|
expect(testResults.combinedOutput).toContain(
|
||||||
|
'Test Suites: 1 passed, 1 total'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildResult = runCLI(`build ${appName}`);
|
||||||
|
expect(buildResult).toContain(`Successfully ran target build`);
|
||||||
|
checkFilesExist(`dist/${appsDir}/${appName}/.next/build-manifest.json`);
|
||||||
|
|
||||||
|
const packageJson = readJson(`dist/${appsDir}/${appName}/package.json`);
|
||||||
|
expect(packageJson.dependencies.react).toBeDefined();
|
||||||
|
expect(packageJson.dependencies['react-dom']).toBeDefined();
|
||||||
|
expect(packageJson.dependencies.next).toBeDefined();
|
||||||
|
|
||||||
|
if (opts.checkE2E && runE2ETests()) {
|
||||||
|
const e2eResults = runCLI(
|
||||||
|
`e2e ${appName}-e2e --no-watch --configuration=production`
|
||||||
|
);
|
||||||
|
expect(e2eResults).toContain('Successfully ran target e2e for project');
|
||||||
|
expect(await killPorts()).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.checkExport) {
|
||||||
|
runCLI(`export ${appName}`);
|
||||||
|
checkFilesExist(`dist/${appsDir}/${appName}/exported/index.html`);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
e2e/next-extensions/tsconfig.json
Normal file
13
e2e/next-extensions/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node", "jest"]
|
||||||
|
},
|
||||||
|
"include": [],
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
e2e/next-extensions/tsconfig.spec.json
Normal file
20
e2e/next-extensions/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.test.jsx",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"jest.config.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user