nx/packages/cypress/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.spec.ts
Colum Ferry ef036b4a46
fix(testing): e2e-ci targetDefaults migration should handle self deps (#27380)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
If the `ciWebServerCommand` depends on a target that exists on the e2e
project, the `dependsOn` is still added with `^` expecting the project
to be an implicit dep.


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The migration should handle when e2e project depends on itself for the
`e2e-ci` command

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2024-08-12 14:19:14 -04:00

457 lines
10 KiB
TypeScript

import { ProjectGraph, readNxJson, type Tree, updateNxJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults';
let projectGraph: ProjectGraph;
jest.mock('@nx/devkit', () => ({
...jest.requireActual<any>('@nx/devkit'),
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
return projectGraph;
}),
}));
describe('add-e2e-ci-target-defaults', () => {
let tree: Tree;
let tempFs: TempFs;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
tempFs = new TempFs('add-e2e-ci');
tree.root = tempFs.tempDir;
projectGraph = {
nodes: {},
dependencies: {},
externalNodes: {},
};
});
afterEach(() => {
tempFs.reset();
});
it('should do nothing when the plugin is not registered', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [];
updateNxJson(tree, nxJson);
// ACT
await addE2eCiTargetDefaults(tree);
// ASSERT
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
{
"build": {
"cache": true,
},
"lint": {
"cache": true,
},
}
`);
});
it('should add the targetDefaults with the correct ciTargetName and buildTarget when there is one plugin', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'e2e-ci',
},
},
];
updateNxJson(tree, nxJson);
addProject(tree, tempFs);
// ACT
await addE2eCiTargetDefaults(tree);
// ASSERT
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
{
"build": {
"cache": true,
},
"e2e-ci--**/*": {
"dependsOn": [
"^build",
],
},
"lint": {
"cache": true,
},
}
`);
});
it('should add the targetDefaults with the correct buildTarget when the e2e project depends on itself', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'e2e-ci',
},
},
];
updateNxJson(tree, nxJson);
addSelfDependentProject(tree, tempFs);
// ACT
await addE2eCiTargetDefaults(tree);
// ASSERT
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
{
"build": {
"cache": true,
},
"e2e-ci--**/*": {
"dependsOn": [
"build",
],
},
"lint": {
"cache": true,
},
}
`);
});
it('should add the targetDefaults with the correct ciTargetNames and buildTargets when there is more than one plugin', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'e2e-ci',
},
include: ['app-e2e/**'],
},
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'cypress:e2e-ci',
},
include: ['shop-e2e/**'],
},
];
updateNxJson(tree, nxJson);
addProject(tree, tempFs);
addProject(tree, tempFs, {
buildTargetName: 'build',
ciTargetName: 'cypress:e2e-ci',
appName: 'shop',
});
// ACT
await addE2eCiTargetDefaults(tree);
// ASSERT
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
{
"build": {
"cache": true,
},
"cypress:e2e-ci--**/*": {
"dependsOn": [
"^build",
],
},
"e2e-ci--**/*": {
"dependsOn": [
"^build",
],
},
"lint": {
"cache": true,
},
}
`);
});
it('should only add the targetDefaults with the correct ciTargetName and buildTargets when there is more than one plugin with only one matching multiple projects', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'e2e-ci',
},
include: ['cart-e2e/**'],
},
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'cypress:e2e-ci',
},
},
];
updateNxJson(tree, nxJson);
addProject(tree, tempFs);
addProject(tree, tempFs, {
buildTargetName: 'bundle',
ciTargetName: 'cypress:e2e-ci',
appName: 'shop',
});
// ACT
await addE2eCiTargetDefaults(tree);
// ASSERT
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
{
"build": {
"cache": true,
},
"cypress:e2e-ci--**/*": {
"dependsOn": [
"^build",
"^bundle",
],
},
"lint": {
"cache": true,
},
}
`);
});
it('should not add the targetDefaults when the ciWebServerCommand is not present', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'cypress:e2e-ci',
},
},
];
updateNxJson(tree, nxJson);
addProject(tree, tempFs, {
appName: 'app',
buildTargetName: 'build',
ciTargetName: 'cypress:e2e-ci',
noCi: true,
});
// ACT
await addE2eCiTargetDefaults(tree);
// ASSERT
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
{
"build": {
"cache": true,
},
"lint": {
"cache": true,
},
}
`);
});
});
function addProject(
tree: Tree,
tempFs: TempFs,
overrides: {
ciTargetName: string;
buildTargetName: string;
appName: string;
noCi?: boolean;
} = { ciTargetName: 'e2e-ci', buildTargetName: 'build', appName: 'app' }
) {
const appProjectConfig = {
name: overrides.appName,
root: overrides.appName,
sourceRoot: `${overrides.appName}/src`,
projectType: 'application',
};
const viteConfig = `/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/${overrides.appName}',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/${overrides.appName}',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
});`;
const e2eProjectConfig = {
name: `${overrides.appName}-e2e`,
root: `${overrides.appName}-e2e`,
sourceRoot: `${overrides.appName}-e2e/src`,
projectType: 'application',
};
const cypressConfig = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
...nxE2EPreset(__filename, {
cypressDir: 'src',
bundler: 'vite',
webServerCommands: {
default: 'nx run ${overrides.appName}:serve',
production: 'nx run ${overrides.appName}:preview',
},
${
!overrides.noCi
? `ciWebServerCommand: 'nx run ${overrides.appName}:serve-static',`
: ''
}
}),
baseUrl: 'http://localhost:4200',
},
});
`;
tree.write(`${overrides.appName}/vite.config.ts`, viteConfig);
tree.write(
`${overrides.appName}/project.json`,
JSON.stringify(appProjectConfig)
);
tree.write(`${overrides.appName}-e2e/cypress.config.ts`, cypressConfig);
tree.write(
`${overrides.appName}-e2e/project.json`,
JSON.stringify(e2eProjectConfig)
);
tempFs.createFilesSync({
[`${overrides.appName}/vite.config.ts`]: viteConfig,
[`${overrides.appName}/project.json`]: JSON.stringify(appProjectConfig),
[`${overrides.appName}-e2e/cypress.config.ts`]: cypressConfig,
[`${overrides.appName}-e2e/project.json`]: JSON.stringify(e2eProjectConfig),
});
projectGraph.nodes[overrides.appName] = {
name: overrides.appName,
type: 'app',
data: {
projectType: 'application',
root: overrides.appName,
targets: {
[overrides.buildTargetName]: {},
'serve-static': {
options: {
buildTarget: overrides.buildTargetName,
},
},
},
},
};
projectGraph.nodes[`${overrides.appName}-e2e`] = {
name: `${overrides.appName}-e2e`,
type: 'app',
data: {
projectType: 'application',
root: `${overrides.appName}-e2e`,
targets: {
e2e: {},
[overrides.ciTargetName]: {},
},
},
};
}
function addSelfDependentProject(tree: Tree, tempFs: TempFs) {
const appProjectConfig = {
name: 'app-e2e',
root: 'app-e2e',
sourceRoot: `app-e2e/src`,
projectType: 'application',
};
const cypressConfig = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
...nxE2EPreset(__filename, {
cypressDir: 'src',
bundler: 'vite',
webServerCommands: {
default: 'nx run app-e2e:serve',
production: 'nx run app-e2e:preview',
},
ciWebServerCommand: 'nx run app-e2e:serve-static',
}
}),
baseUrl: 'http://localhost:4200',
},
});
`;
tree.write(`app-e2e/project.json`, JSON.stringify(appProjectConfig));
tree.write(`app-e2e/cypress.config.ts`, cypressConfig);
tempFs.createFilesSync({
[`app-e2e/project.json`]: JSON.stringify(appProjectConfig),
[`app-e2e/cypress.config.ts`]: cypressConfig,
});
projectGraph.nodes['app-e2e'] = {
name: 'app-e2e',
type: 'app',
data: {
projectType: 'application',
root: 'app-e2e',
targets: {
build: {},
'serve-static': {
dependsOn: ['build'],
},
},
},
};
}