feat(testing): add jest create-nodes plugin (#20045)
This commit is contained in:
parent
595a7743b6
commit
5d9b4c5224
@ -6,13 +6,16 @@ describe('Jest root projects', () => {
|
|||||||
|
|
||||||
describe('angular', () => {
|
describe('angular', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
newProject({ packages: ['@nx/angular', '@nx/react'] });
|
newProject({
|
||||||
|
packages: ['@nx/angular'],
|
||||||
|
unsetProjectNameAndRootFormat: false,
|
||||||
|
});
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/angular:app ${myapp} --directory . --rootProject --projectNameAndRootFormat as-provided --no-interactive`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should test root level app projects', async () => {
|
it('should test root level app projects', async () => {
|
||||||
runCLI(
|
|
||||||
`generate @nx/angular:app ${myapp} --rootProject=true --no-interactive`
|
|
||||||
);
|
|
||||||
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
||||||
expect(rootProjectTestResults.combinedOutput).toContain(
|
expect(rootProjectTestResults.combinedOutput).toContain(
|
||||||
'Test Suites: 1 passed, 1 total'
|
'Test Suites: 1 passed, 1 total'
|
||||||
@ -20,9 +23,8 @@ describe('Jest root projects', () => {
|
|||||||
}, 300_000);
|
}, 300_000);
|
||||||
|
|
||||||
it('should add lib project and tests should still work', async () => {
|
it('should add lib project and tests should still work', async () => {
|
||||||
runCLI(`generate @nx/angular:lib ${mylib} --no-interactive`);
|
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/angular:component ${mylib} --export --standalone --project=${mylib} --no-interactive`
|
`generate @nx/angular:lib ${mylib} --projectNameAndRootFormat as-provided --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
const libProjectTestResults = await runCLIAsync(`test ${mylib}`);
|
const libProjectTestResults = await runCLIAsync(`test ${mylib}`);
|
||||||
@ -41,12 +43,16 @@ describe('Jest root projects', () => {
|
|||||||
|
|
||||||
describe('react', () => {
|
describe('react', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
newProject();
|
newProject({
|
||||||
|
packages: ['@nx/react'],
|
||||||
|
unsetProjectNameAndRootFormat: false,
|
||||||
|
});
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/react:app ${myapp} --directory . --rootProject --projectNameAndRootFormat as-provided`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should test root level app projects', async () => {
|
it('should test root level app projects', async () => {
|
||||||
runCLI(`generate @nx/react:app ${myapp} --rootProject=true`);
|
|
||||||
|
|
||||||
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
||||||
|
|
||||||
expect(rootProjectTestResults.combinedOutput).toContain(
|
expect(rootProjectTestResults.combinedOutput).toContain(
|
||||||
@ -55,7 +61,9 @@ describe('Jest root projects', () => {
|
|||||||
}, 300_000);
|
}, 300_000);
|
||||||
|
|
||||||
it('should add lib project and tests should still work', async () => {
|
it('should add lib project and tests should still work', async () => {
|
||||||
runCLI(`generate @nx/react:lib ${mylib} --unitTestRunner=jest`);
|
runCLI(
|
||||||
|
`generate @nx/react:lib ${mylib} --unitTestRunner=jest --projectNameAndRootFormat as-provided`
|
||||||
|
);
|
||||||
|
|
||||||
const libProjectTestResults = await runCLIAsync(`test ${mylib}`);
|
const libProjectTestResults = await runCLIAsync(`test ${mylib}`);
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,9 @@ describe('@nx/next/plugin', () => {
|
|||||||
let appName: string;
|
let appName: string;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
project = newProject();
|
project = newProject({
|
||||||
|
packages: ['@nx/next'],
|
||||||
|
});
|
||||||
appName = uniq('app');
|
appName = uniq('app');
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/next:app ${appName} --project-name-and-root-format=as-provided --no-interactive`,
|
`generate @nx/next:app ${appName} --project-name-and-root-format=as-provided --no-interactive`,
|
||||||
|
|||||||
@ -24,7 +24,7 @@ describe('@nx/workspace:convert-to-monorepo', () => {
|
|||||||
|
|
||||||
afterEach(() => cleanupProject());
|
afterEach(() => cleanupProject());
|
||||||
|
|
||||||
it('should convert a standalone project to a monorepo', async () => {
|
it('should convert a standalone webpack and jest react project to a monorepo', async () => {
|
||||||
const reactApp = uniq('reactapp');
|
const reactApp = uniq('reactapp');
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/react:app ${reactApp} --rootProject=true --bundler=webpack --unitTestRunner=jest --e2eTestRunner=cypress --no-interactive`
|
`generate @nx/react:app ${reactApp} --rootProject=true --bundler=webpack --unitTestRunner=jest --e2eTestRunner=cypress --no-interactive`
|
||||||
@ -43,6 +43,26 @@ describe('@nx/workspace:convert-to-monorepo', () => {
|
|||||||
expect(() => runCLI(`lint e2e`)).not.toThrow();
|
expect(() => runCLI(`lint e2e`)).not.toThrow();
|
||||||
expect(() => runCLI(`e2e e2e`)).not.toThrow();
|
expect(() => runCLI(`e2e e2e`)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be convert a standalone vite and playwright react project to a monorepo', async () => {
|
||||||
|
const reactApp = uniq('reactapp');
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/react:app ${reactApp} --rootProject=true --bundler=vite --unitTestRunner vitest --e2eTestRunner=playwright --no-interactive`
|
||||||
|
);
|
||||||
|
|
||||||
|
runCLI('generate @nx/workspace:convert-to-monorepo --no-interactive');
|
||||||
|
|
||||||
|
checkFilesExist(
|
||||||
|
`apps/${reactApp}/src/main.tsx`,
|
||||||
|
`apps/e2e/playwright.config.ts`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => runCLI(`build ${reactApp}`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`test ${reactApp}`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`lint ${reactApp}`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`lint e2e`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`e2e e2e`)).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Workspace Tests', () => {
|
describe('Workspace Tests', () => {
|
||||||
|
|||||||
@ -11,7 +11,10 @@ describe('Webpack Plugin (PCv3)', () => {
|
|||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
originalPcv3 = process.env.NX_PCV3;
|
originalPcv3 = process.env.NX_PCV3;
|
||||||
process.env.NX_PCV3 = 'true';
|
process.env.NX_PCV3 = 'true';
|
||||||
newProject();
|
newProject({
|
||||||
|
packages: ['@nx/react'],
|
||||||
|
unsetProjectNameAndRootFormat: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@ -19,13 +22,21 @@ describe('Webpack Plugin (PCv3)', () => {
|
|||||||
cleanupProject();
|
cleanupProject();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate, build, and serve React applications', () => {
|
it('should generate, build, and serve React applications and libraries', () => {
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
|
const libName = uniq('lib');
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/react:app ${appName} --bundler webpack --e2eTestRunner=cypress --no-interactive`
|
`generate @nx/react:app ${appName} --bundler webpack --e2eTestRunner=cypress --rootProject --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(true).toBe(true);
|
expect(() => runCLI(`test ${appName}`)).not.toThrow();
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/react:lib ${libName} --unitTestRunner jest --no-interactive`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => runCLI(`test ${appName}`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`test ${libName}`)).not.toThrow();
|
||||||
|
|
||||||
// TODO: figure out why this test hangs in CI (maybe down to sudo prompt?)
|
// TODO: figure out why this test hangs in CI (maybe down to sudo prompt?)
|
||||||
// expect(() => runCLI(`build ${appName}`)).not.toThrow();
|
// expect(() => runCLI(`build ${appName}`)).not.toThrow();
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type { NormalizedSchema } from './normalized-schema';
|
|||||||
export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
|
export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
|
||||||
if (options.unitTestRunner === UnitTestRunner.Jest) {
|
if (options.unitTestRunner === UnitTestRunner.Jest) {
|
||||||
await configurationGenerator(host, {
|
await configurationGenerator(host, {
|
||||||
|
...options,
|
||||||
project: options.name,
|
project: options.name,
|
||||||
setupFile: 'angular',
|
setupFile: 'angular',
|
||||||
supportTsx: false,
|
supportTsx: false,
|
||||||
|
|||||||
@ -37,6 +37,11 @@ describe('Cypress Component Testing Configuration', () => {
|
|||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
tree.write('.gitignore', '');
|
tree.write('.gitignore', '');
|
||||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
mockedInstalledCypressVersion.mockReturnValue(10);
|
||||||
|
|
||||||
|
projectGraph = {
|
||||||
|
dependencies: {},
|
||||||
|
nodes: {},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -191,6 +196,9 @@ describe('Cypress Component Testing Configuration', () => {
|
|||||||
export: true,
|
export: true,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
const appConfig = readProjectConfiguration(tree, 'fancy-app');
|
const appConfig = readProjectConfiguration(tree, 'fancy-app');
|
||||||
appConfig.targets['build'].executor = 'something/else';
|
appConfig.targets['build'].executor = 'something/else';
|
||||||
updateProjectConfiguration(tree, 'fancy-app', appConfig);
|
updateProjectConfiguration(tree, 'fancy-app', appConfig);
|
||||||
|
|||||||
@ -48,6 +48,11 @@ describe('lib', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
projectGraph = {
|
||||||
|
dependencies: {},
|
||||||
|
nodes: {},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the library generator without erroring if the directory has a trailing slash', async () => {
|
it('should run the library generator without erroring if the directory has a trailing slash', async () => {
|
||||||
|
|||||||
@ -26,6 +26,11 @@ describe('Jest+Ng - 15.9.0 - tsconfig updates', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace();
|
tree = createTreeWithEmptyWorkspace();
|
||||||
tree.write('.gitignore', '');
|
tree.write('.gitignore', '');
|
||||||
|
|
||||||
|
projectGraph = {
|
||||||
|
dependencies: {},
|
||||||
|
nodes: {},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update tsconfig.spec.json with target es2016', async () => {
|
it('should update tsconfig.spec.json with target es2016', async () => {
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
"jest-config": "^29.4.1",
|
"jest-config": "^29.4.1",
|
||||||
"jest-resolve": "^29.4.1",
|
"jest-resolve": "^29.4.1",
|
||||||
"jest-util": "^29.4.1",
|
"jest-util": "^29.4.1",
|
||||||
|
"minimatch": "3.0.5",
|
||||||
"resolve.exports": "1.1.0",
|
"resolve.exports": "1.1.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
|
|||||||
5
packages/jest/plugin.ts
Normal file
5
packages/jest/plugin.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export {
|
||||||
|
createNodes,
|
||||||
|
createDependencies,
|
||||||
|
JestPluginOptions,
|
||||||
|
} from './src/plugins/plugin';
|
||||||
@ -9,6 +9,7 @@ import {
|
|||||||
Tree,
|
Tree,
|
||||||
GeneratorCallback,
|
GeneratorCallback,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
|
readNxJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
|
|
||||||
const schemaDefaults = {
|
const schemaDefaults = {
|
||||||
@ -65,7 +66,16 @@ export async function configurationGenerator(
|
|||||||
checkForTestTarget(tree, options);
|
checkForTestTarget(tree, options);
|
||||||
createFiles(tree, options);
|
createFiles(tree, options);
|
||||||
updateTsConfig(tree, options);
|
updateTsConfig(tree, options);
|
||||||
updateWorkspace(tree, options);
|
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
const hasPlugin = nxJson.plugins?.some((p) =>
|
||||||
|
typeof p === 'string'
|
||||||
|
? p === '@nx/jest/plugin'
|
||||||
|
: p.plugin === '@nx/jest/plugin'
|
||||||
|
);
|
||||||
|
if (!hasPlugin) {
|
||||||
|
updateWorkspace(tree, options);
|
||||||
|
}
|
||||||
|
|
||||||
if (!schema.skipFormat) {
|
if (!schema.skipFormat) {
|
||||||
await formatFiles(tree);
|
await formatFiles(tree);
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
|
let projectGraph: ProjectGraph;
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual<any>('@nx/devkit'),
|
||||||
|
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
|
||||||
|
return projectGraph;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addProjectConfiguration,
|
addProjectConfiguration as _addProjectConfiguration,
|
||||||
NxJsonConfiguration,
|
NxJsonConfiguration,
|
||||||
|
ProjectGraph,
|
||||||
readJson,
|
readJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
stripIndents,
|
stripIndents,
|
||||||
@ -11,11 +20,29 @@ import {
|
|||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { jestInitGenerator } from './init';
|
import { jestInitGenerator } from './init';
|
||||||
|
|
||||||
|
function addProjectConfiguration(tree, name, project) {
|
||||||
|
_addProjectConfiguration(tree, name, project);
|
||||||
|
projectGraph.nodes[name] = {
|
||||||
|
name: name,
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
root: project.root,
|
||||||
|
targets: project.targets,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe('jest', () => {
|
describe('jest', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
projectGraph = {
|
||||||
|
nodes: {},
|
||||||
|
dependencies: {},
|
||||||
|
externalNodes: {},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate files with --js flag', async () => {
|
it('should generate files with --js flag', async () => {
|
||||||
@ -250,6 +277,21 @@ export default {
|
|||||||
);
|
);
|
||||||
await jestInitGenerator(tree, { rootProject: false });
|
await jestInitGenerator(tree, { rootProject: false });
|
||||||
expect(tree.exists('jest.config.app.ts')).toBeTruthy();
|
expect(tree.exists('jest.config.app.ts')).toBeTruthy();
|
||||||
|
expect(tree.read('jest.config.app.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': 'ts-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||||
|
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
|
||||||
|
displayName: 'my-project',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
preset: './jest.preset.js',
|
||||||
|
};
|
||||||
|
"
|
||||||
|
`);
|
||||||
expect(tree.read('jest.config.ts', 'utf-8'))
|
expect(tree.read('jest.config.ts', 'utf-8'))
|
||||||
.toEqual(`import { getJestProjects } from '@nx/jest';
|
.toEqual(`import { getJestProjects } from '@nx/jest';
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
addDependenciesToPackageJson,
|
addDependenciesToPackageJson,
|
||||||
|
createProjectGraphAsync,
|
||||||
GeneratorCallback,
|
GeneratorCallback,
|
||||||
getProjects,
|
|
||||||
readNxJson,
|
readNxJson,
|
||||||
|
readProjectConfiguration,
|
||||||
removeDependenciesFromPackageJson,
|
removeDependenciesFromPackageJson,
|
||||||
runTasksInSerial,
|
runTasksInSerial,
|
||||||
stripIndents,
|
stripIndents,
|
||||||
|
TargetConfiguration,
|
||||||
Tree,
|
Tree,
|
||||||
updateJson,
|
updateJson,
|
||||||
updateNxJson,
|
updateNxJson,
|
||||||
@ -26,6 +28,7 @@ import {
|
|||||||
typesNodeVersion,
|
typesNodeVersion,
|
||||||
} from '../../utils/versions';
|
} from '../../utils/versions';
|
||||||
import { JestInitSchema } from './schema';
|
import { JestInitSchema } from './schema';
|
||||||
|
import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||||
|
|
||||||
interface NormalizedSchema extends ReturnType<typeof normalizeOptions> {}
|
interface NormalizedSchema extends ReturnType<typeof normalizeOptions> {}
|
||||||
|
|
||||||
@ -53,7 +56,28 @@ function generateGlobalConfig(tree: Tree, isJS: boolean) {
|
|||||||
tree.write(`jest.config.${isJS ? 'js' : 'ts'}`, contents);
|
tree.write(`jest.config.${isJS ? 'js' : 'ts'}`, contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createJestConfig(tree: Tree, options: NormalizedSchema) {
|
function addPlugin(tree: Tree) {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
if (
|
||||||
|
!nxJson.plugins.some((p) =>
|
||||||
|
typeof p === 'string'
|
||||||
|
? p === '@nx/jest/plugin'
|
||||||
|
: p.plugin === '@nx/jest/plugin'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: '@nx/jest/plugin',
|
||||||
|
options: {
|
||||||
|
targetName: 'test',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createJestConfig(tree: Tree, options: NormalizedSchema) {
|
||||||
if (!tree.exists('jest.preset.js')) {
|
if (!tree.exists('jest.preset.js')) {
|
||||||
// preset is always js file.
|
// preset is always js file.
|
||||||
tree.write(
|
tree.write(
|
||||||
@ -64,8 +88,15 @@ function createJestConfig(tree: Tree, options: NormalizedSchema) {
|
|||||||
module.exports = { ...nxPreset }`
|
module.exports = { ...nxPreset }`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const shouldAddPlugin = process.env.NX_PCV3 === 'true';
|
||||||
|
if (shouldAddPlugin) {
|
||||||
|
addPlugin(tree);
|
||||||
|
}
|
||||||
|
|
||||||
updateProductionFileSet(tree);
|
updateProductionFileSet(tree);
|
||||||
addJestTargetDefaults(tree);
|
if (!shouldAddPlugin) {
|
||||||
|
addJestTargetDefaults(tree, shouldAddPlugin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (options.rootProject) {
|
if (options.rootProject) {
|
||||||
// we don't want any config to be made because the `configurationGenerator` will do it.
|
// we don't want any config to be made because the `configurationGenerator` will do it.
|
||||||
@ -84,30 +115,83 @@ function createJestConfig(tree: Tree, options: NormalizedSchema) {
|
|||||||
|
|
||||||
if (tree.exists(rootJestPath)) {
|
if (tree.exists(rootJestPath)) {
|
||||||
// moving from root project config to monorepo-style config
|
// moving from root project config to monorepo-style config
|
||||||
const projects = getProjects(tree);
|
const { nodes: projects } = await createProjectGraphAsync();
|
||||||
const projectNames = Array.from(projects.keys());
|
const projectConfigurations = Object.values(projects);
|
||||||
const rootProject = projectNames.find(
|
const rootProject = projectConfigurations.find(
|
||||||
(projectName) => projects.get(projectName)?.root === '.'
|
(projectNode) => projectNode.data?.root === '.'
|
||||||
);
|
);
|
||||||
// root project might have been removed,
|
// root project might have been removed,
|
||||||
// if it's missing there's nothing to migrate
|
// if it's missing there's nothing to migrate
|
||||||
if (rootProject) {
|
if (rootProject) {
|
||||||
const rootProjectConfig = projects.get(rootProject);
|
const jestTarget = Object.entries(rootProject.data?.targets ?? {}).find(
|
||||||
const jestTarget = Object.values(rootProjectConfig.targets || {}).find(
|
([_, t]) =>
|
||||||
(t) =>
|
((t?.executor === '@nx/jest:jest' ||
|
||||||
t?.executor === '@nx/jest:jest' || t?.executor === '@nrwl/jest:jest'
|
t?.executor === '@nrwl/jest:jest') &&
|
||||||
|
t?.options?.jestConfig === rootJestPath) ||
|
||||||
|
(t?.executor === 'nx:run-commands' && t?.options?.command === 'jest')
|
||||||
);
|
);
|
||||||
const isProjectConfig = jestTarget?.options?.jestConfig === rootJestPath;
|
if (!jestTarget) {
|
||||||
// if root project doesn't have jest target, there's nothing to migrate
|
return;
|
||||||
if (isProjectConfig) {
|
|
||||||
const jestProjectConfig = `jest.config.${
|
|
||||||
rootProjectConfig.projectType === 'application' ? 'app' : 'lib'
|
|
||||||
}.${options.js ? 'js' : 'ts'}`;
|
|
||||||
|
|
||||||
tree.rename(rootJestPath, jestProjectConfig);
|
|
||||||
jestTarget.options.jestConfig = jestProjectConfig;
|
|
||||||
updateProjectConfiguration(tree, rootProject, rootProjectConfig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [jestTargetName, jestTargetConfigInGraph] = jestTarget;
|
||||||
|
// if root project doesn't have jest target, there's nothing to migrate
|
||||||
|
const rootProjectConfig = readProjectConfiguration(
|
||||||
|
tree,
|
||||||
|
rootProject.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
rootProjectConfig.targets['test']?.executor === 'nx:run-commands'
|
||||||
|
? rootProjectConfig.targets['test']?.command !== 'jest'
|
||||||
|
: rootProjectConfig.targets['test']?.options?.jestConfig !==
|
||||||
|
rootJestPath
|
||||||
|
) {
|
||||||
|
// Jest target has already been updated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jestProjectConfig = `jest.config.${
|
||||||
|
rootProjectConfig.projectType === 'application' ? 'app' : 'lib'
|
||||||
|
}.${options.js ? 'js' : 'ts'}`;
|
||||||
|
|
||||||
|
tree.rename(rootJestPath, jestProjectConfig);
|
||||||
|
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
const targetDefaults = readTargetDefaultsForTarget(
|
||||||
|
jestTargetName,
|
||||||
|
nxJson.targetDefaults,
|
||||||
|
jestTargetConfigInGraph.executor
|
||||||
|
);
|
||||||
|
|
||||||
|
const target: TargetConfiguration = (rootProjectConfig.targets[
|
||||||
|
jestTargetName
|
||||||
|
] ??=
|
||||||
|
jestTargetConfigInGraph.executor === 'nx:run-commands'
|
||||||
|
? { command: `jest --config ${jestProjectConfig}` }
|
||||||
|
: {
|
||||||
|
executor: jestTargetConfigInGraph.executor,
|
||||||
|
options: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (target.executor === '@nx/jest:jest') {
|
||||||
|
target.options.jestConfig = jestProjectConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetDefaults?.cache === undefined) {
|
||||||
|
target.cache = jestTargetConfigInGraph.cache;
|
||||||
|
}
|
||||||
|
if (targetDefaults?.inputs === undefined) {
|
||||||
|
target.inputs = jestTargetConfigInGraph.inputs;
|
||||||
|
}
|
||||||
|
if (targetDefaults?.outputs === undefined) {
|
||||||
|
target.outputs = jestTargetConfigInGraph.outputs;
|
||||||
|
}
|
||||||
|
if (targetDefaults?.dependsOn === undefined) {
|
||||||
|
target.dependsOn = jestTargetConfigInGraph.dependsOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProjectConfiguration(tree, rootProject.name, rootProjectConfig);
|
||||||
// generate new global config as it was move to project config or is missing
|
// generate new global config as it was move to project config or is missing
|
||||||
generateGlobalConfig(tree, options.js);
|
generateGlobalConfig(tree, options.js);
|
||||||
}
|
}
|
||||||
@ -139,20 +223,23 @@ function updateProductionFileSet(tree: Tree) {
|
|||||||
updateNxJson(tree, nxJson);
|
updateNxJson(tree, nxJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addJestTargetDefaults(tree: Tree) {
|
function addJestTargetDefaults(tree: Tree, hasPlugin: boolean) {
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
const productionFileSet = nxJson.namedInputs?.production;
|
|
||||||
|
|
||||||
nxJson.targetDefaults ??= {};
|
nxJson.targetDefaults ??= {};
|
||||||
nxJson.targetDefaults['@nx/jest:jest'] ??= {};
|
nxJson.targetDefaults['@nx/jest:jest'] ??= {};
|
||||||
nxJson.targetDefaults['@nx/jest:jest'].cache ??= true;
|
|
||||||
|
|
||||||
// Test targets depend on all their project's sources + production sources of dependencies
|
if (!hasPlugin) {
|
||||||
nxJson.targetDefaults['@nx/jest:jest'].inputs ??= [
|
const productionFileSet = nxJson.namedInputs?.production;
|
||||||
'default',
|
|
||||||
productionFileSet ? '^production' : '^default',
|
nxJson.targetDefaults['@nx/jest:jest'].cache ??= true;
|
||||||
'{workspaceRoot}/jest.preset.js',
|
// Test targets depend on all their project's sources + production sources of dependencies
|
||||||
];
|
nxJson.targetDefaults['@nx/jest:jest'].inputs ??= [
|
||||||
|
'default',
|
||||||
|
productionFileSet ? '^production' : '^default',
|
||||||
|
'{workspaceRoot}/jest.preset.js',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
nxJson.targetDefaults['@nx/jest:jest'].options ??= {
|
nxJson.targetDefaults['@nx/jest:jest'].options ??= {
|
||||||
passWithNoTests: true,
|
passWithNoTests: true,
|
||||||
@ -231,7 +318,7 @@ export async function jestInitGenerator(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
createJestConfig(tree, options);
|
await createJestConfig(tree, options);
|
||||||
|
|
||||||
if (!options.skipPackageJson) {
|
if (!options.skipPackageJson) {
|
||||||
removeDependenciesFromPackageJson(tree, ['@nx/jest'], []);
|
removeDependenciesFromPackageJson(tree, ['@nx/jest'], []);
|
||||||
|
|||||||
82
packages/jest/src/plugins/plugin.spec.ts
Normal file
82
packages/jest/src/plugins/plugin.spec.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { CreateNodesContext } from '@nx/devkit';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { createNodes } from './plugin';
|
||||||
|
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||||
|
|
||||||
|
describe('@nx/jest/plugin', () => {
|
||||||
|
let createNodesFunction = createNodes[1];
|
||||||
|
let context: CreateNodesContext;
|
||||||
|
let tempFs: TempFs;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tempFs = new TempFs('test');
|
||||||
|
context = {
|
||||||
|
nxJsonConfiguration: {
|
||||||
|
namedInputs: {
|
||||||
|
default: ['{projectRoot}/**/*'],
|
||||||
|
production: ['!{projectRoot}/**/*.spec.ts'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
await tempFs.createFiles({
|
||||||
|
'proj/jest.config.js': '',
|
||||||
|
'proj/project.json': '{}',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create nodes based on jest.config.ts', async () => {
|
||||||
|
mockJestConfig(
|
||||||
|
{
|
||||||
|
coverageDirectory: '../coverage',
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
const nodes = await createNodesFunction(
|
||||||
|
'proj/jest.config.js',
|
||||||
|
{
|
||||||
|
targetName: 'test',
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(nodes.projects.proj).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"root": "proj",
|
||||||
|
"targets": {
|
||||||
|
"test": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "jest",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^production",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"jest",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "proj",
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{workspaceRoot}/coverage",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function mockJestConfig(config: any, context: CreateNodesContext) {
|
||||||
|
jest.mock(join(context.workspaceRoot, 'proj/jest.config.js'), () => config, {
|
||||||
|
virtual: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
200
packages/jest/src/plugins/plugin.ts
Normal file
200
packages/jest/src/plugins/plugin.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import {
|
||||||
|
CreateDependencies,
|
||||||
|
CreateNodes,
|
||||||
|
CreateNodesContext,
|
||||||
|
joinPathFragments,
|
||||||
|
NxJsonConfiguration,
|
||||||
|
readJsonFile,
|
||||||
|
TargetConfiguration,
|
||||||
|
writeJsonFile,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { dirname, join, relative, resolve } from 'path';
|
||||||
|
|
||||||
|
import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||||
|
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
||||||
|
import { existsSync, readdirSync } from 'fs';
|
||||||
|
import { readConfig } from 'jest-config';
|
||||||
|
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
|
||||||
|
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||||
|
import { getGlobPatternsFromPackageManagerWorkspaces } from 'nx/plugins/package-json-workspaces';
|
||||||
|
import { combineGlobPatterns } from 'nx/src/utils/globs';
|
||||||
|
import * as minimatch from 'minimatch';
|
||||||
|
|
||||||
|
export interface JestPluginOptions {
|
||||||
|
targetName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cachePath = join(projectGraphCacheDirectory, 'jest.hash');
|
||||||
|
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};
|
||||||
|
|
||||||
|
const calculatedTargets: Record<
|
||||||
|
string,
|
||||||
|
Record<string, TargetConfiguration>
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
function readTargetsCache(): Record<
|
||||||
|
string,
|
||||||
|
Record<string, TargetConfiguration>
|
||||||
|
> {
|
||||||
|
return readJsonFile(cachePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTargetsToCache(
|
||||||
|
targets: Record<string, Record<string, TargetConfiguration>>
|
||||||
|
) {
|
||||||
|
writeJsonFile(cachePath, targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDependencies: CreateDependencies = () => {
|
||||||
|
writeTargetsToCache(calculatedTargets);
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createNodes: CreateNodes<JestPluginOptions> = [
|
||||||
|
'**/jest.config.{cjs,mjs,js,cts,mts,ts}',
|
||||||
|
async (configFilePath, options, context) => {
|
||||||
|
const projectRoot = dirname(configFilePath);
|
||||||
|
|
||||||
|
const packageManagerWorkspacesGlob = combineGlobPatterns(
|
||||||
|
getGlobPatternsFromPackageManagerWorkspaces(context.workspaceRoot)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Do not create a project if package.json and project.json isn't there.
|
||||||
|
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
||||||
|
if (
|
||||||
|
!siblingFiles.includes('package.json') &&
|
||||||
|
!siblingFiles.includes('project.json')
|
||||||
|
) {
|
||||||
|
return {};
|
||||||
|
} else if (
|
||||||
|
!siblingFiles.includes('project.json') &&
|
||||||
|
siblingFiles.includes('package.json')
|
||||||
|
) {
|
||||||
|
const path = joinPathFragments(projectRoot, 'package.json');
|
||||||
|
|
||||||
|
const isPackageJsonProject = minimatch(
|
||||||
|
path,
|
||||||
|
packageManagerWorkspacesGlob
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isPackageJsonProject) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options = normalizeOptions(options);
|
||||||
|
|
||||||
|
const hash = calculateHashForCreateNodes(projectRoot, options, context);
|
||||||
|
const targets =
|
||||||
|
targetsCache[hash] ??
|
||||||
|
(await buildJestTargets(configFilePath, projectRoot, options, context));
|
||||||
|
|
||||||
|
calculatedTargets[hash] = targets;
|
||||||
|
|
||||||
|
return {
|
||||||
|
projects: {
|
||||||
|
[projectRoot]: {
|
||||||
|
root: projectRoot,
|
||||||
|
targets: targets,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async function buildJestTargets(
|
||||||
|
configFilePath: string,
|
||||||
|
projectRoot: string,
|
||||||
|
options: JestPluginOptions,
|
||||||
|
context: CreateNodesContext
|
||||||
|
) {
|
||||||
|
const config = await readConfig(
|
||||||
|
{
|
||||||
|
_: [],
|
||||||
|
$0: undefined,
|
||||||
|
},
|
||||||
|
resolve(context.workspaceRoot, configFilePath),
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const targetDefaults = readTargetDefaultsForTarget(
|
||||||
|
options.targetName,
|
||||||
|
context.nxJsonConfiguration.targetDefaults,
|
||||||
|
'nx:run-commands'
|
||||||
|
);
|
||||||
|
|
||||||
|
const namedInputs = getNamedInputs(projectRoot, context);
|
||||||
|
|
||||||
|
const targets: Record<string, TargetConfiguration> = {};
|
||||||
|
|
||||||
|
const target: TargetConfiguration = (targets[options.targetName] = {
|
||||||
|
command: 'jest',
|
||||||
|
options: {
|
||||||
|
cwd: projectRoot,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetDefaults?.cache) {
|
||||||
|
target.cache = true;
|
||||||
|
}
|
||||||
|
if (!targetDefaults?.inputs) {
|
||||||
|
target.inputs = getInputs(namedInputs);
|
||||||
|
}
|
||||||
|
if (!targetDefaults?.outputs) {
|
||||||
|
target.outputs = getOutputs(projectRoot, config, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputs(
|
||||||
|
namedInputs: NxJsonConfiguration['namedInputs']
|
||||||
|
): TargetConfiguration['inputs'] {
|
||||||
|
return [
|
||||||
|
...('production' in namedInputs
|
||||||
|
? ['default', '^production']
|
||||||
|
: ['default', '^default']),
|
||||||
|
{
|
||||||
|
externalDependencies: ['jest'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputs(
|
||||||
|
projectRoot: string,
|
||||||
|
{ globalConfig }: Awaited<ReturnType<typeof readConfig>>,
|
||||||
|
context: CreateNodesContext
|
||||||
|
): string[] {
|
||||||
|
function getOutput(path: string): string {
|
||||||
|
const relativePath = relative(
|
||||||
|
join(context.workspaceRoot, projectRoot),
|
||||||
|
path
|
||||||
|
);
|
||||||
|
if (relativePath.startsWith('..')) {
|
||||||
|
return join('{workspaceRoot}', join(projectRoot, relativePath));
|
||||||
|
} else {
|
||||||
|
return join('{projectRoot}', relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputs = [];
|
||||||
|
|
||||||
|
for (const outputOption of [
|
||||||
|
globalConfig.coverageDirectory,
|
||||||
|
globalConfig.outputFile,
|
||||||
|
]) {
|
||||||
|
if (outputOption) {
|
||||||
|
outputs.push(getOutput(outputOption));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputs;
|
||||||
|
}
|
||||||
|
function normalizeOptions(options: JestPluginOptions): JestPluginOptions {
|
||||||
|
options ??= {};
|
||||||
|
options.targetName ??= 'test';
|
||||||
|
return options;
|
||||||
|
}
|
||||||
@ -104,44 +104,6 @@ describe('monorepo generator', () => {
|
|||||||
expect(tree.exists('libs/inner/my-lib/src/index.ts')).toBeTruthy();
|
expect(tree.exists('libs/inner/my-lib/src/index.ts')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should convert root React app (Webpack, Jest)', async () => {
|
|
||||||
await reactAppGenerator(tree, {
|
|
||||||
name: 'demo',
|
|
||||||
style: 'css',
|
|
||||||
bundler: 'webpack',
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
e2eTestRunner: 'none',
|
|
||||||
linter: 'eslint',
|
|
||||||
rootProject: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await monorepoGenerator(tree, {});
|
|
||||||
|
|
||||||
expect(readProjectConfiguration(tree, 'demo')).toMatchObject({
|
|
||||||
sourceRoot: 'apps/demo/src',
|
|
||||||
targets: {
|
|
||||||
build: {
|
|
||||||
executor: '@nx/webpack:webpack',
|
|
||||||
options: {
|
|
||||||
main: 'apps/demo/src/main.tsx',
|
|
||||||
tsConfig: 'apps/demo/tsconfig.app.json',
|
|
||||||
webpackConfig: 'apps/demo/webpack.config.js',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
test: {
|
|
||||||
executor: '@nx/jest:jest',
|
|
||||||
options: {
|
|
||||||
jestConfig: 'apps/demo/jest.config.app.ts',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extracted base config files
|
|
||||||
expect(tree.exists('tsconfig.base.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('jest.config.ts')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should convert root Next.js app with existing libraries', async () => {
|
it('should convert root Next.js app with existing libraries', async () => {
|
||||||
await nextAppGenerator(tree, {
|
await nextAppGenerator(tree, {
|
||||||
name: 'demo',
|
name: 'demo',
|
||||||
|
|||||||
@ -136,279 +136,6 @@ describe('move', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support moving root projects', async () => {
|
|
||||||
// Test that these are not moved
|
|
||||||
tree.write('.gitignore', '');
|
|
||||||
tree.write('README.md', '');
|
|
||||||
|
|
||||||
await libraryGenerator(tree, {
|
|
||||||
name: 'my-lib',
|
|
||||||
rootProject: true,
|
|
||||||
bundler: 'tsc',
|
|
||||||
buildable: true,
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
linter: 'eslint',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
|
|
||||||
updateJson(tree, 'tsconfig.json', (json) => {
|
|
||||||
json.extends = './tsconfig.base.json';
|
|
||||||
json.files = ['./node_modules/@foo/bar/index.d.ts'];
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
|
|
||||||
let projectJson = readJson(tree, 'project.json');
|
|
||||||
expect(projectJson['$schema']).toEqual(
|
|
||||||
'node_modules/nx/schemas/project-schema.json'
|
|
||||||
);
|
|
||||||
// Test that this does not get moved
|
|
||||||
tree.write('other-lib/index.ts', '');
|
|
||||||
|
|
||||||
await moveGenerator(tree, {
|
|
||||||
projectName: 'my-lib',
|
|
||||||
importPath: '@proj/my-lib',
|
|
||||||
updateImportPath: true,
|
|
||||||
destination: 'my-lib',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(readJson(tree, 'my-lib/project.json')).toMatchObject({
|
|
||||||
name: 'my-lib',
|
|
||||||
$schema: '../node_modules/nx/schemas/project-schema.json',
|
|
||||||
sourceRoot: 'my-lib/src',
|
|
||||||
projectType: 'library',
|
|
||||||
targets: {
|
|
||||||
build: {
|
|
||||||
executor: '@nx/js:tsc',
|
|
||||||
outputs: ['{options.outputPath}'],
|
|
||||||
options: {
|
|
||||||
outputPath: 'dist/my-lib',
|
|
||||||
main: 'my-lib/src/index.ts',
|
|
||||||
tsConfig: 'my-lib/tsconfig.lib.json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lint: {
|
|
||||||
executor: '@nx/eslint:lint',
|
|
||||||
},
|
|
||||||
test: {
|
|
||||||
executor: '@nx/jest:jest',
|
|
||||||
outputs: ['{workspaceRoot}/coverage/{projectName}'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(readJson(tree, 'my-lib/tsconfig.json')).toMatchObject({
|
|
||||||
extends: '../tsconfig.base.json',
|
|
||||||
files: ['../node_modules/@foo/bar/index.d.ts'],
|
|
||||||
references: [
|
|
||||||
{ path: './tsconfig.lib.json' },
|
|
||||||
{ path: './tsconfig.spec.json' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const jestConfig = tree.read('my-lib/jest.config.lib.ts', 'utf-8');
|
|
||||||
expect(jestConfig).toContain(`preset: '../jest.preset.js'`);
|
|
||||||
|
|
||||||
expect(tree.exists('my-lib/tsconfig.lib.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('my-lib/tsconfig.spec.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('my-lib/.eslintrc.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('my-lib/src/index.ts')).toBeTruthy();
|
|
||||||
|
|
||||||
// Test that other libs and workspace files are not moved.
|
|
||||||
expect(tree.exists('package.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('README.md')).toBeTruthy();
|
|
||||||
expect(tree.exists('.gitignore')).toBeTruthy();
|
|
||||||
expect(tree.exists('other-lib/index.ts')).toBeTruthy();
|
|
||||||
|
|
||||||
// Test that root configs are extracted
|
|
||||||
expect(tree.exists('tsconfig.base.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('jest.config.ts')).toBeTruthy();
|
|
||||||
expect(tree.exists('.eslintrc.base.json')).not.toBeTruthy();
|
|
||||||
expect(tree.exists('.eslintrc.json')).toBeTruthy();
|
|
||||||
|
|
||||||
// Test that eslint migration was done
|
|
||||||
expect(readJson(tree, 'my-lib/.eslintrc.json').extends)
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
[
|
|
||||||
"../.eslintrc.json",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
expect(readJson(tree, 'my-lib/.eslintrc.json').plugins).not.toBeDefined();
|
|
||||||
expect(readJson(tree, '.eslintrc.json').plugins).toEqual(['@nx']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support moving standalone repos', async () => {
|
|
||||||
// Test that these are not moved
|
|
||||||
tree.write('.gitignore', '');
|
|
||||||
tree.write('README.md', '');
|
|
||||||
|
|
||||||
await applicationGenerator(tree, {
|
|
||||||
name: 'react-app',
|
|
||||||
rootProject: true,
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
e2eTestRunner: 'cypress',
|
|
||||||
linter: 'eslint',
|
|
||||||
style: 'css',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
expect(readJson(tree, '.eslintrc.json').plugins).toEqual(['@nx']);
|
|
||||||
expect(readJson(tree, 'e2e/.eslintrc.json').plugins).toEqual(['@nx']);
|
|
||||||
|
|
||||||
// Test that this does not get moved
|
|
||||||
tree.write('other-lib/index.ts', '');
|
|
||||||
|
|
||||||
await moveGenerator(tree, {
|
|
||||||
projectName: 'react-app',
|
|
||||||
updateImportPath: false,
|
|
||||||
destination: 'apps/react-app',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
|
|
||||||
// expect both eslint configs to have been changed
|
|
||||||
expect(tree.exists('.eslintrc.json')).toBeDefined();
|
|
||||||
expect(
|
|
||||||
readJson(tree, 'apps/react-app/.eslintrc.json').plugins
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(readJson(tree, 'e2e/.eslintrc.json').plugins).toBeUndefined();
|
|
||||||
|
|
||||||
await moveGenerator(tree, {
|
|
||||||
projectName: 'e2e',
|
|
||||||
updateImportPath: false,
|
|
||||||
destination: 'apps/react-app-e2e',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(tree.read('apps/react-app-e2e/cypress.config.ts').toString())
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
|
||||||
|
|
||||||
import { defineConfig } from 'cypress';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
e2e: {
|
|
||||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
|
||||||
baseUrl: 'http://localhost:4200',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
"
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should correctly move standalone repos that have migrated eslint config', async () => {
|
|
||||||
// Test that these are not moved
|
|
||||||
tree.write('.gitignore', '');
|
|
||||||
tree.write('README.md', '');
|
|
||||||
|
|
||||||
await applicationGenerator(tree, {
|
|
||||||
name: 'react-app',
|
|
||||||
rootProject: true,
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
e2eTestRunner: 'cypress',
|
|
||||||
linter: 'eslint',
|
|
||||||
style: 'css',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
await libraryGenerator(tree, {
|
|
||||||
name: 'my-lib',
|
|
||||||
bundler: 'tsc',
|
|
||||||
buildable: true,
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
linter: 'eslint',
|
|
||||||
directory: 'my-lib',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
// assess the correct starting position
|
|
||||||
expect(tree.exists('.eslintrc.base.json')).toBeTruthy();
|
|
||||||
expect(readJson(tree, '.eslintrc.json').plugins).not.toBeDefined();
|
|
||||||
expect(readJson(tree, '.eslintrc.json').extends).toEqual([
|
|
||||||
'plugin:@nx/react',
|
|
||||||
'./.eslintrc.base.json',
|
|
||||||
]);
|
|
||||||
expect(readJson(tree, 'e2e/.eslintrc.json').plugins).not.toBeDefined();
|
|
||||||
expect(readJson(tree, 'e2e/.eslintrc.json').extends).toEqual([
|
|
||||||
'plugin:cypress/recommended',
|
|
||||||
'../.eslintrc.base.json',
|
|
||||||
]);
|
|
||||||
|
|
||||||
await moveGenerator(tree, {
|
|
||||||
projectName: 'react-app',
|
|
||||||
updateImportPath: false,
|
|
||||||
destination: 'apps/react-app',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
|
|
||||||
// expect both eslint configs to have been changed
|
|
||||||
expect(tree.exists('.eslintrc.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('.eslintrc.base.json')).toBeFalsy();
|
|
||||||
|
|
||||||
expect(readJson(tree, 'apps/react-app/.eslintrc.json').extends).toEqual([
|
|
||||||
'plugin:@nx/react',
|
|
||||||
'../../.eslintrc.json',
|
|
||||||
]);
|
|
||||||
expect(readJson(tree, 'e2e/.eslintrc.json').extends).toEqual([
|
|
||||||
'plugin:cypress/recommended',
|
|
||||||
'../.eslintrc.json',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support scoped new project name for libraries', async () => {
|
|
||||||
await libraryGenerator(tree, {
|
|
||||||
name: 'my-lib',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
|
|
||||||
await moveGenerator(tree, {
|
|
||||||
projectName: 'my-lib',
|
|
||||||
newProjectName: '@proj/shared-my-lib',
|
|
||||||
updateImportPath: true,
|
|
||||||
destination: 'shared/my-lib',
|
|
||||||
projectNameAndRootFormat: 'as-provided',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(tree.exists('shared/my-lib/package.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('shared/my-lib/tsconfig.lib.json')).toBeTruthy();
|
|
||||||
expect(tree.exists('shared/my-lib/src/index.ts')).toBeTruthy();
|
|
||||||
expect(readProjectConfiguration(tree, '@proj/shared-my-lib'))
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"name": "@proj/shared-my-lib",
|
|
||||||
"projectType": "library",
|
|
||||||
"root": "shared/my-lib",
|
|
||||||
"sourceRoot": "shared/my-lib/src",
|
|
||||||
"tags": [],
|
|
||||||
"targets": {
|
|
||||||
"build": {
|
|
||||||
"executor": "@nx/js:tsc",
|
|
||||||
"options": {
|
|
||||||
"assets": [
|
|
||||||
"shared/my-lib/*.md",
|
|
||||||
],
|
|
||||||
"main": "shared/my-lib/src/index.ts",
|
|
||||||
"outputPath": "dist/shared/my-lib",
|
|
||||||
"tsConfig": "shared/my-lib/tsconfig.lib.json",
|
|
||||||
},
|
|
||||||
"outputs": [
|
|
||||||
"{options.outputPath}",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"executor": "@nx/eslint:lint",
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"executor": "@nx/jest:jest",
|
|
||||||
"options": {
|
|
||||||
"jestConfig": "shared/my-lib/jest.config.ts",
|
|
||||||
},
|
|
||||||
"outputs": [
|
|
||||||
"{workspaceRoot}/coverage/{projectRoot}",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should move project correctly when --project-name-and-root-format=derived', async () => {
|
it('should move project correctly when --project-name-and-root-format=derived', async () => {
|
||||||
await libraryGenerator(tree, {
|
await libraryGenerator(tree, {
|
||||||
name: 'my-lib',
|
name: 'my-lib',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user