chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Next.js Applications - E2E and Snapshots next-env.d.ts should remain the same after a build 1`] = `
|
||||
"/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import './.next/types/routes.d.ts';
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Next.js Applications - E2E and Snapshots next-env.d.ts should remain the same after a build 2`] = `
|
||||
"/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import './.next/types/routes.d.ts';
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Next.js Applications - E2E and Snapshots next-env.d.ts should remain the same after a build 3`] = `
|
||||
"/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Next.js Applications - E2E and Snapshots next-env.d.ts should remain the same after a build 4`] = `
|
||||
"/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
"
|
||||
`;
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
killPorts,
|
||||
newProject,
|
||||
runCLI,
|
||||
runE2ETests,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e-utils';
|
||||
|
||||
describe('Next.js App Router', () => {
|
||||
let proj: string;
|
||||
|
||||
beforeAll(
|
||||
() =>
|
||||
(proj = newProject({
|
||||
packages: ['@nx/next', '@nx/js', '@nx/playwright'],
|
||||
}))
|
||||
);
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should be able to generate and build app with default App Router', async () => {
|
||||
const appName = uniq('app');
|
||||
const jsLib = uniq('tslib');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --e2eTestRunner=playwright --appDir=true`
|
||||
);
|
||||
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`);
|
||||
|
||||
// Turbopack interprets the TS source incorrectly assuming ESM despite package.json type stating module
|
||||
// TODO(Colum): remove this when JS Lib generator switches to ESM
|
||||
updateJson(`${jsLib}/package.json`, (json) => {
|
||||
delete json.type;
|
||||
return json;
|
||||
});
|
||||
|
||||
checkFilesExist(`${appName}/src/app/page.tsx`);
|
||||
checkFilesExist(`${appName}-e2e/src/example.spec.ts`);
|
||||
|
||||
updateFile(
|
||||
`${appName}/src/app/page.tsx`,
|
||||
`
|
||||
import React from 'react';
|
||||
import { ${jsLib} } from '@${proj}/${jsLib}';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<p>{${jsLib}()}</p>
|
||||
);
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${appName}-e2e/src/example.spec.ts`,
|
||||
`
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('has ${jsLib}', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Expect h1 to contain a substring.
|
||||
expect(await page.locator('p').innerText()).toContain('${jsLib}');
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
const lintResults = runCLI(`lint ${appName}`);
|
||||
expect(lintResults).toContain('Successfully ran target lint');
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResults = runCLI(
|
||||
`e2e ${appName}-e2e --configuration=production`
|
||||
);
|
||||
expect(e2eResults).toContain('Successfully ran target e2e for project');
|
||||
await killPorts();
|
||||
}
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -0,0 +1,217 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
createFile,
|
||||
newProject,
|
||||
packageInstall,
|
||||
runCLI,
|
||||
runE2ETests,
|
||||
uniq,
|
||||
updateFile,
|
||||
} from '@nx/e2e-utils';
|
||||
|
||||
describe('NextJs Component Testing', () => {
|
||||
beforeAll(() => {
|
||||
newProject({
|
||||
name: uniq('next-ct'),
|
||||
packages: ['@nx/next', '@nx/cypress'],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
// TODO(nicholas): this is erroring out due to useState error when serving the app in CI. It passes for me locally.
|
||||
xit('should test a NextJs app', () => {
|
||||
const appName = uniq('next-app');
|
||||
createAppWithCt(appName);
|
||||
if (runE2ETests()) {
|
||||
expect(runCLI(`component-test ${appName}`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(jack): re-enable when lodash@4.18.0 assignWith bug is resolved
|
||||
it.skip('should test a NextJs app using babel compiler', () => {
|
||||
const appName = uniq('next-app');
|
||||
createAppWithCt(appName);
|
||||
// add bable compiler to app
|
||||
addBabelSupport(`apps/${appName}`);
|
||||
if (runE2ETests()) {
|
||||
expect(runCLI(`component-test ${appName}`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(jack): re-enable when lodash@4.18.0 assignWith bug is resolved
|
||||
it.skip('should test a NextJs lib using babel compiler', async () => {
|
||||
const libName = uniq('next-lib');
|
||||
createLibWithCt(libName, false);
|
||||
// add bable compiler to lib
|
||||
addBabelSupport(`libs/${libName}`);
|
||||
if (runE2ETests()) {
|
||||
expect(runCLI(`component-test ${libName}`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(jack): re-enable when lodash@4.18.0 assignWith bug is resolved
|
||||
it.skip('should test a NextJs lib', async () => {
|
||||
const libName = uniq('next-lib');
|
||||
createLibWithCt(libName, false);
|
||||
if (runE2ETests()) {
|
||||
expect(runCLI(`component-test ${libName}`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(jack): re-enable when lodash@4.18.0 assignWith bug is resolved
|
||||
it.skip('should test a NextJs buildable lib', async () => {
|
||||
const buildableLibName = uniq('next-buildable-lib');
|
||||
createLibWithCt(buildableLibName, true);
|
||||
if (runE2ETests()) {
|
||||
expect(runCLI(`component-test ${buildableLibName}`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(jack): re-enable when lodash@4.18.0 assignWith bug is resolved
|
||||
it.skip('should test a NextJs server component that uses router', async () => {
|
||||
const lib = uniq('next-lib');
|
||||
createLibWithCtCypress(lib);
|
||||
if (runE2ETests()) {
|
||||
expect(runCLI(`component-test ${lib}`)).toContain('All specs passed!');
|
||||
}
|
||||
}, 600_000);
|
||||
});
|
||||
|
||||
function addBabelSupport(path: string) {
|
||||
updateFile(`${path}/cypress.config.ts`, (content) => {
|
||||
// apply babel compiler
|
||||
return content.replace(
|
||||
'nxComponentTestingPreset(__filename)',
|
||||
'nxComponentTestingPreset(__filename, {compiler: "babel"})'
|
||||
);
|
||||
});
|
||||
|
||||
// Install babel-plugin-istanbul needed for code coverage
|
||||
packageInstall('babel-plugin-istanbul', null, '7.0.0');
|
||||
|
||||
// added needed .babelrc file with defaults
|
||||
createFile(
|
||||
`${path}/.babelrc`,
|
||||
JSON.stringify({ presets: ['next/babel'], plugins: ['istanbul'] })
|
||||
);
|
||||
}
|
||||
|
||||
function createAppWithCt(appName: string) {
|
||||
runCLI(
|
||||
`generate @nx/next:app apps/${appName} --no-interactive --appDir=false --src=false`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/next:component apps/${appName}/components/button --no-interactive`
|
||||
);
|
||||
createFile(
|
||||
`apps/${appName}/public/data.json`,
|
||||
JSON.stringify({ message: 'loaded from app data.json' })
|
||||
);
|
||||
|
||||
updateFile(`apps/${appName}/components/button.tsx`, (content) => {
|
||||
return `import { useEffect, useState } from 'react';
|
||||
|
||||
export interface ButtonProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const data = fetch('/data.json').then((r) => r.json());
|
||||
export default function Button(props: ButtonProps) {
|
||||
const [state, setState] = useState<Record<string, any>>();
|
||||
useEffect(() => {
|
||||
data.then(setState);
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
{state && <pre>{JSON.stringify(state, null, 2)}</pre>}
|
||||
<p className="text-blue-600">Button</p>
|
||||
<button className="text-white bg-black p-4">{props.text}</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:cypress-component-configuration --project=${appName} --generate-tests --no-interactive`
|
||||
);
|
||||
}
|
||||
|
||||
function createLibWithCt(libName: string, buildable: boolean) {
|
||||
runCLI(
|
||||
`generate @nx/next:lib ${libName} --directory=libs/${libName} --buildable=${buildable} --no-interactive`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:component libs/${libName}/src/lib/button --export --no-interactive`
|
||||
);
|
||||
updateFile(`libs/${libName}/src/lib/button.tsx`, (content) => {
|
||||
return `import { useEffect, useState } from 'react';
|
||||
export interface ButtonProps {
|
||||
text: string
|
||||
}
|
||||
|
||||
export function Button(props: ButtonProps) {
|
||||
return <button className="text-white bg-black p-4">{props.text}</button>
|
||||
}
|
||||
|
||||
export default Button;
|
||||
`;
|
||||
});
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:cypress-component-configuration --project=${libName} --generate-tests --no-interactive`
|
||||
);
|
||||
}
|
||||
|
||||
function createLibWithCtCypress(libName: string) {
|
||||
runCLI(`generate @nx/next:lib ${libName} --no-interactive`);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:cypress-component-configuration --project=${libName} --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(`${libName}/src/lib/hello-server.tsx`, () => {
|
||||
return `import { useRouter } from 'next/router';
|
||||
|
||||
export function HelloServer() {
|
||||
useRouter();
|
||||
|
||||
return <h1>Hello Server</h1>;
|
||||
}
|
||||
`;
|
||||
});
|
||||
// Add cypress component test
|
||||
createFile(
|
||||
`${libName}/src/lib/hello-server.cy.tsx`,
|
||||
`import * as Router from 'next/router';
|
||||
import { HelloServer } from './hello-server';
|
||||
|
||||
describe('HelloServer', () => {
|
||||
context('stubbing out \`useRouter\` hook', () => {
|
||||
let router;
|
||||
beforeEach(() => {
|
||||
router = cy.stub();
|
||||
|
||||
cy.stub(Router, 'useRouter').returns(router);
|
||||
});
|
||||
it('should work', () => {
|
||||
cy.mount(<HelloServer />);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
`
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { checkFilesExist, runCLI, uniq } from '@nx/e2e-utils';
|
||||
import {
|
||||
setupNextTest,
|
||||
resetNextEnv,
|
||||
cleanupNextTest,
|
||||
NextTestSetup,
|
||||
} from './next-setup';
|
||||
|
||||
describe('Next.js Applications - Custom Server', () => {
|
||||
let setup: NextTestSetup;
|
||||
|
||||
beforeAll(() => {
|
||||
setup = setupNextTest();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetNextEnv(setup);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetNextEnv(setup);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupNextTest());
|
||||
|
||||
it('should support --custom-server flag (swc)', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
// Check for custom server files added to source
|
||||
checkFilesExist(`${appName}/server/main.ts`);
|
||||
checkFilesExist(`${appName}/.server.swcrc`);
|
||||
|
||||
const result = runCLI(`build ${appName}`);
|
||||
|
||||
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||
|
||||
expect(result).toContain(
|
||||
`Successfully ran target build for project ${appName}`
|
||||
);
|
||||
}, 300_000);
|
||||
|
||||
it('should support --custom-server flag (tsc)', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --swc=false --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
checkFilesExist(`${appName}/server/main.ts`);
|
||||
|
||||
const result = runCLI(`build ${appName}`);
|
||||
|
||||
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||
|
||||
expect(result).toContain(
|
||||
`Successfully ran target build for project ${appName}`
|
||||
);
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
getStrippedEnvironmentVariables,
|
||||
readFile,
|
||||
runCLI,
|
||||
runE2ETests,
|
||||
uniq,
|
||||
} from '@nx/e2e-utils';
|
||||
import {
|
||||
setupNextTest,
|
||||
resetNextEnv,
|
||||
cleanupNextTest,
|
||||
NextTestSetup,
|
||||
} from './next-setup';
|
||||
|
||||
describe('Next.js Applications - E2E and Snapshots', () => {
|
||||
let setup: NextTestSetup;
|
||||
|
||||
beforeAll(() => {
|
||||
setup = setupNextTest();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetNextEnv(setup);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetNextEnv(setup);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupNextTest());
|
||||
|
||||
it('should run e2e-ci test', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --style=css --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
if (runE2ETests('playwright')) {
|
||||
const e2eResults = runCLI(`e2e-ci ${appName}-e2e --verbose`, {
|
||||
verbose: true,
|
||||
env: {
|
||||
...getStrippedEnvironmentVariables(),
|
||||
NX_SKIP_ATOMIZER_VALIDATION: 'true',
|
||||
},
|
||||
});
|
||||
expect(e2eResults).toContain(
|
||||
'Successfully ran target e2e-ci for project'
|
||||
);
|
||||
}
|
||||
}, 600_000);
|
||||
|
||||
it('next-env.d.ts should remain the same after a build', async () => {
|
||||
const appName = uniq('app');
|
||||
const pagesAppName = uniq('pages-app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --style=css --no-interactive --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/next:app ${pagesAppName} --appDir=false --style=css --no-interactive --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
const appDirNextEnv = `${appName}/next-env.d.ts`;
|
||||
const appDirNextEnvContent = readFile(appDirNextEnv);
|
||||
expect(appDirNextEnvContent).toMatchSnapshot();
|
||||
const pagesDirNextEnv = `${pagesAppName}/next-env.d.ts`;
|
||||
const pagesDirNextEnvContent = readFile(pagesDirNextEnv);
|
||||
expect(pagesDirNextEnvContent).toMatchSnapshot();
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${pagesAppName}`);
|
||||
|
||||
const postBuildAppContent = readFile(appDirNextEnv);
|
||||
const postBuildPagesContent = readFile(pagesDirNextEnv);
|
||||
expect(postBuildAppContent).toMatchSnapshot();
|
||||
expect(postBuildPagesContent).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
readFile,
|
||||
runCLI,
|
||||
uniq,
|
||||
updateFile,
|
||||
} from '@nx/e2e-utils';
|
||||
import { checkApp } from './utils';
|
||||
import {
|
||||
setupNextTest,
|
||||
resetNextEnv,
|
||||
cleanupNextTest,
|
||||
NextTestSetup,
|
||||
} from './next-setup';
|
||||
|
||||
describe('Next.js Applications - Generation', () => {
|
||||
let setup: NextTestSetup;
|
||||
|
||||
beforeAll(() => {
|
||||
setup = setupNextTest();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetNextEnv(setup);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetNextEnv(setup);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupNextTest());
|
||||
|
||||
it('should support generating projects with the new name and root format', () => {
|
||||
const { proj } = setup;
|
||||
const appName = uniq('app1');
|
||||
const libName = uniq('@my-org/lib1');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
// check files are generated without the layout directory ("apps/") and
|
||||
// using the project name as the directory when no directory is provided
|
||||
checkFilesExist(`${appName}/src/app/page.tsx`);
|
||||
// check build works
|
||||
expect(runCLI(`build ${appName}`)).toContain(
|
||||
`Successfully ran target build for project ${appName}`
|
||||
);
|
||||
// check tests pass
|
||||
const appTestResult = runCLI(`test ${appName} --passWithNoTests`);
|
||||
expect(appTestResult).toContain(
|
||||
`Successfully ran target test for project ${appName}`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:lib ${libName} --buildable --no-interactive --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
// check files are generated without the layout directory ("libs/") and
|
||||
// using the project name as the directory when no directory is provided
|
||||
checkFilesExist(`${libName}/src/index.ts`);
|
||||
// check build works
|
||||
expect(runCLI(`build ${libName}`)).toContain(
|
||||
`Successfully ran target build for project ${libName}`
|
||||
);
|
||||
}, 600_000);
|
||||
|
||||
it('should build in dev mode without errors', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
checkFilesDoNotExist(`${appName}/.next/build-manifest.json`);
|
||||
checkFilesDoNotExist(`${appName}/.nx-helpers/with-nx.js`);
|
||||
|
||||
expect(() => {
|
||||
runCLI(`build ${appName} --configuration=development`);
|
||||
}).not.toThrow();
|
||||
}, 300_000);
|
||||
|
||||
it('should support --js flag', async () => {
|
||||
const { proj } = setup;
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --js --appDir=false --e2eTestRunner=playwright --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
checkFilesExist(`${appName}/src/pages/index.js`);
|
||||
|
||||
await checkApp(appName, {
|
||||
checkUnitTest: true,
|
||||
checkLint: true,
|
||||
checkE2E: false,
|
||||
});
|
||||
|
||||
// Consume a JS lib
|
||||
const libName = uniq('lib');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:lib ${libName} --no-interactive --style=none --js --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
const mainPath = `${appName}/src/pages/index.js`;
|
||||
updateFile(
|
||||
mainPath,
|
||||
`import '@${proj}/${libName}';\n` + readFile(mainPath)
|
||||
);
|
||||
|
||||
// Update lib to use css modules
|
||||
updateFile(
|
||||
`${libName}/src/lib/${libName}.js`,
|
||||
`
|
||||
import styles from './style.module.css';
|
||||
export function Test() {
|
||||
return <div className={styles.container}>Hello</div>;
|
||||
}
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`${libName}/src/lib/style.module.css`,
|
||||
`
|
||||
.container {}
|
||||
`
|
||||
);
|
||||
|
||||
await checkApp(appName, {
|
||||
checkUnitTest: true,
|
||||
checkLint: true,
|
||||
checkE2E: false,
|
||||
});
|
||||
}, 300_000);
|
||||
|
||||
it('should support --no-swc flag', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --no-swc --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
// Next.js enables SWC when custom .babelrc is not provided.
|
||||
checkFilesExist(`${appName}/.babelrc`);
|
||||
|
||||
await checkApp(appName, {
|
||||
checkUnitTest: false,
|
||||
checkLint: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
createFile,
|
||||
newProject,
|
||||
runCLI,
|
||||
uniq,
|
||||
} from '@nx/e2e-utils';
|
||||
|
||||
describe('Next.js Jest Configuration', () => {
|
||||
let proj: string;
|
||||
|
||||
beforeAll(() => {
|
||||
proj = newProject({
|
||||
packages: ['@nx/next', '@nx/jest', '@nx/playwright'],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should generate app with next/jest configuration', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --style=css --unitTestRunner=jest --projectNameAndRootFormat=as-provided --linter=eslint`
|
||||
);
|
||||
|
||||
createFile(
|
||||
`${appName}/src/app/page.spec.tsx`,
|
||||
`
|
||||
import { render } from '@testing-library/react';
|
||||
import Page from './page';
|
||||
|
||||
describe('Page', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Page />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
const testResult = runCLI(`test ${appName}`);
|
||||
expect(testResult).not.toContain('outdated JSX transform');
|
||||
}, 300_000);
|
||||
|
||||
it('should work with JS projects', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --style=css --unitTestRunner=jest --projectNameAndRootFormat=as-provided --linter=eslint --js`
|
||||
);
|
||||
|
||||
createFile(
|
||||
`${appName}/src/lib/example.spec.js`,
|
||||
`
|
||||
test('example', () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
const testResult = runCLI(`test ${appName}`);
|
||||
expect(testResult).not.toContain('outdated JSX transform');
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -0,0 +1,322 @@
|
||||
import { joinPathFragments, names } from '@nx/devkit';
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
detectPackageManager,
|
||||
getPackageManagerCommand,
|
||||
isNotWindows,
|
||||
killPort,
|
||||
newProject,
|
||||
packageManagerLockFile,
|
||||
readFile,
|
||||
reservePort,
|
||||
runCLI,
|
||||
runCommand,
|
||||
runCommandUntil,
|
||||
tmpProjPath,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e-utils';
|
||||
import { mkdirSync, removeSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { checkApp } from './utils';
|
||||
|
||||
describe('@nx/next (legacy)', () => {
|
||||
let proj: string;
|
||||
let originalEnv: string;
|
||||
let packageManager;
|
||||
|
||||
beforeAll(() => {
|
||||
proj = newProject({
|
||||
packages: ['@nx/next', '@nx/jest', '@nx/eslint', '@nx/playwright'],
|
||||
});
|
||||
packageManager = detectPackageManager(tmpProjPath());
|
||||
originalEnv = process.env.NODE_ENV;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should build app and .next artifacts at the outputPath if provided by the CLI', () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`, {
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
});
|
||||
|
||||
runCLI(`build ${appName} --outputPath="dist/foo"`);
|
||||
|
||||
checkFilesExist('dist/foo/package.json');
|
||||
checkFilesExist('dist/foo/next.config.js');
|
||||
// Next Files
|
||||
checkFilesExist('dist/foo/.next/package.json');
|
||||
checkFilesExist('dist/foo/.next/build-manifest.json');
|
||||
}, 600_000);
|
||||
|
||||
it('should copy relative modules needed by the next.config.js file', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(`generate @nx/next:app ${appName} --style=css --no-interactive`, {
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
});
|
||||
|
||||
updateFile(`${appName}/redirects.js`, 'module.exports = [];');
|
||||
updateFile(
|
||||
`${appName}/nested/headers.js`,
|
||||
`module.exports = require('./headers-2');`
|
||||
);
|
||||
updateFile(`${appName}/nested/headers-2.js`, 'module.exports = [];');
|
||||
updateFile(`${appName}/next.config.js`, (content) => {
|
||||
return `const redirects = require('./redirects');\nconst headers = require('./nested/headers.js');\n${content}`;
|
||||
});
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
checkFilesExist(`dist/${appName}/redirects.js`);
|
||||
checkFilesExist(`dist/${appName}/nested/headers.js`);
|
||||
checkFilesExist(`dist/${appName}/nested/headers-2.js`);
|
||||
}, 120_000);
|
||||
|
||||
it('should build and install pruned lock file', async () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`, {
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
});
|
||||
|
||||
const result = runCLI(`build ${appName} --generateLockfile=true`);
|
||||
expect(result).not.toMatch(/Graph is not consistent/);
|
||||
checkFilesExist(
|
||||
`dist/${appName}/${packageManagerLockFile[packageManager]}`
|
||||
);
|
||||
runCommand(`${getPackageManagerCommand().ciInstall}`, {
|
||||
cwd: joinPathFragments(tmpProjPath(), 'dist', appName),
|
||||
});
|
||||
}, 1_000_000);
|
||||
|
||||
// Reenable this test once we fix Error: Cannot find module 'ajv/dist/compile/codegen'
|
||||
xit('should produce a self-contained artifact in dist', 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`,
|
||||
{
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
}
|
||||
);
|
||||
runCLI(`generate @nx/next:lib ${nextLib} --no-interactive`, {
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
});
|
||||
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`, {
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
});
|
||||
runCLI(
|
||||
`generate @nx/js:lib ${buildableLib} --no-interactive --bundler=vite`,
|
||||
{
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
}
|
||||
);
|
||||
|
||||
// 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');
|
||||
updateJson(join('packages', appName, 'project.json'), (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}/src/pages/index.tsx`;
|
||||
const content = readFile(mainPath);
|
||||
|
||||
updateFile(
|
||||
`packages/${appName}/src/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.${
|
||||
names(nextLib).className
|
||||
})
|
||||
);
|
||||
${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(),
|
||||
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 = await reservePort();
|
||||
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 = await reservePort();
|
||||
runCLI(
|
||||
`generate @nx/workspace:run-commands serve-prod --project ${appName} --cwd=dist/packages/${appName} --command="npx next start --port=${selfContainedPort}"`,
|
||||
{
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
}
|
||||
);
|
||||
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 --custom-server flag (swc)', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`,
|
||||
{ env: { NX_ADD_PLUGINS: 'false' } }
|
||||
);
|
||||
|
||||
// Check for custom server files added to source
|
||||
checkFilesExist(`${appName}/server/main.ts`);
|
||||
checkFilesExist(`${appName}/.server.swcrc`);
|
||||
|
||||
const result = runCLI(`build ${appName}`);
|
||||
|
||||
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||
|
||||
expect(result).toContain(
|
||||
`Successfully ran target build for project ${appName}`
|
||||
);
|
||||
}, 300_000);
|
||||
|
||||
it('should support --custom-server flag (tsc)', async () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --swc=false --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`,
|
||||
{ env: { NX_ADD_PLUGINS: 'false' } }
|
||||
);
|
||||
|
||||
checkFilesExist(`${appName}/server/main.ts`);
|
||||
|
||||
const result = runCLI(`build ${appName}`);
|
||||
|
||||
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||
|
||||
expect(result).toContain(
|
||||
`Successfully ran target build for project ${appName}`
|
||||
);
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
fileExists,
|
||||
getSelectedPackageManager,
|
||||
runCLI,
|
||||
runCreateWorkspace,
|
||||
uniq,
|
||||
} from '@nx/e2e-utils';
|
||||
|
||||
// This test exists as coverage for a previous regression
|
||||
describe('nextjs standalone playwright linting', () => {
|
||||
const packageManager = getSelectedPackageManager() || 'pnpm';
|
||||
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it('should work', async () => {
|
||||
const wsName = uniq('next');
|
||||
const appName = uniq('app');
|
||||
runCreateWorkspace(wsName, {
|
||||
preset: 'nextjs-standalone',
|
||||
style: 'css',
|
||||
nextAppDir: true,
|
||||
nextSrcDir: true,
|
||||
appName,
|
||||
packageManager,
|
||||
e2eTestRunner: 'playwright',
|
||||
});
|
||||
// Ensure that the expected standalone setup is correct
|
||||
expect(fileExists('.eslintrc.base.json')).toBe(false);
|
||||
|
||||
const output = runCLI(`lint ${appName}`);
|
||||
expect(output).toContain(
|
||||
`Successfully ran target lint for project ${appName}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
runCLI,
|
||||
runE2ETests,
|
||||
cleanupProject,
|
||||
newProject,
|
||||
uniq,
|
||||
updateFile,
|
||||
} from '@nx/e2e-utils';
|
||||
|
||||
describe('Next Playwright e2e tests', () => {
|
||||
let projectName;
|
||||
const appName = uniq('pw-next-app');
|
||||
const usedInAppLibName = uniq('pw-next-lib');
|
||||
|
||||
beforeAll(async () => {
|
||||
projectName = newProject({
|
||||
name: uniq('pw-next'),
|
||||
packages: ['@nx/next', '@nx/js', '@nx/playwright'],
|
||||
});
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --e2eTestRunner=playwright --no-interactive`
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should execute e2e tests using playwright', () => {
|
||||
if (runE2ETests('playwright')) {
|
||||
const result = runCLI(`e2e ${appName}-e2e --verbose`);
|
||||
expect(result).toContain(
|
||||
`Successfully ran target e2e for project ${appName}-e2e`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should execute e2e tests using playwright with a library used in the app', () => {
|
||||
runCLI(
|
||||
`generate @nx/js:library ${usedInAppLibName} --unitTestRunner=none --importPath=@mylib --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${appName}-e2e/src/example.spec.ts`,
|
||||
`
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as mylib from '@mylib'
|
||||
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Expect h1 to contain a substring.
|
||||
expect(await page.locator('h1').innerText()).toContain('Welcome');
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
if (runE2ETests('playwright')) {
|
||||
const result = runCLI(`e2e ${appName}-e2e --verbose`);
|
||||
expect(result).toContain(
|
||||
`Successfully ran target e2e for project ${appName}-e2e`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { cleanupProject, newProject } from '@nx/e2e-utils';
|
||||
|
||||
export interface NextTestSetup {
|
||||
proj: string;
|
||||
originalEnv: string;
|
||||
}
|
||||
|
||||
export function setupNextTest(): NextTestSetup {
|
||||
const proj = newProject({
|
||||
packages: [
|
||||
'@nx/next',
|
||||
'@nx/cypress',
|
||||
'@nx/jest',
|
||||
'@nx/eslint',
|
||||
'@nx/playwright',
|
||||
],
|
||||
});
|
||||
const originalEnv = process.env.NODE_ENV;
|
||||
|
||||
return {
|
||||
proj,
|
||||
originalEnv,
|
||||
};
|
||||
}
|
||||
|
||||
export function resetNextEnv(setup: NextTestSetup): void {
|
||||
process.env.NODE_ENV = setup.originalEnv;
|
||||
}
|
||||
|
||||
export function cleanupNextTest(): void {
|
||||
cleanupProject();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
newProject,
|
||||
runCLI,
|
||||
uniq,
|
||||
} from '@nx/e2e-utils';
|
||||
|
||||
// TODO(Colum): Re-enable this test once storybook releases a version with: https://github.com/storybookjs/storybook/pull/32306
|
||||
xdescribe('Next.js Storybook', () => {
|
||||
const appName = uniq('app');
|
||||
beforeAll(() => {
|
||||
newProject({
|
||||
name: 'proj',
|
||||
packageManager: 'npm',
|
||||
packages: ['@nx/next', '@nx/react'],
|
||||
});
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --e2eTestRunner=none --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/next:component ${appName}/components/foo/foo --no-interactive`
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should run a Next.js based Storybook setup', async () => {
|
||||
runCLI(
|
||||
`generate @nx/react:storybook-configuration ${appName} --no-interactive`
|
||||
);
|
||||
runCLI(`build-storybook ${appName}`);
|
||||
checkFilesExist(`${appName}/storybook-static/index.html`);
|
||||
}, 600_000);
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
getPackageManagerCommand,
|
||||
newProject,
|
||||
readFile,
|
||||
runCLI,
|
||||
runCommand,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e-utils';
|
||||
describe('Next TS Solutions', () => {
|
||||
let proj: string;
|
||||
|
||||
beforeAll(() => {
|
||||
proj = newProject({
|
||||
packages: ['@nx/next', '@nx/js', '@nx/vite', '@nx/eslint'],
|
||||
preset: 'ts',
|
||||
});
|
||||
});
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should support importing a esm library', async () => {
|
||||
const appName = uniq('app');
|
||||
const libName = uniq('lib');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --style=css --linter=none --unitTestRunner=none --e2eTestRunner=none`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${libName} --bundler=vite --no-interactive --unit-test-runner=none --skipFormat --linter=eslint`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${appName}/src/app/page.tsx`,
|
||||
`
|
||||
import {${libName}} from '@${proj}/${libName}';
|
||||
${readFile(`${appName}/src/app/page.tsx`)}
|
||||
console.log(${libName}());
|
||||
`
|
||||
);
|
||||
runCLI('sync');
|
||||
|
||||
// Add library to package.json to make sure it is linked (not needed for npm package manager)
|
||||
updateJson(`${appName}/package.json`, (json) => {
|
||||
return {
|
||||
...json,
|
||||
devDependencies: {
|
||||
...(json.devDependencies || {}),
|
||||
[`@${proj}/${libName}`]: 'workspace:*',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
runCommand(`cd ${appName} && ${getPackageManagerCommand().install}`);
|
||||
|
||||
const output = runCLI(`build ${appName}`);
|
||||
expect(output).toContain(
|
||||
`Successfully ran target build for project @${proj}/${appName}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
newProject,
|
||||
rmDist,
|
||||
runCLI,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e-utils';
|
||||
import { join } from 'path';
|
||||
|
||||
describe('Next.js Webpack', () => {
|
||||
let proj: string;
|
||||
let originalEnv: string;
|
||||
|
||||
beforeEach(() => {
|
||||
proj = newProject({
|
||||
packages: ['@nx/next', '@nx/jest', '@nx/playwright'],
|
||||
});
|
||||
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`,
|
||||
{
|
||||
env: {
|
||||
NX_ADD_PLUGINS: 'false',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
checkFilesExist(`${appName}/next.config.js`);
|
||||
updateFile(
|
||||
`${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((rule) => {
|
||||
// Check if the rule is for SVG files
|
||||
if (!/\.(svg)$/i.test('test.svg')) return false;
|
||||
|
||||
// Check if the rule has a 'oneOf' structure
|
||||
if (!rule.oneOf || !Array.isArray(rule.oneOf)) return false;
|
||||
|
||||
// Check each item in 'oneOf' for SVGR loader
|
||||
return rule.oneOf.some((oneOfRule) => {
|
||||
if (!oneOfRule.use) return false;
|
||||
// 'use' might be an object or an array, ensure it's an array for consistency
|
||||
const uses = Array.isArray(oneOfRule.use)
|
||||
? oneOfRule.use
|
||||
: [oneOfRule.use];
|
||||
return uses.some(use => {
|
||||
if (typeof use.loader !== 'string') return false;
|
||||
|
||||
const svgrRegex = new RegExp('@svgr/webpack');
|
||||
return svgrRegex.test(use.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} --webpack`);
|
||||
|
||||
checkFilesExist(`dist/${appName}/next.config.js`);
|
||||
expect(result).toContain('NODE_ENV is production');
|
||||
|
||||
updateFile(
|
||||
`${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} --webpack`);
|
||||
checkFilesExist(`dist/${appName}/next.config.js`);
|
||||
|
||||
// Make sure withNx works with run-commands.
|
||||
updateJson(join(appName, 'project.json'), (json) => {
|
||||
json.targets.build = {
|
||||
command: 'npx next build',
|
||||
outputs: [`{projectRoot}/.next`],
|
||||
options: {
|
||||
cwd: `${appName}`,
|
||||
},
|
||||
};
|
||||
return json;
|
||||
});
|
||||
expect(() => {
|
||||
runCLI(`build ${appName} --webpack`);
|
||||
}).not.toThrow();
|
||||
checkFilesExist(`dist/${appName}/.next/build-manifest.json`);
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
exists,
|
||||
killPorts,
|
||||
readJson,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runE2ETests,
|
||||
} from '@nx/e2e-utils';
|
||||
|
||||
export async function checkApp(
|
||||
appName: string,
|
||||
opts: {
|
||||
checkUnitTest: boolean;
|
||||
checkLint: boolean;
|
||||
checkE2E: boolean;
|
||||
appsDir?: string;
|
||||
useWebpack?: boolean;
|
||||
}
|
||||
) {
|
||||
if (opts.checkLint) {
|
||||
const lintResults = runCLI(`lint ${appName}`);
|
||||
expect(lintResults).toContain('Successfully ran target lint');
|
||||
}
|
||||
|
||||
if (opts.checkUnitTest) {
|
||||
const testResults = await runCLIAsync(`test ${appName}`);
|
||||
expect(testResults.combinedOutput).toContain(
|
||||
'Test Suites: 1 passed, 1 total'
|
||||
);
|
||||
}
|
||||
|
||||
const buildResult = runCLI(
|
||||
`build ${appName}${opts.useWebpack ? ' --webpack' : ''}`
|
||||
);
|
||||
expect(buildResult).toContain(`Successfully ran target build`);
|
||||
// Executor will point to dist, whereas inferred build target will output to `<proj-root>/.next`
|
||||
try {
|
||||
checkFilesExist(`dist/${appName}/.next/build-manifest.json`);
|
||||
} catch {
|
||||
checkFilesExist(`${appName}/.next/build-manifest.json`);
|
||||
}
|
||||
|
||||
// Only the executor will output package.json file to dist
|
||||
if (exists(`dist/${appName}/package.json`)) {
|
||||
const packageJson = readJson(`dist/${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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user