chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:36 +08:00
commit 8e2a6eb840
10194 changed files with 1593658 additions and 0 deletions
@@ -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.
"
`;
+83
View File
@@ -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);
});
+217
View File
@@ -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 />);
});
});
});
`
);
}
+63
View File
@@ -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();
});
});
+153
View File
@@ -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);
});
+65
View File
@@ -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);
});
+322
View File
@@ -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}`
);
});
});
+63
View File
@@ -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`
);
}
});
});
+32
View File
@@ -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();
}
+35
View File
@@ -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);
});
+63
View File
@@ -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}`
);
});
});
+125
View File
@@ -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);
});
+59
View File
@@ -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();
}
}