feat(testing): add support for cypress v14 (#30618)
## Current Behavior Cypress v14 is not supported. ## Expected Behavior Cypress v14 is supported. ## Related Issue(s) Fixes #30097
This commit is contained in:
parent
3b91e0b32d
commit
5feafd64d4
@ -1372,6 +1372,56 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrations": {
|
"migrations": {
|
||||||
|
"/nx-api/cypress/migrations/set-inject-document-domain": {
|
||||||
|
"description": "Replaces the `experimentalSkipDomainInjection` configuration option with the new `injectDocumentDomain` configuration option.",
|
||||||
|
"file": "generated/packages/cypress/migrations/set-inject-document-domain.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "set-inject-document-domain",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "/nx-api/cypress/migrations/set-inject-document-domain",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
"/nx-api/cypress/migrations/remove-experimental-fetch-polyfill": {
|
||||||
|
"description": "Removes the `experimentalFetchPolyfill` configuration option.",
|
||||||
|
"file": "generated/packages/cypress/migrations/remove-experimental-fetch-polyfill.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "remove-experimental-fetch-polyfill",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "/nx-api/cypress/migrations/remove-experimental-fetch-polyfill",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
"/nx-api/cypress/migrations/replace-experimental-just-in-time-compile": {
|
||||||
|
"description": "Replaces the `experimentalJustInTimeCompile` configuration option with the new `justInTimeCompile` configuration option.",
|
||||||
|
"file": "generated/packages/cypress/migrations/replace-experimental-just-in-time-compile.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "replace-experimental-just-in-time-compile",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "/nx-api/cypress/migrations/replace-experimental-just-in-time-compile",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
"/nx-api/cypress/migrations/update-component-testing-mount-imports": {
|
||||||
|
"description": "Updates the module specifier for the Component Testing `mount` function.",
|
||||||
|
"file": "generated/packages/cypress/migrations/update-component-testing-mount-imports.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "update-component-testing-mount-imports",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "/nx-api/cypress/migrations/update-component-testing-mount-imports",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
"/nx-api/cypress/migrations/20.8.0-package-updates": {
|
||||||
|
"description": "",
|
||||||
|
"file": "generated/packages/cypress/migrations/20.8.0-package-updates.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "20.8.0-package-updates",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "/nx-api/cypress/migrations/20.8.0-package-updates",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
"/nx-api/cypress/migrations/update-19-6-0-update-ci-webserver-for-vite": {
|
"/nx-api/cypress/migrations/update-19-6-0-update-ci-webserver-for-vite": {
|
||||||
"description": "Update ciWebServerCommand to use static serve for the application.",
|
"description": "Update ciWebServerCommand to use static serve for the application.",
|
||||||
"file": "generated/packages/cypress/migrations/update-19-6-0-update-ci-webserver-for-vite.json",
|
"file": "generated/packages/cypress/migrations/update-19-6-0-update-ci-webserver-for-vite.json",
|
||||||
|
|||||||
@ -1364,6 +1364,56 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"migrations": [
|
"migrations": [
|
||||||
|
{
|
||||||
|
"description": "Replaces the `experimentalSkipDomainInjection` configuration option with the new `injectDocumentDomain` configuration option.",
|
||||||
|
"file": "generated/packages/cypress/migrations/set-inject-document-domain.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "set-inject-document-domain",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "cypress/migrations/set-inject-document-domain",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Removes the `experimentalFetchPolyfill` configuration option.",
|
||||||
|
"file": "generated/packages/cypress/migrations/remove-experimental-fetch-polyfill.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "remove-experimental-fetch-polyfill",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "cypress/migrations/remove-experimental-fetch-polyfill",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Replaces the `experimentalJustInTimeCompile` configuration option with the new `justInTimeCompile` configuration option.",
|
||||||
|
"file": "generated/packages/cypress/migrations/replace-experimental-just-in-time-compile.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "replace-experimental-just-in-time-compile",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "cypress/migrations/replace-experimental-just-in-time-compile",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Updates the module specifier for the Component Testing `mount` function.",
|
||||||
|
"file": "generated/packages/cypress/migrations/update-component-testing-mount-imports.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "update-component-testing-mount-imports",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "cypress/migrations/update-component-testing-mount-imports",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"file": "generated/packages/cypress/migrations/20.8.0-package-updates.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "20.8.0-package-updates",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"originalFilePath": "/packages/cypress",
|
||||||
|
"path": "cypress/migrations/20.8.0-package-updates",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Update ciWebServerCommand to use static serve for the application.",
|
"description": "Update ciWebServerCommand to use static serve for the application.",
|
||||||
"file": "generated/packages/cypress/migrations/update-19-6-0-update-ci-webserver-for-vite.json",
|
"file": "generated/packages/cypress/migrations/update-19-6-0-update-ci-webserver-for-vite.json",
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "20.8.0-package-updates",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"x-prompt": "Do you want to update the Cypress version to v14?",
|
||||||
|
"requires": { "cypress": ">=13.0.0 <14.0.0" },
|
||||||
|
"packages": {
|
||||||
|
"cypress": { "version": "^14.2.1", "alwaysAddToPackageJson": false },
|
||||||
|
"@cypress/vite-dev-server": {
|
||||||
|
"version": "^6.0.3",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
},
|
||||||
|
"@cypress/webpack-dev-server": {
|
||||||
|
"version": "^4.0.2",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aliases": [],
|
||||||
|
"description": "",
|
||||||
|
"hidden": false,
|
||||||
|
"implementation": "",
|
||||||
|
"path": "/packages/cypress",
|
||||||
|
"schema": null,
|
||||||
|
"type": "migration"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "remove-experimental-fetch-polyfill",
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"requires": { "cypress": ">=14.0.0" },
|
||||||
|
"description": "Removes the `experimentalFetchPolyfill` configuration option.",
|
||||||
|
"implementation": "/packages/cypress/src/migrations/update-20-8-0/remove-experimental-fetch-polyfill.ts",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/cypress",
|
||||||
|
"schema": null,
|
||||||
|
"type": "migration",
|
||||||
|
"examplesFile": "#### Remove `experimentalFetchPolyfill` Configuration Option\n\nRemoves the `experimentalFetchPolyfill` configuration option that was removed in Cypress v14. Read more at the [migration notes](<https://docs.cypress.io/app/references/changelog#:~:text=The%20experimentalFetchPolyfill%20configuration%20option%20was,cy.intercept()%20for%20handling%20fetch%20requests>).\n\n#### Examples\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1-e2e/cypress.config.ts\" %}\nimport { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n e2e: {\n ...nxE2EPreset(__filename, {\n cypressDir: 'src',\n bundler: 'vite',\n webServerCommands: {\n default: 'pnpm exec nx run app1:dev',\n production: 'pnpm exec nx run app1:dev',\n },\n ciWebServerCommand: 'pnpm exec nx run app1:dev',\n ciBaseUrl: 'http://localhost:4200',\n }),\n baseUrl: 'http://localhost:4200',\n experimentalFetchPolyfill: true,\n },\n});\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1-e2e/cypress.config.ts\" %}\nimport { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n e2e: {\n ...nxE2EPreset(__filename, {\n cypressDir: 'src',\n bundler: 'vite',\n webServerCommands: {\n default: 'pnpm exec nx run app1:dev',\n production: 'pnpm exec nx run app1:dev',\n },\n ciWebServerCommand: 'pnpm exec nx run app1:dev',\n ciBaseUrl: 'http://localhost:4200',\n }),\n baseUrl: 'http://localhost:4200',\n },\n});\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "replace-experimental-just-in-time-compile",
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"requires": { "cypress": ">=14.0.0" },
|
||||||
|
"description": "Replaces the `experimentalJustInTimeCompile` configuration option with the new `justInTimeCompile` configuration option.",
|
||||||
|
"implementation": "/packages/cypress/src/migrations/update-20-8-0/replace-experimental-just-in-time-compile.ts",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/cypress",
|
||||||
|
"schema": null,
|
||||||
|
"type": "migration",
|
||||||
|
"examplesFile": "#### Replace the `experimentalJustInTimeCompile` Configuration Option with `justInTimeCompile`\n\nReplaces the `experimentalJustInTimeCompile` configuration option with the new `justInTimeCompile` configuration option. Read more at the [migration notes](https://docs.cypress.io/app/references/migration-guide#CT-Just-in-Time-Compile-changes).\n\n#### Examples\n\nIf the `experimentalJustInTimeCompile` configuration option is present and set to `true`, the migration will remove it. This is to account for the fact that JIT compilation is the default behavior in Cypress v14.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n component: {\n devServer: {\n framework: 'angular',\n bundler: 'webpack',\n },\n experimentalJustInTimeCompile: true,\n },\n});\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n component: {\n devServer: {\n framework: 'angular',\n bundler: 'webpack',\n },\n },\n});\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf the `experimentalJustInTimeCompile` configuration option is set to `false` and it is using webpack, the migration will rename it to `justInTimeCompile`.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n component: {\n devServer: {\n framework: 'angular',\n bundler: 'webpack',\n },\n experimentalJustInTimeCompile: false,\n },\n});\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/cypress.config.ts\" highlightLines=[9] %}\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n component: {\n devServer: {\n framework: 'angular',\n bundler: 'webpack',\n },\n justInTimeCompile: false,\n },\n});\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf the `experimentalJustInTimeCompile` configuration is set to any value and it is using Vite, the migration will remove it. This is to account for the fact that JIT compilation no longer applies to Vite.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n component: {\n devServer: {\n framework: 'react',\n bundler: 'vite',\n },\n experimentalJustInTimeCompile: false,\n },\n});\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n component: {\n devServer: {\n framework: 'react',\n bundler: 'vite',\n },\n },\n});\n```\n\n{% /tab %}\n{% /tabs %}\n"
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "update-component-testing-mount-imports",
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"requires": { "cypress": ">=14.0.0" },
|
||||||
|
"description": "Updates the module specifier for the Component Testing `mount` function.",
|
||||||
|
"implementation": "/packages/cypress/src/migrations/update-20-8-0/update-component-testing-mount-imports.ts",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/cypress",
|
||||||
|
"schema": null,
|
||||||
|
"type": "migration",
|
||||||
|
"examplesFile": "#### Update Component Testing `mount` Imports\n\nUpdates the relevant module specifiers when importing the `mount` function and using the Angular or React frameworks. Read more at the [Angular migration notes](https://docs.cypress.io/app/references/migration-guide#Angular-1720-CT-no-longer-supported) and the [React migration notes](https://docs.cypress.io/app/references/migration-guide#React-18-CT-no-longer-supported).\n\n#### Examples\n\nIf using the Angular framework with a version greater than or equal to v17.2.0 and importing the `mount` function from the `cypress/angular-signals` module, the migration will update the import to use the `cypress/angular` module.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/cypress/support/component.ts\" %}\nimport { mount } from 'cypress/angular-signals';\nimport './commands';\n\ndeclare global {\n namespace Cypress {\n interface Chainable<Subject> {\n mount: typeof mount;\n }\n }\n}\n\nCypress.Commands.add('mount', mount);\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/cypress/support/component.ts\" highlightLines=[1] %}\nimport { mount } from 'cypress/angular';\nimport './commands';\n\ndeclare global {\n namespace Cypress {\n interface Chainable<Subject> {\n mount: typeof mount;\n }\n }\n}\n\nCypress.Commands.add('mount', mount);\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf using the Angular framework with a version lower than v17.2.0 and importing the `mount` function from the `cypress/angular` module, the migration will install the `@cypress/angular@2` package and update the import to use the `@cypress/angular` module.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"package.json\" %}\n{\n \"name\": \"@my-repo/source\",\n \"dependencies\": {\n ...\n \"cypress\": \"^14.2.1\"\n }\n}\n```\n\n```ts {% fileName=\"apps/app1/cypress/support/component.ts\" %}\nimport { mount } from 'cypress/angular';\nimport './commands';\n\ndeclare global {\n namespace Cypress {\n interface Chainable<Subject> {\n mount: typeof mount;\n }\n }\n}\n\nCypress.Commands.add('mount', mount);\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"package.json\" highlightLines=[6] %}\n{\n \"name\": \"@my-repo/source\",\n \"dependencies\": {\n ...\n \"cypress\": \"^14.2.1\",\n \"@cypress/angular\": \"^2.1.0\"\n }\n}\n```\n\n```ts {% fileName=\"apps/app1/cypress/support/component.ts\" highlightLines=[1] %}\nimport { mount } from '@cypress/angular';\nimport './commands';\n\ndeclare global {\n namespace Cypress {\n interface Chainable<Subject> {\n mount: typeof mount;\n }\n }\n}\n\nCypress.Commands.add('mount', mount);\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf using the React framework and importing the `mount` function from the `cypress/react18` module, the migration will update the import to use the `cypress/react` module.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/cypress/support/component.ts\" %}\nimport { mount } from 'cypress/react18';\nimport './commands';\n\ndeclare global {\n namespace Cypress {\n interface Chainable<Subject> {\n mount: typeof mount;\n }\n }\n}\n\nCypress.Commands.add('mount', mount);\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/cypress/support/component.ts\" highlightLines=[1] %}\nimport { mount } from 'cypress/react';\nimport './commands';\n\ndeclare global {\n namespace Cypress {\n interface Chainable<Subject> {\n mount: typeof mount;\n }\n }\n}\n\nCypress.Commands.add('mount', mount);\n```\n\n{% /tab %}\n{% /tabs %}\n"
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import { Tree, updateProjectConfiguration, writeJson } from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
import * as devkit from '@nx/devkit';
|
||||||
import {
|
import {
|
||||||
NxJsonConfiguration,
|
NxJsonConfiguration,
|
||||||
@ -7,7 +6,9 @@ import {
|
|||||||
readJson,
|
readJson,
|
||||||
readNxJson,
|
readNxJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
updateJson,
|
updateJson,
|
||||||
|
updateProjectConfiguration,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Linter } from '@nx/eslint';
|
import { Linter } from '@nx/eslint';
|
||||||
@ -25,7 +26,10 @@ import { generateTestApplication } from '../utils/testing';
|
|||||||
import type { Schema } from './schema';
|
import type { Schema } from './schema';
|
||||||
|
|
||||||
// need to mock cypress otherwise it'll use installed version in this repo's package.json
|
// need to mock cypress otherwise it'll use installed version in this repo's package.json
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
jest.mock('enquirer');
|
jest.mock('enquirer');
|
||||||
jest.mock('@nx/devkit', () => {
|
jest.mock('@nx/devkit', () => {
|
||||||
const original = jest.requireActual('@nx/devkit');
|
const original = jest.requireActual('@nx/devkit');
|
||||||
@ -42,8 +46,8 @@ jest.mock('@nx/devkit', () => {
|
|||||||
describe('app', () => {
|
describe('app', () => {
|
||||||
let appTree: Tree;
|
let appTree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockedInstalledCypressVersion.mockReturnValue(null);
|
mockedInstalledCypressVersion.mockReturnValue(null);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { assertMinimumCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { assertMinimumCypressVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import { Tree } from '@nx/devkit';
|
import { Tree } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Linter } from '@nx/eslint';
|
import { Linter } from '@nx/eslint';
|
||||||
@ -10,7 +10,7 @@ import { generateTestLibrary } from '../utils/testing';
|
|||||||
import { componentTestGenerator } from './component-test';
|
import { componentTestGenerator } from './component-test';
|
||||||
import { EOL } from 'node:os';
|
import { EOL } from 'node:os';
|
||||||
|
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions');
|
||||||
|
|
||||||
describe('Angular Cypress Component Test Generator', () => {
|
describe('Angular Cypress Component Test Generator', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
|
|||||||
@ -19,8 +19,8 @@ export async function componentTestGenerator(
|
|||||||
) {
|
) {
|
||||||
ensurePackage('@nx/cypress', nxVersion);
|
ensurePackage('@nx/cypress', nxVersion);
|
||||||
const { assertMinimumCypressVersion } = <
|
const { assertMinimumCypressVersion } = <
|
||||||
typeof import('@nx/cypress/src/utils/cypress-version')
|
typeof import('@nx/cypress/src/utils/versions')
|
||||||
>require('@nx/cypress/src/utils/cypress-version');
|
>require('@nx/cypress/src/utils/versions');
|
||||||
assertMinimumCypressVersion(10);
|
assertMinimumCypressVersion(10);
|
||||||
const { root } = readProjectConfiguration(tree, options.project);
|
const { root } = readProjectConfiguration(tree, options.project);
|
||||||
const componentDirPath = joinPathFragments(root, options.componentDir);
|
const componentDirPath = joinPathFragments(root, options.componentDir);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import {
|
import {
|
||||||
DependencyType,
|
DependencyType,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
@ -23,7 +23,10 @@ import { librarySecondaryEntryPointGenerator } from '../library-secondary-entry-
|
|||||||
import { generateTestApplication, generateTestLibrary } from '../utils/testing';
|
import { generateTestApplication, generateTestLibrary } from '../utils/testing';
|
||||||
import { cypressComponentConfiguration } from './cypress-component-configuration';
|
import { cypressComponentConfiguration } from './cypress-component-configuration';
|
||||||
|
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
// nested code imports graph from the repo, which might have innacurate graph version
|
// nested code imports graph from the repo, which might have innacurate graph version
|
||||||
jest.mock('nx/src/project-graph/project-graph', () => ({
|
jest.mock('nx/src/project-graph/project-graph', () => ({
|
||||||
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
|
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
|
||||||
@ -33,8 +36,8 @@ jest.mock('nx/src/project-graph/project-graph', () => ({
|
|||||||
describe('Cypress Component Testing Configuration', () => {
|
describe('Cypress Component Testing Configuration', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
// TODO(@leosvelperez): Turn this to adding the plugin
|
// TODO(@leosvelperez): Turn this to adding the plugin
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
// mock so we can test multiple versions
|
// mock so we can test multiple versions
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual<any>('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
// mock bc the nxE2EPreset uses fs for path normalization
|
// mock bc the nxE2EPreset uses fs for path normalization
|
||||||
jest.mock('fs', () => {
|
jest.mock('fs', () => {
|
||||||
return {
|
return {
|
||||||
@ -12,7 +15,7 @@ jest.mock('fs', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import { formatFiles, ProjectConfiguration, Tree } from '@nx/devkit';
|
import { formatFiles, ProjectConfiguration, Tree } from '@nx/devkit';
|
||||||
import {
|
import {
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
@ -30,8 +33,9 @@ const mockedLogger = { warn: jest.fn() };
|
|||||||
describe('e2e migrator', () => {
|
describe('e2e migrator', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let migrator: E2eMigrator;
|
let migrator: E2eMigrator;
|
||||||
let mockedInstalledCypressVersion = installedCypressVersion as jest.Mock<
|
let mockedInstalledCypressVersion =
|
||||||
ReturnType<typeof installedCypressVersion>
|
getInstalledCypressMajorVersion as jest.Mock<
|
||||||
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
function addProject(
|
function addProject(
|
||||||
|
|||||||
@ -262,9 +262,9 @@ export class E2eMigrator extends ProjectMigrator<SupportedTargets> {
|
|||||||
} else if (this.isCypressE2eProject()) {
|
} else if (this.isCypressE2eProject()) {
|
||||||
ensurePackage('@nx/cypress', nxVersion);
|
ensurePackage('@nx/cypress', nxVersion);
|
||||||
const {
|
const {
|
||||||
installedCypressVersion,
|
getInstalledCypressMajorVersion,
|
||||||
} = require('@nx/cypress/src/utils/cypress-version');
|
} = require('@nx/cypress/src/utils/versions');
|
||||||
this.cypressInstalledVersion = installedCypressVersion();
|
this.cypressInstalledVersion = getInstalledCypressMajorVersion(this.tree);
|
||||||
this.project = {
|
this.project = {
|
||||||
...this.project,
|
...this.project,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@ -11,6 +11,42 @@
|
|||||||
"version": "19.6.0-beta.4",
|
"version": "19.6.0-beta.4",
|
||||||
"description": "Update ciWebServerCommand to use static serve for the application.",
|
"description": "Update ciWebServerCommand to use static serve for the application.",
|
||||||
"implementation": "./src/migrations/update-19-6-0/update-ci-webserver-for-static-serve"
|
"implementation": "./src/migrations/update-19-6-0/update-ci-webserver-for-static-serve"
|
||||||
|
},
|
||||||
|
"set-inject-document-domain": {
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"requires": {
|
||||||
|
"cypress": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"description": "Replaces the `experimentalSkipDomainInjection` configuration option with the new `injectDocumentDomain` configuration option.",
|
||||||
|
"implementation": "./src/migrations/update-20-8-0/set-inject-document-domain"
|
||||||
|
},
|
||||||
|
"remove-experimental-fetch-polyfill": {
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"requires": {
|
||||||
|
"cypress": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"description": "Removes the `experimentalFetchPolyfill` configuration option.",
|
||||||
|
"implementation": "./src/migrations/update-20-8-0/remove-experimental-fetch-polyfill"
|
||||||
|
},
|
||||||
|
"replace-experimental-just-in-time-compile": {
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"requires": {
|
||||||
|
"cypress": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"description": "Replaces the `experimentalJustInTimeCompile` configuration option with the new `justInTimeCompile` configuration option.",
|
||||||
|
"implementation": "./src/migrations/update-20-8-0/replace-experimental-just-in-time-compile"
|
||||||
|
},
|
||||||
|
"update-component-testing-mount-imports": {
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"requires": {
|
||||||
|
"cypress": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"description": "Updates the module specifier for the Component Testing `mount` function.",
|
||||||
|
"implementation": "./src/migrations/update-20-8-0/update-component-testing-mount-imports"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageJsonUpdates": {
|
"packageJsonUpdates": {
|
||||||
@ -59,6 +95,27 @@
|
|||||||
"alwaysAddToPackageJson": false
|
"alwaysAddToPackageJson": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"20.8.0": {
|
||||||
|
"version": "20.8.0-beta.0",
|
||||||
|
"x-prompt": "Do you want to update the Cypress version to v14?",
|
||||||
|
"requires": {
|
||||||
|
"cypress": ">=13.0.0 <14.0.0"
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"cypress": {
|
||||||
|
"version": "^14.2.1",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
},
|
||||||
|
"@cypress/vite-dev-server": {
|
||||||
|
"version": "^6.0.3",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
},
|
||||||
|
"@cypress/webpack-dev-server": {
|
||||||
|
"version": "^4.0.2",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,10 +41,11 @@
|
|||||||
"@nx/js": "file:../js",
|
"@nx/js": "file:../js",
|
||||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||||
"detect-port": "^1.5.1",
|
"detect-port": "^1.5.1",
|
||||||
|
"semver": "^7.6.3",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"cypress": ">= 3 < 14"
|
"cypress": ">= 3 < 15"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"cypress": {
|
"cypress": {
|
||||||
|
|||||||
@ -1,15 +1,18 @@
|
|||||||
import { getTempTailwindPath } from '../../utils/ct-helpers';
|
|
||||||
import { ExecutorContext, stripIndents } from '@nx/devkit';
|
import { ExecutorContext, stripIndents } from '@nx/devkit';
|
||||||
|
import * as detectPort from 'detect-port';
|
||||||
import * as executorUtils from 'nx/src/command-line/run/executor-utils';
|
import * as executorUtils from 'nx/src/command-line/run/executor-utils';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
import { getTempTailwindPath } from '../../utils/ct-helpers';
|
||||||
|
import { getInstalledCypressMajorVersion } from '../../utils/versions';
|
||||||
import cypressExecutor, { CypressExecutorOptions } from './cypress.impl';
|
import cypressExecutor, { CypressExecutorOptions } from './cypress.impl';
|
||||||
|
|
||||||
jest.mock('@nx/devkit');
|
jest.mock('@nx/devkit');
|
||||||
let devkit = require('@nx/devkit');
|
let devkit = require('@nx/devkit');
|
||||||
jest.mock('detect-port', () => jest.fn().mockResolvedValue(4200));
|
jest.mock('detect-port', () => jest.fn().mockResolvedValue(4200));
|
||||||
import * as detectPort from 'detect-port';
|
jest.mock('../../utils/versions', () => ({
|
||||||
jest.mock('../../utils/cypress-version');
|
...jest.requireActual('../../utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
jest.mock('../../utils/ct-helpers');
|
jest.mock('../../utils/ct-helpers');
|
||||||
const Cypress = require('cypress');
|
const Cypress = require('cypress');
|
||||||
|
|
||||||
@ -29,8 +32,8 @@ describe('Cypress builder', () => {
|
|||||||
};
|
};
|
||||||
let mockContext: ExecutorContext;
|
let mockContext: ExecutorContext;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as any;
|
> = getInstalledCypressMajorVersion as any;
|
||||||
mockContext = {
|
mockContext = {
|
||||||
root: '/root',
|
root: '/root',
|
||||||
workspace: { projects: {} },
|
workspace: { projects: {} },
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { ExecutorContext, logger, stripIndents } from '@nx/devkit';
|
|||||||
import { existsSync, readdirSync, unlinkSync } from 'fs';
|
import { existsSync, readdirSync, unlinkSync } from 'fs';
|
||||||
import { basename, dirname } from 'path';
|
import { basename, dirname } from 'path';
|
||||||
import { getTempTailwindPath } from '../../utils/ct-helpers';
|
import { getTempTailwindPath } from '../../utils/ct-helpers';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '../../utils/versions';
|
||||||
import { startDevServer } from '../../utils/start-dev-server';
|
import { startDevServer } from '../../utils/start-dev-server';
|
||||||
|
|
||||||
const Cypress = require('cypress'); // @NOTE: Importing via ES6 messes the whole test dependencies.
|
const Cypress = require('cypress'); // @NOTE: Importing via ES6 messes the whole test dependencies.
|
||||||
@ -53,7 +53,8 @@ export default async function cypressExecutor(
|
|||||||
options: CypressExecutorOptions,
|
options: CypressExecutorOptions,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
) {
|
) {
|
||||||
options = normalizeOptions(options, context);
|
const installedCypressMajorVersion = getInstalledCypressMajorVersion();
|
||||||
|
options = normalizeOptions(options, context, installedCypressMajorVersion);
|
||||||
// this is used by cypress component testing presets to build the executor contexts with the correct configuration options.
|
// this is used by cypress component testing presets to build the executor contexts with the correct configuration options.
|
||||||
process.env.NX_CYPRESS_TARGET_CONFIGURATION = context.configurationName;
|
process.env.NX_CYPRESS_TARGET_CONFIGURATION = context.configurationName;
|
||||||
let success;
|
let success;
|
||||||
@ -61,10 +62,14 @@ export default async function cypressExecutor(
|
|||||||
const generatorInstance = startDevServer(options, context);
|
const generatorInstance = startDevServer(options, context);
|
||||||
for await (const devServerValues of generatorInstance) {
|
for await (const devServerValues of generatorInstance) {
|
||||||
try {
|
try {
|
||||||
success = await runCypress(devServerValues.baseUrl, {
|
success = await runCypress(
|
||||||
|
devServerValues.baseUrl,
|
||||||
|
{
|
||||||
...options,
|
...options,
|
||||||
portLockFilePath: devServerValues.portLockFilePath,
|
portLockFilePath: devServerValues.portLockFilePath,
|
||||||
});
|
},
|
||||||
|
installedCypressMajorVersion
|
||||||
|
);
|
||||||
if (!options.watch) {
|
if (!options.watch) {
|
||||||
generatorInstance.return();
|
generatorInstance.return();
|
||||||
break;
|
break;
|
||||||
@ -81,7 +86,8 @@ export default async function cypressExecutor(
|
|||||||
|
|
||||||
function normalizeOptions(
|
function normalizeOptions(
|
||||||
options: CypressExecutorOptions,
|
options: CypressExecutorOptions,
|
||||||
context: ExecutorContext
|
context: ExecutorContext,
|
||||||
|
installedCypressMajorVersion: number | null
|
||||||
): NormalizedCypressExecutorOptions {
|
): NormalizedCypressExecutorOptions {
|
||||||
options.env = options.env || {};
|
options.env = options.env || {};
|
||||||
if (options.testingType === 'component') {
|
if (options.testingType === 'component') {
|
||||||
@ -90,19 +96,22 @@ function normalizeOptions(
|
|||||||
options.ctTailwindPath = getTempTailwindPath(context);
|
options.ctTailwindPath = getTempTailwindPath(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkSupportedBrowser(options);
|
checkSupportedBrowser(options, installedCypressMajorVersion);
|
||||||
warnDeprecatedHeadless(options);
|
warnDeprecatedHeadless(options, installedCypressMajorVersion);
|
||||||
warnDeprecatedCypressVersion();
|
warnDeprecatedCypressVersion(installedCypressMajorVersion);
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkSupportedBrowser({ browser }: CypressExecutorOptions) {
|
function checkSupportedBrowser(
|
||||||
|
{ browser }: CypressExecutorOptions,
|
||||||
|
installedCypressMajorVersion: number | null
|
||||||
|
) {
|
||||||
// Browser was not passed in as an option, cypress will use whatever default it has set and we dont need to check it
|
// Browser was not passed in as an option, cypress will use whatever default it has set and we dont need to check it
|
||||||
if (!browser) {
|
if (!browser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installedCypressVersion() >= 4 && browser == 'canary') {
|
if (installedCypressMajorVersion >= 4 && browser == 'canary') {
|
||||||
logger.warn(stripIndents`
|
logger.warn(stripIndents`
|
||||||
Warning:
|
Warning:
|
||||||
You are using a browser that is not supported by cypress v4+.
|
You are using a browser that is not supported by cypress v4+.
|
||||||
@ -115,7 +124,7 @@ function checkSupportedBrowser({ browser }: CypressExecutorOptions) {
|
|||||||
|
|
||||||
const supportedV3Browsers = ['electron', 'chrome', 'canary', 'chromium'];
|
const supportedV3Browsers = ['electron', 'chrome', 'canary', 'chromium'];
|
||||||
if (
|
if (
|
||||||
installedCypressVersion() <= 3 &&
|
installedCypressMajorVersion <= 3 &&
|
||||||
!supportedV3Browsers.includes(browser)
|
!supportedV3Browsers.includes(browser)
|
||||||
) {
|
) {
|
||||||
logger.warn(stripIndents`
|
logger.warn(stripIndents`
|
||||||
@ -126,8 +135,11 @@ function checkSupportedBrowser({ browser }: CypressExecutorOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function warnDeprecatedHeadless({ headless }: CypressExecutorOptions) {
|
function warnDeprecatedHeadless(
|
||||||
if (installedCypressVersion() < 8 || headless === undefined) {
|
{ headless }: CypressExecutorOptions,
|
||||||
|
installedCypressMajorVersion: number | null
|
||||||
|
) {
|
||||||
|
if (installedCypressMajorVersion < 8 || headless === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +152,10 @@ function warnDeprecatedHeadless({ headless }: CypressExecutorOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function warnDeprecatedCypressVersion() {
|
function warnDeprecatedCypressVersion(
|
||||||
if (installedCypressVersion() < 10) {
|
installedCypressMajorVersion: number | null
|
||||||
|
) {
|
||||||
|
if (installedCypressMajorVersion < 10) {
|
||||||
logger.warn(stripIndents`
|
logger.warn(stripIndents`
|
||||||
NOTE:
|
NOTE:
|
||||||
Support for Cypress versions < 10 is deprecated. Please upgrade to at least Cypress version 10.
|
Support for Cypress versions < 10 is deprecated. Please upgrade to at least Cypress version 10.
|
||||||
@ -157,9 +171,9 @@ A generator to migrate from v8 to v10 is provided. See https://nx.dev/cypress/v1
|
|||||||
*/
|
*/
|
||||||
async function runCypress(
|
async function runCypress(
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
opts: NormalizedCypressExecutorOptions
|
opts: NormalizedCypressExecutorOptions,
|
||||||
|
installedCypressMajorVersion: number | null
|
||||||
) {
|
) {
|
||||||
const cypressVersion = installedCypressVersion();
|
|
||||||
// Cypress expects the folder where a cypress config is present
|
// Cypress expects the folder where a cypress config is present
|
||||||
const projectFolderPath = dirname(opts.cypressConfig);
|
const projectFolderPath = dirname(opts.cypressConfig);
|
||||||
const options: any = {
|
const options: any = {
|
||||||
@ -200,7 +214,7 @@ async function runCypress(
|
|||||||
options.ciBuildId = opts.ciBuildId?.toString();
|
options.ciBuildId = opts.ciBuildId?.toString();
|
||||||
options.group = opts.group;
|
options.group = opts.group;
|
||||||
// renamed in cy 10
|
// renamed in cy 10
|
||||||
if (cypressVersion >= 10) {
|
if (installedCypressMajorVersion >= 10) {
|
||||||
options.config ??= {};
|
options.config ??= {};
|
||||||
options.config[opts.testingType] = {
|
options.config[opts.testingType] = {
|
||||||
excludeSpecPattern: opts.ignoreTestFiles,
|
excludeSpecPattern: opts.ignoreTestFiles,
|
||||||
|
|||||||
@ -12,11 +12,15 @@ import {
|
|||||||
updateProjectConfiguration,
|
updateProjectConfiguration,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '../../utils/versions';
|
||||||
import { componentConfigurationGenerator } from './component-configuration';
|
import { componentConfigurationGenerator } from './component-configuration';
|
||||||
import { cypressInitGenerator } from '../init/init';
|
import { cypressInitGenerator } from '../init/init';
|
||||||
|
|
||||||
jest.mock('../../utils/cypress-version');
|
jest.mock('../../utils/versions', () => ({
|
||||||
|
...jest.requireActual('../../utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
let projectConfig: ProjectConfiguration = {
|
let projectConfig: ProjectConfiguration = {
|
||||||
projectType: 'library',
|
projectType: 'library',
|
||||||
sourceRoot: 'libs/cool-lib/src',
|
sourceRoot: 'libs/cool-lib/src',
|
||||||
@ -39,8 +43,8 @@ let projectConfig: ProjectConfiguration = {
|
|||||||
describe('Cypress Component Configuration', () => {
|
describe('Cypress Component Configuration', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|||||||
@ -2,31 +2,27 @@ import {
|
|||||||
addDependenciesToPackageJson,
|
addDependenciesToPackageJson,
|
||||||
formatFiles,
|
formatFiles,
|
||||||
generateFiles,
|
generateFiles,
|
||||||
|
GeneratorCallback,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
offsetFromRoot,
|
offsetFromRoot,
|
||||||
ProjectConfiguration,
|
ProjectConfiguration,
|
||||||
|
readJson,
|
||||||
readNxJson,
|
readNxJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
|
runTasksInSerial,
|
||||||
Tree,
|
Tree,
|
||||||
updateJson,
|
updateJson,
|
||||||
updateProjectConfiguration,
|
|
||||||
updateNxJson,
|
updateNxJson,
|
||||||
runTasksInSerial,
|
updateProjectConfiguration,
|
||||||
GeneratorCallback,
|
|
||||||
readJson,
|
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
cypressVersion,
|
getInstalledCypressMajorVersion,
|
||||||
cypressViteDevServerVersion,
|
versions,
|
||||||
cypressWebpackVersion,
|
|
||||||
htmlWebpackPluginVersion,
|
|
||||||
} from '../../utils/versions';
|
} from '../../utils/versions';
|
||||||
import { CypressComponentConfigurationSchema } from './schema';
|
|
||||||
import { addBaseCypressSetup } from '../base-setup/base-setup';
|
import { addBaseCypressSetup } from '../base-setup/base-setup';
|
||||||
import init from '../init/init';
|
import init from '../init/init';
|
||||||
|
import { CypressComponentConfigurationSchema } from './schema';
|
||||||
|
|
||||||
type NormalizeCTOptions = ReturnType<typeof normalizeOptions>;
|
type NormalizeCTOptions = ReturnType<typeof normalizeOptions>;
|
||||||
|
|
||||||
@ -49,12 +45,14 @@ export async function componentConfigurationGeneratorInternal(
|
|||||||
const tasks: GeneratorCallback[] = [];
|
const tasks: GeneratorCallback[] = [];
|
||||||
const opts = normalizeOptions(tree, options);
|
const opts = normalizeOptions(tree, options);
|
||||||
|
|
||||||
|
if (!getInstalledCypressMajorVersion(tree)) {
|
||||||
tasks.push(
|
tasks.push(
|
||||||
await init(tree, {
|
await init(tree, {
|
||||||
...opts,
|
...opts,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
const hasPlugin = nxJson.plugins?.some((p) =>
|
const hasPlugin = nxJson.plugins?.some((p) =>
|
||||||
@ -86,7 +84,7 @@ function normalizeOptions(
|
|||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: CypressComponentConfigurationSchema
|
options: CypressComponentConfigurationSchema
|
||||||
) {
|
) {
|
||||||
const cyVersion = installedCypressVersion();
|
const cyVersion = getInstalledCypressMajorVersion(tree);
|
||||||
if (cyVersion && cyVersion < 10) {
|
if (cyVersion && cyVersion < 10) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cypress version of 10 or higher is required to use component testing. See the migration guide to upgrade. https://nx.dev/cypress/v11-migration-guide'
|
'Cypress version of 10 or higher is required to use component testing. See the migration guide to upgrade. https://nx.dev/cypress/v11-migration-guide'
|
||||||
@ -107,18 +105,21 @@ function normalizeOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateDeps(tree: Tree, opts: NormalizeCTOptions) {
|
function updateDeps(tree: Tree, opts: NormalizeCTOptions) {
|
||||||
|
const pkgVersions = versions(tree);
|
||||||
|
|
||||||
const devDeps = {
|
const devDeps = {
|
||||||
cypress: cypressVersion,
|
cypress: pkgVersions.cypressVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (opts.bundler === 'vite') {
|
if (opts.bundler === 'vite') {
|
||||||
devDeps['@cypress/vite-dev-server'] = cypressViteDevServerVersion;
|
devDeps['@cypress/vite-dev-server'] =
|
||||||
|
pkgVersions.cypressViteDevServerVersion;
|
||||||
} else {
|
} else {
|
||||||
devDeps['@cypress/webpack-dev-server'] = cypressWebpackVersion;
|
devDeps['@cypress/webpack-dev-server'] = pkgVersions.cypressWebpackVersion;
|
||||||
devDeps['html-webpack-plugin'] = htmlWebpackPluginVersion;
|
devDeps['html-webpack-plugin'] = pkgVersions.htmlWebpackPluginVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addDependenciesToPackageJson(tree, {}, devDeps);
|
return addDependenciesToPackageJson(tree, {}, devDeps, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addProjectFiles(
|
function addProjectFiles(
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Cypress e2e configuration v10+ should not override eslint settings if preset 1`] = `
|
|
||||||
{
|
|
||||||
"extends": [
|
|
||||||
"plugin:cypress/recommended",
|
|
||||||
"../../.eslintrc.json",
|
|
||||||
],
|
|
||||||
"ignorePatterns": [
|
|
||||||
"!**/*",
|
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"extends": [
|
|
||||||
"plugin:@nx/angular",
|
|
||||||
"plugin:@angular-eslint/template/process-inline-templates",
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"*.ts",
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/component-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"prefix": "cy-port-test",
|
|
||||||
"style": "kebab-case",
|
|
||||||
"type": "element",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@angular-eslint/directive-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"prefix": "cyPortTest",
|
|
||||||
"style": "camelCase",
|
|
||||||
"type": "attribute",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"extends": [
|
|
||||||
"plugin:@nx/angular-template",
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"*.html",
|
|
||||||
],
|
|
||||||
"rules": {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"*.cy.{ts,js,tsx,jsx}",
|
|
||||||
"cypress/**/*.{ts,js,tsx,jsx}",
|
|
||||||
],
|
|
||||||
"rules": {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@ -12,32 +12,16 @@ import {
|
|||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import cypressE2EConfigurationGenerator from './configuration';
|
import cypressE2EConfigurationGenerator from './configuration';
|
||||||
|
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
|
||||||
import { cypressInitGenerator } from '../init/init';
|
import { cypressInitGenerator } from '../init/init';
|
||||||
|
|
||||||
jest.mock('../../utils/cypress-version');
|
|
||||||
|
|
||||||
describe('Cypress e2e configuration', () => {
|
describe('Cypress e2e configuration', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
|
||||||
ReturnType<typeof installedCypressVersion>
|
|
||||||
> = installedCypressVersion as never;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
tree.write('.eslintrc.json', '{}'); // we are explicitly checking for existance of config type
|
tree.write('.eslintrc.json', '{}'); // we are explicitly checking for existance of config type
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('v10+', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add web server commands to the cypress config when the @nx/cypress/plugin is present', async () => {
|
it('should add web server commands to the cypress config when the @nx/cypress/plugin is present', async () => {
|
||||||
await cypressInitGenerator(tree, {
|
await cypressInitGenerator(tree, {
|
||||||
addPlugin: true,
|
addPlugin: true,
|
||||||
@ -77,10 +61,9 @@ describe('Cypress e2e configuration', () => {
|
|||||||
`);
|
`);
|
||||||
expect(
|
expect(
|
||||||
readProjectConfiguration(tree, 'my-app').targets.e2e
|
readProjectConfiguration(tree, 'my-app').targets.e2e
|
||||||
).toMatchInlineSnapshot(`undefined`);
|
).toBeUndefined();
|
||||||
|
|
||||||
expect(readJson(tree, 'apps/my-app/tsconfig.json'))
|
expect(readJson(tree, 'apps/my-app/tsconfig.json')).toMatchInlineSnapshot(`
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
@ -106,13 +89,14 @@ describe('Cypress e2e configuration', () => {
|
|||||||
assertCypressFiles(tree, 'apps/my-app/src');
|
assertCypressFiles(tree, 'apps/my-app/src');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add e2e target to existing app', async () => {
|
it('should add e2e target to existing app when not using plugin', async () => {
|
||||||
addProject(tree, { name: 'my-app', type: 'apps' });
|
addProject(tree, { name: 'my-app', type: 'apps' });
|
||||||
|
|
||||||
await cypressE2EConfigurationGenerator(tree, {
|
await cypressE2EConfigurationGenerator(tree, {
|
||||||
project: 'my-app',
|
project: 'my-app',
|
||||||
addPlugin: true,
|
addPlugin: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tree.read('apps/my-app/cypress.config.ts', 'utf-8'))
|
expect(tree.read('apps/my-app/cypress.config.ts', 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
@ -122,22 +106,31 @@ describe('Cypress e2e configuration', () => {
|
|||||||
e2e: {
|
e2e: {
|
||||||
...nxE2EPreset(__filename, {
|
...nxE2EPreset(__filename, {
|
||||||
cypressDir: 'src',
|
cypressDir: 'src',
|
||||||
webServerCommands: {
|
|
||||||
default: 'nx run my-app:serve',
|
|
||||||
production: 'nx run my-app:serve:production',
|
|
||||||
},
|
|
||||||
ciWebServerCommand: 'nx run my-app:serve-static',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
expect(
|
expect(readProjectConfiguration(tree, 'my-app').targets.e2e)
|
||||||
readProjectConfiguration(tree, 'my-app').targets.e2e
|
|
||||||
).toMatchInlineSnapshot(`undefined`);
|
|
||||||
|
|
||||||
expect(readJson(tree, 'apps/my-app/tsconfig.json'))
|
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"devServerTarget": "my-app:serve-static",
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"devServerTarget": "my-app:serve:production",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"executor": "@nx/cypress:cypress",
|
||||||
|
"options": {
|
||||||
|
"cypressConfig": "apps/my-app/cypress.config.ts",
|
||||||
|
"devServerTarget": "my-app:serve",
|
||||||
|
"testingType": "e2e",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(readJson(tree, 'apps/my-app/tsconfig.json')).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
@ -377,7 +370,62 @@ describe('Cypress e2e configuration', () => {
|
|||||||
baseUrl: 'http://localhost:4200',
|
baseUrl: 'http://localhost:4200',
|
||||||
addPlugin: true,
|
addPlugin: true,
|
||||||
});
|
});
|
||||||
expect(readJson(tree, 'libs/my-lib/.eslintrc.json')).toMatchSnapshot();
|
expect(readJson(tree, 'libs/my-lib/.eslintrc.json')).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:cypress/recommended",
|
||||||
|
"../../.eslintrc.json",
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"!**/*",
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:@nx/angular",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates",
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"prefix": "cy-port-test",
|
||||||
|
"style": "kebab-case",
|
||||||
|
"type": "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"prefix": "cyPortTest",
|
||||||
|
"style": "camelCase",
|
||||||
|
"type": "attribute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:@nx/angular-template",
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"*.html",
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.cy.{ts,js,tsx,jsx}",
|
||||||
|
"cypress/**/*.{ts,js,tsx,jsx}",
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add serve-static target to CI configuration', async () => {
|
it('should add serve-static target to CI configuration', async () => {
|
||||||
@ -397,8 +445,7 @@ describe('Cypress e2e configuration', () => {
|
|||||||
});
|
});
|
||||||
assertCypressFiles(tree, 'libs/my-lib/cypress');
|
assertCypressFiles(tree, 'libs/my-lib/cypress');
|
||||||
expect(
|
expect(
|
||||||
readProjectConfiguration(tree, 'my-lib').targets['e2e'].configurations
|
readProjectConfiguration(tree, 'my-lib').targets['e2e'].configurations.ci
|
||||||
.ci
|
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"devServerTarget": "my-app:serve-static",
|
"devServerTarget": "my-app:serve-static",
|
||||||
@ -465,9 +512,7 @@ export default defineConfig({
|
|||||||
expect(
|
expect(
|
||||||
tree.exists('libs/my-lib/cypress/fixtures/example.json')
|
tree.exists('libs/my-lib/cypress/fixtures/example.json')
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
expect(
|
expect(tree.exists('libs/my-lib/cypress/support/commands.ts')).toBeFalsy();
|
||||||
tree.exists('libs/my-lib/cypress/support/commands.ts')
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw if e2e is already defined', async () => {
|
it('should not throw if e2e is already defined', async () => {
|
||||||
@ -627,7 +672,6 @@ export default defineConfig({
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function addProject(
|
function addProject(
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
writeJson,
|
writeJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { resolveImportPath } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
import { resolveImportPath } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt';
|
||||||
import { Linter, LinterType } from '@nx/eslint';
|
import { Linter, LinterType } from '@nx/eslint';
|
||||||
import {
|
import {
|
||||||
getRelativePathToRootTsConfig,
|
getRelativePathToRootTsConfig,
|
||||||
@ -35,11 +36,12 @@ import { PackageJson } from 'nx/src/utils/package-json';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { addLinterToCyProject } from '../../utils/add-linter';
|
import { addLinterToCyProject } from '../../utils/add-linter';
|
||||||
import { addDefaultE2EConfig } from '../../utils/config';
|
import { addDefaultE2EConfig } from '../../utils/config';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
import {
|
||||||
import { typesNodeVersion, viteVersion } from '../../utils/versions';
|
getInstalledCypressMajorVersion,
|
||||||
|
versions,
|
||||||
|
} from '../../utils/versions';
|
||||||
import { addBaseCypressSetup } from '../base-setup/base-setup';
|
import { addBaseCypressSetup } from '../base-setup/base-setup';
|
||||||
import cypressInitGenerator, { addPlugin } from '../init/init';
|
import cypressInitGenerator, { addPlugin } from '../init/init';
|
||||||
import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt';
|
|
||||||
|
|
||||||
export interface CypressE2EConfigSchema {
|
export interface CypressE2EConfigSchema {
|
||||||
project: string;
|
project: string;
|
||||||
@ -82,7 +84,7 @@ export async function configurationGeneratorInternal(
|
|||||||
const tasks: GeneratorCallback[] = [];
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|
||||||
const projectGraph = await createProjectGraphAsync();
|
const projectGraph = await createProjectGraphAsync();
|
||||||
if (!installedCypressVersion()) {
|
if (!getInstalledCypressMajorVersion(tree)) {
|
||||||
tasks.push(await jsInitGenerator(tree, { ...options, skipFormat: true }));
|
tasks.push(await jsInitGenerator(tree, { ...options, skipFormat: true }));
|
||||||
tasks.push(
|
tasks.push(
|
||||||
await cypressInitGenerator(tree, {
|
await cypressInitGenerator(tree, {
|
||||||
@ -174,15 +176,23 @@ export async function configurationGeneratorInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ensureDependencies(tree: Tree, options: NormalizedSchema) {
|
function ensureDependencies(tree: Tree, options: NormalizedSchema) {
|
||||||
|
const pkgVersions = versions(tree);
|
||||||
|
|
||||||
const devDependencies: Record<string, string> = {
|
const devDependencies: Record<string, string> = {
|
||||||
'@types/node': typesNodeVersion,
|
'@types/node': pkgVersions.typesNodeVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.bundler === 'vite') {
|
if (options.bundler === 'vite') {
|
||||||
devDependencies['vite'] = viteVersion;
|
devDependencies['vite'] = pkgVersions.viteVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addDependenciesToPackageJson(tree, {}, devDependencies);
|
return addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
devDependencies,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function normalizeOptions(tree: Tree, options: CypressE2EConfigSchema) {
|
async function normalizeOptions(tree: Tree, options: CypressE2EConfigSchema) {
|
||||||
@ -280,7 +290,7 @@ async function addFiles(
|
|||||||
hasPlugin: boolean
|
hasPlugin: boolean
|
||||||
) {
|
) {
|
||||||
const projectConfig = readProjectConfiguration(tree, options.project);
|
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||||
const cyVersion = installedCypressVersion();
|
const cyVersion = getInstalledCypressMajorVersion(tree);
|
||||||
const filesToUse = cyVersion && cyVersion < 10 ? 'v9' : 'v10';
|
const filesToUse = cyVersion && cyVersion < 10 ? 'v9' : 'v10';
|
||||||
|
|
||||||
const hasTsConfig = tree.exists(
|
const hasTsConfig = tree.exists(
|
||||||
@ -400,7 +410,7 @@ function addTarget(
|
|||||||
projectGraph: ProjectGraph
|
projectGraph: ProjectGraph
|
||||||
) {
|
) {
|
||||||
const projectConfig = readProjectConfiguration(tree, opts.project);
|
const projectConfig = readProjectConfiguration(tree, opts.project);
|
||||||
const cyVersion = installedCypressVersion();
|
const cyVersion = getInstalledCypressMajorVersion(tree);
|
||||||
projectConfig.targets ??= {};
|
projectConfig.targets ??= {};
|
||||||
projectConfig.targets.e2e = {
|
projectConfig.targets.e2e = {
|
||||||
executor: '@nx/cypress:cypress',
|
executor: '@nx/cypress:cypress',
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
writeJson,
|
writeJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '../../utils/versions';
|
||||||
import { configurationGenerator } from '../configuration/configuration';
|
import { configurationGenerator } from '../configuration/configuration';
|
||||||
import {
|
import {
|
||||||
createSupportFileImport,
|
createSupportFileImport,
|
||||||
@ -21,13 +21,16 @@ import {
|
|||||||
} from './conversion.util';
|
} from './conversion.util';
|
||||||
import { migrateCypressProject } from './migrate-to-cypress-11';
|
import { migrateCypressProject } from './migrate-to-cypress-11';
|
||||||
|
|
||||||
jest.mock('../../utils/cypress-version');
|
jest.mock('../../utils/versions', () => ({
|
||||||
|
...jest.requireActual('../../utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('convertToCypressTen', () => {
|
describe('convertToCypressTen', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
import {
|
|
||||||
assertMinimumCypressVersion,
|
|
||||||
installedCypressVersion,
|
|
||||||
} from '../../utils/cypress-version';
|
|
||||||
import {
|
import {
|
||||||
formatFiles,
|
formatFiles,
|
||||||
installPackagesTask,
|
installPackagesTask,
|
||||||
@ -16,6 +12,10 @@ import {
|
|||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
||||||
import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl';
|
import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl';
|
||||||
|
import {
|
||||||
|
assertMinimumCypressVersion,
|
||||||
|
getInstalledCypressMajorVersion,
|
||||||
|
} from '../../utils/versions';
|
||||||
import {
|
import {
|
||||||
addConfigToTsConfig,
|
addConfigToTsConfig,
|
||||||
createNewCypressConfig,
|
createNewCypressConfig,
|
||||||
@ -119,7 +119,7 @@ https://nx.dev/cypress/v10-migration-guide
|
|||||||
|
|
||||||
export async function migrateCypressProject(tree: Tree) {
|
export async function migrateCypressProject(tree: Tree) {
|
||||||
assertMinimumCypressVersion(8);
|
assertMinimumCypressVersion(8);
|
||||||
if (installedCypressVersion() >= 10) {
|
if (getInstalledCypressMajorVersion(tree) >= 10) {
|
||||||
logger.info('NX This workspace is already using Cypress v10+');
|
logger.info('NX This workspace is already using Cypress v10+');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { readJson, updateJson, type Tree } from '@nx/devkit';
|
import { readJson, updateJson, type Tree } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import * as cypressVersionUtils from '../../utils/cypress-version';
|
import * as cypressVersions from '../../utils/versions';
|
||||||
import migration from './update-cypress-version-13-6-6';
|
import migration from './update-cypress-version-13-6-6';
|
||||||
|
|
||||||
describe('update-cypress-version migration', () => {
|
describe('update-cypress-version migration', () => {
|
||||||
@ -14,7 +14,7 @@ describe('update-cypress-version migration', () => {
|
|||||||
});
|
});
|
||||||
const major = parseInt(version.split('.')[0].replace('^', ''), 10);
|
const major = parseInt(version.split('.')[0].replace('^', ''), 10);
|
||||||
jest
|
jest
|
||||||
.spyOn(cypressVersionUtils, 'installedCypressVersion')
|
.spyOn(cypressVersions, 'getInstalledCypressMajorVersion')
|
||||||
.mockReturnValue(major);
|
.mockReturnValue(major);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,10 @@ import {
|
|||||||
formatFiles,
|
formatFiles,
|
||||||
type Tree,
|
type Tree,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '../../utils/versions';
|
||||||
|
|
||||||
export default async function (tree: Tree) {
|
export default async function (tree: Tree) {
|
||||||
if (installedCypressVersion() < 13) {
|
if (getInstalledCypressMajorVersion(tree) < 13) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
#### Remove `experimentalFetchPolyfill` Configuration Option
|
||||||
|
|
||||||
|
Removes the `experimentalFetchPolyfill` configuration option that was removed in Cypress v14. Read more at the [migration notes](<https://docs.cypress.io/app/references/changelog#:~:text=The%20experimentalFetchPolyfill%20configuration%20option%20was,cy.intercept()%20for%20handling%20fetch%20requests>).
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalFetchPolyfill: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% /tabs %}
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
import { addProjectConfiguration, type Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import migration from './remove-experimental-fetch-polyfill';
|
||||||
|
|
||||||
|
describe('remove-experimental-fetch-polyfill', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when there are no projects with cypress config', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.exists('apps/app1-e2e/cypress.config.ts')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when the cypress config cannot be parsed as expected', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write('apps/app1-e2e/cypress.config.ts', `export const foo = 'bar';`);
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"export const foo = 'bar';
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle when the cypress config path in the executor is not valid', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
e2e: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'apps/app1-e2e/non-existent-cypress.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalFetchPolyfill property even if defined multiple times', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'vue',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
experimentalFetchPolyfill: true,
|
||||||
|
},
|
||||||
|
e2e: {
|
||||||
|
experimentalFetchPolyfill: true,
|
||||||
|
},
|
||||||
|
experimentalFetchPolyfill: true,
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'vue',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
e2e: {},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cypress config files in projects using the "@nx/cypress:cypress" executor', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
e2e: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'apps/app1-e2e/cypress.custom-config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.custom-config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalFetchPolyfill: true,
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.custom-config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import { formatFiles, type Tree } from '@nx/devkit';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import type { Printer } from 'typescript';
|
||||||
|
import { resolveCypressConfigObject } from '../../utils/config';
|
||||||
|
import { cypressProjectConfigs } from '../../utils/migrations';
|
||||||
|
|
||||||
|
let printer: Printer;
|
||||||
|
let ts: typeof import('typescript');
|
||||||
|
|
||||||
|
// https://docs.cypress.io/app/references/changelog#:~:text=The%20experimentalFetchPolyfill%20configuration%20option%20was,cy.intercept()%20for%20handling%20fetch%20requests
|
||||||
|
export default async function (tree: Tree) {
|
||||||
|
for await (const { cypressConfigPath } of cypressProjectConfigs(tree)) {
|
||||||
|
if (!tree.exists(cypressConfigPath)) {
|
||||||
|
// cypress config file doesn't exist, so skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ts ??= ensureTypescript();
|
||||||
|
printer ??= ts.createPrinter();
|
||||||
|
|
||||||
|
const cypressConfig = tree.read(cypressConfigPath, 'utf-8');
|
||||||
|
const updatedConfig = removeExperimentalFetchPolyfill(cypressConfig);
|
||||||
|
|
||||||
|
tree.write(cypressConfigPath, updatedConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeExperimentalFetchPolyfill(cypressConfig: string): string {
|
||||||
|
const config = resolveCypressConfigObject(cypressConfig);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
// couldn't find the config object, leave as is
|
||||||
|
return cypressConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFile = tsquery.ast(cypressConfig);
|
||||||
|
|
||||||
|
const updatedConfig = ts.factory.updateObjectLiteralExpression(
|
||||||
|
config,
|
||||||
|
config.properties
|
||||||
|
// remove the experimentalFetchPolyfill property from the top level config object
|
||||||
|
.filter(
|
||||||
|
(p) =>
|
||||||
|
!ts.isPropertyAssignment(p) ||
|
||||||
|
p.name.getText() !== 'experimentalFetchPolyfill'
|
||||||
|
)
|
||||||
|
.map((p) => {
|
||||||
|
if (
|
||||||
|
ts.isPropertyAssignment(p) &&
|
||||||
|
['component', 'e2e'].includes(p.name.getText()) &&
|
||||||
|
ts.isObjectLiteralExpression(p.initializer)
|
||||||
|
) {
|
||||||
|
// remove the experimentalFetchPolyfill property from the component or e2e config object
|
||||||
|
return ts.factory.updatePropertyAssignment(
|
||||||
|
p,
|
||||||
|
p.name,
|
||||||
|
ts.factory.updateObjectLiteralExpression(
|
||||||
|
p.initializer,
|
||||||
|
p.initializer.properties.filter(
|
||||||
|
(ip) =>
|
||||||
|
!ts.isPropertyAssignment(ip) ||
|
||||||
|
ip.name.getText() !== 'experimentalFetchPolyfill'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return cypressConfig.replace(
|
||||||
|
config.getText(),
|
||||||
|
printer.printNode(ts.EmitHint.Unspecified, updatedConfig, sourceFile)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
#### Replace the `experimentalJustInTimeCompile` Configuration Option with `justInTimeCompile`
|
||||||
|
|
||||||
|
Replaces the `experimentalJustInTimeCompile` configuration option with the new `justInTimeCompile` configuration option. Read more at the [migration notes](https://docs.cypress.io/app/references/migration-guide#CT-Just-in-Time-Compile-changes).
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
If the `experimentalJustInTimeCompile` configuration option is present and set to `true`, the migration will remove it. This is to account for the fact that JIT compilation is the default behavior in Cypress v14.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress.config.ts" %}
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'angular',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress.config.ts" %}
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'angular',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If the `experimentalJustInTimeCompile` configuration option is set to `false` and it is using webpack, the migration will rename it to `justInTimeCompile`.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress.config.ts" %}
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'angular',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress.config.ts" highlightLines=[9] %}
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'angular',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
justInTimeCompile: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If the `experimentalJustInTimeCompile` configuration is set to any value and it is using Vite, the migration will remove it. This is to account for the fact that JIT compilation no longer applies to Vite.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress.config.ts" %}
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress.config.ts" %}
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
@ -0,0 +1,319 @@
|
|||||||
|
import { addProjectConfiguration, type Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import migration from './replace-experimental-just-in-time-compile';
|
||||||
|
|
||||||
|
describe('replace-experimental-just-in-time-compile', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when there are no projects with cypress config', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.exists('apps/app1/cypress.config.ts')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when the cypress config cannot be parsed as expected', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write('apps/app1/cypress.config.ts', `export const foo = 'bar';`);
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.read('apps/app1/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"export const foo = 'bar';
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle when the cypress config path in the executor is not valid', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
ct: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'apps/app1/non-existent-cypress.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cypress config files in projects using the "@nx/cypress:cypress" executor', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
ct: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'apps/app1/cypress.custom-config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.custom-config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'vue',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
const config = tree.read('apps/app1/cypress.custom-config.ts', 'utf-8');
|
||||||
|
expect(config).not.toContain('experimentalJustInTimeCompile');
|
||||||
|
expect(config).not.toContain('justInTimeCompile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalJustInTimeCompile property from the top-level config when using vite', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'vue',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
const config = tree.read('apps/app1/cypress.config.ts', 'utf-8');
|
||||||
|
expect(config).not.toContain('experimentalJustInTimeCompile');
|
||||||
|
expect(config).not.toContain('justInTimeCompile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalJustInTimeCompile property from the component config when using vite', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'vue',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
const config = tree.read('apps/app1/cypress.config.ts', 'utf-8');
|
||||||
|
expect(config).not.toContain('experimentalJustInTimeCompile');
|
||||||
|
expect(config).not.toContain('justInTimeCompile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalJustInTimeCompile property from both the top-level config and the component config when using vite', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'vue',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
const config = tree.read('apps/app1/cypress.config.ts', 'utf-8');
|
||||||
|
expect(config).not.toContain('experimentalJustInTimeCompile');
|
||||||
|
expect(config).not.toContain('justInTimeCompile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalJustInTimeCompile property from the top-level config when set to true and it is using webpack', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: true,
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
const config = tree.read('apps/app1/cypress.config.ts', 'utf-8');
|
||||||
|
expect(config).not.toContain('experimentalJustInTimeCompile');
|
||||||
|
expect(config).not.toContain('justInTimeCompile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rename the experimentalJustInTimeCompile property to justInTimeCompile in the top-level config when set to false and it is using webpack', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
justInTimeCompile: false,
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalJustInTimeCompile property from the component config when set to true and it is using webpack', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
const config = tree.read('apps/app1/cypress.config.ts', 'utf-8');
|
||||||
|
expect(config).not.toContain('experimentalJustInTimeCompile');
|
||||||
|
expect(config).not.toContain('justInTimeCompile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rename the experimentalJustInTimeCompile property to justInTimeCompile in the component config when set to false and it is using webpack', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
experimentalJustInTimeCompile: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
justInTimeCompile: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
import { formatFiles, workspaceRoot, type Tree } from '@nx/devkit';
|
||||||
|
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import type {
|
||||||
|
ObjectLiteralExpression,
|
||||||
|
Printer,
|
||||||
|
PropertyAssignment,
|
||||||
|
} from 'typescript';
|
||||||
|
import { resolveCypressConfigObject } from '../../utils/config';
|
||||||
|
import {
|
||||||
|
cypressProjectConfigs,
|
||||||
|
getObjectProperty,
|
||||||
|
removeObjectProperty,
|
||||||
|
updateObjectProperty,
|
||||||
|
} from '../../utils/migrations';
|
||||||
|
|
||||||
|
let printer: Printer;
|
||||||
|
let ts: typeof import('typescript');
|
||||||
|
|
||||||
|
// https://docs.cypress.io/app/references/migration-guide#CT-Just-in-Time-Compile-changes
|
||||||
|
export default async function (tree: Tree) {
|
||||||
|
for await (const { cypressConfigPath } of cypressProjectConfigs(tree)) {
|
||||||
|
if (!tree.exists(cypressConfigPath)) {
|
||||||
|
// cypress config file doesn't exist, so skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedConfig = await updateCtJustInTimeCompile(
|
||||||
|
tree,
|
||||||
|
cypressConfigPath
|
||||||
|
);
|
||||||
|
|
||||||
|
tree.write(cypressConfigPath, updatedConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateCtJustInTimeCompile(
|
||||||
|
tree: Tree,
|
||||||
|
cypressConfigPath: string
|
||||||
|
): Promise<string> {
|
||||||
|
const cypressConfig = tree.read(cypressConfigPath, 'utf-8');
|
||||||
|
const config = resolveCypressConfigObject(cypressConfig);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
// couldn't find the config object, leave as is
|
||||||
|
return cypressConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getObjectProperty(config, 'component')) {
|
||||||
|
// no component config, leave as is
|
||||||
|
return cypressConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
ts ??= ensureTypescript();
|
||||||
|
printer ??= ts.createPrinter();
|
||||||
|
|
||||||
|
const sourceFile = tsquery.ast(cypressConfig);
|
||||||
|
let updatedConfig = config;
|
||||||
|
|
||||||
|
const bundler = await resolveBundler(updatedConfig, cypressConfigPath);
|
||||||
|
const isViteBundler = bundler === 'vite';
|
||||||
|
|
||||||
|
const existingJustInTimeCompileProperty = getObjectProperty(
|
||||||
|
updatedConfig,
|
||||||
|
'experimentalJustInTimeCompile'
|
||||||
|
);
|
||||||
|
if (existingJustInTimeCompileProperty) {
|
||||||
|
if (
|
||||||
|
isViteBundler ||
|
||||||
|
existingJustInTimeCompileProperty.initializer.kind ===
|
||||||
|
ts.SyntaxKind.TrueKeyword
|
||||||
|
) {
|
||||||
|
// if it's using vite or it's set to true (the new default value), remove it
|
||||||
|
updatedConfig = removeObjectProperty(
|
||||||
|
updatedConfig,
|
||||||
|
existingJustInTimeCompileProperty
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// rename to justInTimeCompile
|
||||||
|
updatedConfig = updateObjectProperty(
|
||||||
|
updatedConfig,
|
||||||
|
existingJustInTimeCompileProperty,
|
||||||
|
{ newName: 'justInTimeCompile' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentProperty = getObjectProperty(updatedConfig, 'component');
|
||||||
|
if (
|
||||||
|
componentProperty &&
|
||||||
|
ts.isObjectLiteralExpression(componentProperty.initializer)
|
||||||
|
) {
|
||||||
|
const componentConfigObject = componentProperty.initializer;
|
||||||
|
const existingJustInTimeCompileProperty = getObjectProperty(
|
||||||
|
componentConfigObject,
|
||||||
|
'experimentalJustInTimeCompile'
|
||||||
|
);
|
||||||
|
if (existingJustInTimeCompileProperty) {
|
||||||
|
if (
|
||||||
|
isViteBundler ||
|
||||||
|
existingJustInTimeCompileProperty.initializer.kind ===
|
||||||
|
ts.SyntaxKind.TrueKeyword
|
||||||
|
) {
|
||||||
|
// if it's using vite or it's set to true (the new default value), remove it
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, componentProperty, {
|
||||||
|
newValue: removeObjectProperty(
|
||||||
|
componentConfigObject,
|
||||||
|
existingJustInTimeCompileProperty
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// rename to justInTimeCompile
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, componentProperty, {
|
||||||
|
newValue: updateObjectProperty(
|
||||||
|
componentConfigObject,
|
||||||
|
existingJustInTimeCompileProperty,
|
||||||
|
{ newName: 'justInTimeCompile' }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cypressConfig.replace(
|
||||||
|
config.getText(),
|
||||||
|
printer.printNode(ts.EmitHint.Unspecified, updatedConfig, sourceFile)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveBundler(
|
||||||
|
config: ObjectLiteralExpression,
|
||||||
|
cypressConfigPath: string
|
||||||
|
): Promise<string | null> {
|
||||||
|
const bundlerProperty = tsquery.query<PropertyAssignment>(
|
||||||
|
config,
|
||||||
|
'PropertyAssignment:has(Identifier[name=component]) PropertyAssignment:has(Identifier[name=devServer]) PropertyAssignment:has(Identifier[name=bundler])'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (bundlerProperty) {
|
||||||
|
return ts.isStringLiteral(bundlerProperty.initializer)
|
||||||
|
? bundlerProperty.initializer.getText().replace(/['"`]/g, '')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// we can't statically resolve the bundler from the config, so we load the config
|
||||||
|
const cypressConfig = await loadConfigFile(
|
||||||
|
join(workspaceRoot, cypressConfigPath)
|
||||||
|
);
|
||||||
|
|
||||||
|
return cypressConfig.component?.devServer?.bundler;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
#### Set `injectDocumentDomain` Configuration Option
|
||||||
|
|
||||||
|
Replaces the removed `experimentalSkipDomainInjection` configuration option with the new `injectDocumentDomain` configuration option when needed. Skipping domain injection is the default behavior in Cypress v14 and therefore, it is required to use the `cy.origin()` command when navigating between domains. The `injectDocumentDomain` option was introduced to ease the transition to v14, but it is deprecated and will be removed in Cypress v15. Read more at the [migration notes](https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin).
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
If the `experimentalSkipDomainInjection` configuration option is present, the migration will remove it. This is to account for the fact that skipping domain injection is the default behavior in Cypress v14.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalSkipDomainInjection: ['https://example.com'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If the `experimentalSkipDomainInjection` configuration option is present and set to an empty array (no domain injection is skipped), the migration will remove it and will set the `injectDocumentDomain` option to `true`.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalSkipDomainInjection: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" highlightLines=["17-19"] %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
// Please ensure you use `cy.origin()` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If the `experimentalSkipDomainInjection` configuration option is not present (no domain injection is skipped), the migration will set the `injectDocumentDomain` option to `true`.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1-e2e/cypress.config.ts" highlightLines=["17-19"] %}
|
||||||
|
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
// Please ensure you use `cy.origin()` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% /tabs %}
|
||||||
@ -0,0 +1,712 @@
|
|||||||
|
import { addProjectConfiguration, type Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import migration from './set-inject-document-domain';
|
||||||
|
|
||||||
|
describe('set-inject-document-domain', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when there are no projects with cypress config', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.exists('apps/app1-e2e/cypress.config.ts')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when the cypress config cannot be parsed as expected', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write('apps/app1-e2e/cypress.config.ts', `export const foo = 'bar';`);
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"export const foo = 'bar';
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle when the cypress config path in the executor is not valid', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
e2e: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'apps/app1-e2e/non-existent-cypress.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the injectDocumentDomain property to true in the top-level config when there is no e2e or component config', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the experimentalSkipDomainInjection property in the top-level config with injectDocumentDomain when it is set to an empty array', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalSkipDomainInjection: [],
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the experimentalSkipDomainInjection property in the top-level config with injectDocumentDomain when it is set to an empty array and there is an e2e or component config without experimentalSkipDomainInjection', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
experimentalSkipDomainInjection: [],
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalSkipDomainInjection property from the top-level config when it is set to a non-empty array', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalSkipDomainInjection: ['https://example.com'],
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the injectDocumentDomain property to true in the e2e config when defined and experimentalSkipDomainInjection is not set', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the injectDocumentDomain property to true in the e2e config when it is not an object literal', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the experimentalSkipDomainInjection property in the e2e config with injectDocumentDomain when it is set to an empty array', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalSkipDomainInjection: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalSkipDomainInjection property from the e2e config when it is set to a non-empty array', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
experimentalSkipDomainInjection: ['https://example.com'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the injectDocumentDomain property to true in the component config when defined and experimentalSkipDomainInjection is not set', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
...nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
...nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the injectDocumentDomain property to true in the component config when it is not an object literal', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
...nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the experimentalSkipDomainInjection property in the component config with injectDocumentDomain when it is set to an empty array', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
...nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
experimentalSkipDomainInjection: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
...nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the experimentalSkipDomainInjection property in the component config when it is set to a non-empty array', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.config.ts',
|
||||||
|
`import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
...nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
experimentalSkipDomainInjection: ['https://example.com'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
...nxComponentTestingPreset(__filename, { bundler: 'vite' }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cypress config files in projects using the "@nx/cypress:cypress" executor', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
e2e: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'apps/app1-e2e/cypress.custom-config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1-e2e/cypress.custom-config.ts',
|
||||||
|
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.custom-config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
bundler: 'vite',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'pnpm exec nx run app1:dev',
|
||||||
|
production: 'pnpm exec nx run app1:dev',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'pnpm exec nx run app1:dev',
|
||||||
|
ciBaseUrl: 'http://localhost:4200',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
// Please ensure you use \`cy.origin()\` when navigating between domains and remove this option.
|
||||||
|
// See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
injectDocumentDomain: true,
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,251 @@
|
|||||||
|
import { formatFiles, type Tree } from '@nx/devkit';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import type {
|
||||||
|
Expression,
|
||||||
|
ObjectLiteralExpression,
|
||||||
|
Printer,
|
||||||
|
PropertyAssignment,
|
||||||
|
} from 'typescript';
|
||||||
|
import { resolveCypressConfigObject } from '../../utils/config';
|
||||||
|
import {
|
||||||
|
cypressProjectConfigs,
|
||||||
|
getObjectProperty,
|
||||||
|
removeObjectProperty,
|
||||||
|
updateObjectProperty,
|
||||||
|
} from '../../utils/migrations';
|
||||||
|
|
||||||
|
let printer: Printer;
|
||||||
|
let ts: typeof import('typescript');
|
||||||
|
|
||||||
|
// https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin
|
||||||
|
// https://docs.cypress.io/app/references/changelog#:~:text=The%20experimentalSkipDomainInjection%20configuration%20has%20been,injectDocumentDomain%20configuration
|
||||||
|
export default async function (tree: Tree) {
|
||||||
|
for await (const { cypressConfigPath } of cypressProjectConfigs(tree)) {
|
||||||
|
if (!tree.exists(cypressConfigPath)) {
|
||||||
|
// cypress config file doesn't exist, so skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ts ??= ensureTypescript();
|
||||||
|
printer ??= ts.createPrinter();
|
||||||
|
|
||||||
|
const cypressConfig = tree.read(cypressConfigPath, 'utf-8');
|
||||||
|
const updatedConfig = setInjectDocumentDomain(cypressConfig);
|
||||||
|
|
||||||
|
tree.write(cypressConfigPath, updatedConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInjectDocumentDomain(cypressConfig: string): string {
|
||||||
|
const config = resolveCypressConfigObject(cypressConfig);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
// couldn't find the config object, leave as is
|
||||||
|
return cypressConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFile = tsquery.ast(cypressConfig);
|
||||||
|
let e2eProperty = getObjectProperty(config, 'e2e');
|
||||||
|
let componentProperty = getObjectProperty(config, 'component');
|
||||||
|
let updatedConfig = config;
|
||||||
|
|
||||||
|
const topLevelExperimentalSkipDomainInjectionProperty = getObjectProperty(
|
||||||
|
updatedConfig,
|
||||||
|
'experimentalSkipDomainInjection'
|
||||||
|
);
|
||||||
|
const topLevelSkipDomainState: 'not-set' | 'skipping' | 'not-skipping' =
|
||||||
|
!topLevelExperimentalSkipDomainInjectionProperty
|
||||||
|
? 'not-set'
|
||||||
|
: !ts.isArrayLiteralExpression(
|
||||||
|
topLevelExperimentalSkipDomainInjectionProperty.initializer
|
||||||
|
) ||
|
||||||
|
topLevelExperimentalSkipDomainInjectionProperty.initializer.elements
|
||||||
|
.length > 0
|
||||||
|
? 'skipping'
|
||||||
|
: 'not-skipping';
|
||||||
|
|
||||||
|
let e2eSkipDomainState: 'not-set' | 'skipping' | 'not-skipping' = 'not-set';
|
||||||
|
if (e2eProperty) {
|
||||||
|
let experimentalSkipDomainInjectionProperty: PropertyAssignment | undefined;
|
||||||
|
let isObjectLiteral = false;
|
||||||
|
if (ts.isObjectLiteralExpression(e2eProperty.initializer)) {
|
||||||
|
experimentalSkipDomainInjectionProperty = getObjectProperty(
|
||||||
|
e2eProperty.initializer,
|
||||||
|
'experimentalSkipDomainInjection'
|
||||||
|
);
|
||||||
|
isObjectLiteral = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (experimentalSkipDomainInjectionProperty) {
|
||||||
|
e2eSkipDomainState =
|
||||||
|
!ts.isArrayLiteralExpression(
|
||||||
|
experimentalSkipDomainInjectionProperty.initializer
|
||||||
|
) ||
|
||||||
|
experimentalSkipDomainInjectionProperty.initializer.elements.length > 0
|
||||||
|
? 'skipping'
|
||||||
|
: 'not-skipping';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
e2eSkipDomainState === 'not-set' &&
|
||||||
|
topLevelSkipDomainState === 'not-set'
|
||||||
|
) {
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, e2eProperty, {
|
||||||
|
newValue: setInjectDocumentDomainInObject(e2eProperty.initializer),
|
||||||
|
});
|
||||||
|
} else if (e2eSkipDomainState === 'not-skipping') {
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, e2eProperty, {
|
||||||
|
newValue: replaceExperimentalSkipDomainInjectionInObject(
|
||||||
|
e2eProperty.initializer
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else if (e2eSkipDomainState === 'skipping') {
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, e2eProperty, {
|
||||||
|
newValue: removeObjectProperty(
|
||||||
|
// we only determine that it's skipping if it's an object literal
|
||||||
|
e2eProperty.initializer as ObjectLiteralExpression,
|
||||||
|
getObjectProperty(
|
||||||
|
e2eProperty.initializer as ObjectLiteralExpression,
|
||||||
|
'experimentalSkipDomainInjection'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let componentSkipDomainState: 'not-set' | 'skipping' | 'not-skipping' =
|
||||||
|
'not-set';
|
||||||
|
if (componentProperty) {
|
||||||
|
let experimentalSkipDomainInjectionProperty: PropertyAssignment | undefined;
|
||||||
|
let isObjectLiteral = false;
|
||||||
|
if (ts.isObjectLiteralExpression(componentProperty.initializer)) {
|
||||||
|
experimentalSkipDomainInjectionProperty = getObjectProperty(
|
||||||
|
componentProperty.initializer,
|
||||||
|
'experimentalSkipDomainInjection'
|
||||||
|
);
|
||||||
|
isObjectLiteral = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (experimentalSkipDomainInjectionProperty) {
|
||||||
|
componentSkipDomainState =
|
||||||
|
!ts.isArrayLiteralExpression(
|
||||||
|
experimentalSkipDomainInjectionProperty.initializer
|
||||||
|
) ||
|
||||||
|
experimentalSkipDomainInjectionProperty.initializer.elements.length > 0
|
||||||
|
? 'skipping'
|
||||||
|
: 'not-skipping';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
componentSkipDomainState === 'not-set' &&
|
||||||
|
topLevelSkipDomainState === 'not-set'
|
||||||
|
) {
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, componentProperty, {
|
||||||
|
newValue: setInjectDocumentDomainInObject(
|
||||||
|
componentProperty.initializer
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else if (componentSkipDomainState === 'not-skipping') {
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, componentProperty, {
|
||||||
|
newValue: replaceExperimentalSkipDomainInjectionInObject(
|
||||||
|
componentProperty.initializer
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else if (componentSkipDomainState === 'skipping') {
|
||||||
|
updatedConfig = updateObjectProperty(updatedConfig, componentProperty, {
|
||||||
|
newValue: removeObjectProperty(
|
||||||
|
// we only determine that it's skipping if it's an object literal
|
||||||
|
componentProperty.initializer as ObjectLiteralExpression,
|
||||||
|
getObjectProperty(
|
||||||
|
componentProperty.initializer as ObjectLiteralExpression,
|
||||||
|
'experimentalSkipDomainInjection'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
topLevelSkipDomainState === 'not-set' &&
|
||||||
|
!e2eProperty &&
|
||||||
|
!componentProperty
|
||||||
|
) {
|
||||||
|
updatedConfig = setInjectDocumentDomainInObject(updatedConfig);
|
||||||
|
} else if (topLevelSkipDomainState === 'not-skipping') {
|
||||||
|
updatedConfig =
|
||||||
|
replaceExperimentalSkipDomainInjectionInObject(updatedConfig);
|
||||||
|
} else if (topLevelSkipDomainState === 'skipping') {
|
||||||
|
updatedConfig = removeObjectProperty(
|
||||||
|
updatedConfig,
|
||||||
|
topLevelExperimentalSkipDomainInjectionProperty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cypressConfig.replace(
|
||||||
|
config.getText(),
|
||||||
|
printer.printNode(ts.EmitHint.Unspecified, updatedConfig, sourceFile)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInjectDocumentDomainInObject(
|
||||||
|
config: Expression
|
||||||
|
): ObjectLiteralExpression {
|
||||||
|
let configToUpdate: ObjectLiteralExpression;
|
||||||
|
if (ts.isObjectLiteralExpression(config)) {
|
||||||
|
configToUpdate = config;
|
||||||
|
} else {
|
||||||
|
// spread the current expression into a new object literal
|
||||||
|
configToUpdate = ts.factory.createObjectLiteralExpression([
|
||||||
|
ts.factory.createSpreadAssignment(config),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts.factory.updateObjectLiteralExpression(
|
||||||
|
configToUpdate,
|
||||||
|
ts.factory.createNodeArray([
|
||||||
|
...configToUpdate.properties,
|
||||||
|
getInjectDocumentDomainPropertyAssignment(),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceExperimentalSkipDomainInjectionInObject(
|
||||||
|
config: Expression
|
||||||
|
): ObjectLiteralExpression {
|
||||||
|
let configToUpdate: ObjectLiteralExpression;
|
||||||
|
if (ts.isObjectLiteralExpression(config)) {
|
||||||
|
configToUpdate = config;
|
||||||
|
} else {
|
||||||
|
// spread the current expression into a new object literal
|
||||||
|
configToUpdate = ts.factory.createObjectLiteralExpression([
|
||||||
|
ts.factory.createSpreadAssignment(config),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts.factory.updateObjectLiteralExpression(
|
||||||
|
configToUpdate,
|
||||||
|
configToUpdate.properties.map((property) =>
|
||||||
|
property.name?.getText() === 'experimentalSkipDomainInjection'
|
||||||
|
? getInjectDocumentDomainPropertyAssignment()
|
||||||
|
: property
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInjectDocumentDomainPropertyAssignment(): PropertyAssignment {
|
||||||
|
return ts.addSyntheticLeadingComment(
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
ts.factory.createPropertyAssignment(
|
||||||
|
ts.factory.createIdentifier('injectDocumentDomain'),
|
||||||
|
ts.factory.createTrue()
|
||||||
|
),
|
||||||
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
||||||
|
' Please ensure you use `cy.origin()` when navigating between domains and remove this option.'
|
||||||
|
),
|
||||||
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
||||||
|
' See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin'
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
#### Update Component Testing `mount` Imports
|
||||||
|
|
||||||
|
Updates the relevant module specifiers when importing the `mount` function and using the Angular or React frameworks. Read more at the [Angular migration notes](https://docs.cypress.io/app/references/migration-guide#Angular-1720-CT-no-longer-supported) and the [React migration notes](https://docs.cypress.io/app/references/migration-guide#React-18-CT-no-longer-supported).
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
If using the Angular framework with a version greater than or equal to v17.2.0 and importing the `mount` function from the `cypress/angular-signals` module, the migration will update the import to use the `cypress/angular` module.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress/support/component.ts" %}
|
||||||
|
import { mount } from 'cypress/angular-signals';
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress/support/component.ts" highlightLines=[1] %}
|
||||||
|
import { mount } from 'cypress/angular';
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If using the Angular framework with a version lower than v17.2.0 and importing the `mount` function from the `cypress/angular` module, the migration will install the `@cypress/angular@2` package and update the import to use the `@cypress/angular` module.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```json {% fileName="package.json" %}
|
||||||
|
{
|
||||||
|
"name": "@my-repo/source",
|
||||||
|
"dependencies": {
|
||||||
|
...
|
||||||
|
"cypress": "^14.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress/support/component.ts" %}
|
||||||
|
import { mount } from 'cypress/angular';
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```json {% fileName="package.json" highlightLines=[6] %}
|
||||||
|
{
|
||||||
|
"name": "@my-repo/source",
|
||||||
|
"dependencies": {
|
||||||
|
...
|
||||||
|
"cypress": "^14.2.1",
|
||||||
|
"@cypress/angular": "^2.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress/support/component.ts" highlightLines=[1] %}
|
||||||
|
import { mount } from '@cypress/angular';
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If using the React framework and importing the `mount` function from the `cypress/react18` module, the migration will update the import to use the `cypress/react` module.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress/support/component.ts" %}
|
||||||
|
import { mount } from 'cypress/react18';
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/cypress/support/component.ts" highlightLines=[1] %}
|
||||||
|
import { mount } from 'cypress/react';
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
@ -0,0 +1,443 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
readJson,
|
||||||
|
type ProjectConfiguration,
|
||||||
|
type ProjectGraph,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import migration from './update-component-testing-mount-imports';
|
||||||
|
|
||||||
|
let projectGraph: ProjectGraph;
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual('@nx/devkit'),
|
||||||
|
createProjectGraphAsync: jest.fn(() => Promise.resolve(projectGraph)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('update-component-testing-mount-imports', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when there are no projects with cypress config', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.exists('apps/app1-e2e/cypress.config.ts')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when the cypress config cannot be parsed as expected', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write('apps/app1-e2e/cypress.config.ts', `export const foo = 'bar';`);
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
expect(tree.read('apps/app1-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"export const foo = 'bar';
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle when the cypress config path in the executor is not valid', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1-e2e', {
|
||||||
|
root: 'apps/app1-e2e',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
e2e: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'apps/app1-e2e/non-existent-cypress.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(migration(tree)).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert import from cypress/react18 to cypress/react when the framework information is directly set in the config', async () => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from 'cypress';
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/src/app/App.cy.tsx',
|
||||||
|
`import { mount } from 'cypress/react18';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(<App />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/src/app/App.cy.tsx', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/react';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(<App />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(['react', 'next', 'remix'])(
|
||||||
|
'should convert import from cypress/react18 to cypress/react when the framework information is set in the Nx preset import for %s',
|
||||||
|
async (framework: string) => {
|
||||||
|
addProjectConfiguration(tree, 'app1', {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { nxComponentTestingPreset } from '@nx/${framework}/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
export default defineConfig({
|
||||||
|
component: nxComponentTestingPreset(__filename),
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/src/app/App.cy.tsx',
|
||||||
|
`import { mount } from 'cypress/react18';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(<App />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/src/app/App.cy.tsx', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/react';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(<App />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each(['react', 'next'])(
|
||||||
|
'should convert import from cypress/react18 to cypress/react when the framework cannot be determined from statically analysing the config but the project depends on %s',
|
||||||
|
async (pkg: string) => {
|
||||||
|
const project: ProjectConfiguration = {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
};
|
||||||
|
projectGraph = {
|
||||||
|
nodes: { app1: { name: 'app1', type: 'app', data: project } },
|
||||||
|
dependencies: {
|
||||||
|
app1: [{ target: `npm:${pkg}`, type: 'static', source: 'app1' }],
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
[`npm:${pkg}`]: {
|
||||||
|
name: `npm:${pkg}`,
|
||||||
|
type: 'npm',
|
||||||
|
data: { packageName: pkg, version: '19.0.0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addProjectConfiguration(tree, 'app1', project);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { somePreset } from '@org/some-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
export default defineConfig({
|
||||||
|
component: somePreset(__filename),
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/src/app/App.cy.tsx',
|
||||||
|
`import { mount } from 'cypress/react18';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(<App />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/src/app/App.cy.tsx', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/react';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(<App />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should convert import from cypress/angular-signals to cypress/angular when the framework information is directly set in the config', async () => {
|
||||||
|
const project: ProjectConfiguration = {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
};
|
||||||
|
projectGraph = {
|
||||||
|
nodes: { app1: { name: 'app1', type: 'app', data: project } },
|
||||||
|
dependencies: {
|
||||||
|
app1: [{ target: 'npm:@angular/core', type: 'static', source: 'app1' }],
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:@angular/core': {
|
||||||
|
name: 'npm:@angular/core',
|
||||||
|
type: 'npm',
|
||||||
|
data: {
|
||||||
|
packageName: '@angular/core',
|
||||||
|
version: '19.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addProjectConfiguration(tree, 'app1', project);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from 'cypress';
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'angular',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/src/app/app.component.cy.ts',
|
||||||
|
`import { mount } from 'cypress/angular-signals';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/src/app/app.component.cy.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/angular';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert import from cypress/angular-signals to cypress/angular when the framework is set in the Nx preset import', async () => {
|
||||||
|
const project: ProjectConfiguration = {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
};
|
||||||
|
projectGraph = {
|
||||||
|
nodes: { app1: { name: 'app1', type: 'app', data: project } },
|
||||||
|
dependencies: {
|
||||||
|
app1: [{ target: 'npm:@angular/core', type: 'static', source: 'app1' }],
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:@angular/core': {
|
||||||
|
name: 'npm:@angular/core',
|
||||||
|
type: 'npm',
|
||||||
|
data: {
|
||||||
|
packageName: '@angular/core',
|
||||||
|
version: '19.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addProjectConfiguration(tree, 'app1', project);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { nxComponentTestingPreset } from '@nx/angular/plugins/component-testing';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
export default defineConfig({
|
||||||
|
component: nxComponentTestingPreset(__filename),
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/src/app/app.component.cy.ts',
|
||||||
|
`import { mount } from 'cypress/angular-signals';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/src/app/app.component.cy.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/angular';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert import from cypress/angular-signals to cypress/angular when the framework cannot be determined from statically analysing the config but the project depends on angular', async () => {
|
||||||
|
const project: ProjectConfiguration = {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
};
|
||||||
|
projectGraph = {
|
||||||
|
nodes: { app1: { name: 'app1', type: 'app', data: project } },
|
||||||
|
dependencies: {
|
||||||
|
app1: [{ target: 'npm:@angular/core', type: 'static', source: 'app1' }],
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:@angular/core': {
|
||||||
|
name: 'npm:@angular/core',
|
||||||
|
type: 'npm',
|
||||||
|
data: {
|
||||||
|
packageName: '@angular/core',
|
||||||
|
version: '19.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addProjectConfiguration(tree, 'app1', project);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { somePreset } from '@org/some-preset';
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
export default defineConfig({
|
||||||
|
component: somePreset(__filename),
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/src/app/app.component.cy.ts',
|
||||||
|
`import { mount } from 'cypress/angular-signals';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/src/app/app.component.cy.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/angular';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert import from cypress/angular to @cypress/angular and install the package if it is a version lower than v17.2.0', async () => {
|
||||||
|
const project: ProjectConfiguration = {
|
||||||
|
root: 'apps/app1',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {},
|
||||||
|
};
|
||||||
|
projectGraph = {
|
||||||
|
nodes: { app1: { name: 'app1', type: 'app', data: project } },
|
||||||
|
dependencies: {
|
||||||
|
app1: [{ target: 'npm:@angular/core', type: 'static', source: 'app1' }],
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:@angular/core': {
|
||||||
|
name: 'npm:@angular/core',
|
||||||
|
type: 'npm',
|
||||||
|
data: {
|
||||||
|
packageName: '@angular/core',
|
||||||
|
version: '17.1.3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addProjectConfiguration(tree, 'app1', project);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/cypress.config.ts',
|
||||||
|
`import { defineConfig } from 'cypress';
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'angular',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/app1/src/app/app.component.cy.ts',
|
||||||
|
`import { mount } from 'cypress/angular';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read('apps/app1/src/app/app.component.cy.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from '@cypress/angular';
|
||||||
|
describe('App', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
mount(AppComponent, {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(
|
||||||
|
readJson(tree, 'package.json').devDependencies['@cypress/angular']
|
||||||
|
).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,299 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
createProjectGraphAsync,
|
||||||
|
formatFiles,
|
||||||
|
visitNotIgnoredFiles,
|
||||||
|
type ProjectConfiguration,
|
||||||
|
type ProjectGraph,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import { lt, valid } from 'semver';
|
||||||
|
import type {
|
||||||
|
ImportDeclaration,
|
||||||
|
ObjectLiteralExpression,
|
||||||
|
Printer,
|
||||||
|
PropertyAssignment,
|
||||||
|
} from 'typescript';
|
||||||
|
import { resolveCypressConfigObject } from '../../utils/config';
|
||||||
|
import {
|
||||||
|
cypressProjectConfigs,
|
||||||
|
getObjectProperty,
|
||||||
|
} from '../../utils/migrations';
|
||||||
|
|
||||||
|
let printer: Printer;
|
||||||
|
let ts: typeof import('typescript');
|
||||||
|
|
||||||
|
export default async function (tree: Tree) {
|
||||||
|
const projectGraph = await createProjectGraphAsync();
|
||||||
|
|
||||||
|
for await (const {
|
||||||
|
cypressConfigPath,
|
||||||
|
projectName,
|
||||||
|
projectConfig,
|
||||||
|
} of cypressProjectConfigs(tree)) {
|
||||||
|
if (!tree.exists(cypressConfigPath)) {
|
||||||
|
// cypress config file doesn't exist, so skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ts ??= ensureTypescript();
|
||||||
|
printer ??= ts.createPrinter();
|
||||||
|
|
||||||
|
const migrationInfo = parseMigrationInfo(
|
||||||
|
tree,
|
||||||
|
cypressConfigPath,
|
||||||
|
projectName,
|
||||||
|
projectGraph
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!migrationInfo) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (migrationInfo.framework === 'angular') {
|
||||||
|
migrateAngularFramework(
|
||||||
|
tree,
|
||||||
|
projectConfig,
|
||||||
|
migrationInfo.isLegacyVersion
|
||||||
|
);
|
||||||
|
} else if (migrationInfo.framework === 'react') {
|
||||||
|
migrateReactFramework(tree, projectConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMigrationInfo(
|
||||||
|
tree: Tree,
|
||||||
|
cypressConfigPath: string,
|
||||||
|
projectName: string,
|
||||||
|
projectGraph: ProjectGraph
|
||||||
|
): {
|
||||||
|
framework?: 'angular' | 'react';
|
||||||
|
isLegacyVersion?: boolean;
|
||||||
|
} | null {
|
||||||
|
const cypressConfig = tree.read(cypressConfigPath, 'utf-8');
|
||||||
|
const config = resolveCypressConfigObject(cypressConfig);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
// couldn't find the config object, leave as is
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getObjectProperty(config, 'component')) {
|
||||||
|
// no component config, leave as is
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const framework = resolveFramework(
|
||||||
|
cypressConfig,
|
||||||
|
config,
|
||||||
|
projectName,
|
||||||
|
projectGraph
|
||||||
|
);
|
||||||
|
|
||||||
|
if (framework === 'react') {
|
||||||
|
return { framework: 'react' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (framework === 'angular') {
|
||||||
|
const angularCoreDep = projectGraph.dependencies[projectName].find((d) =>
|
||||||
|
// account for possible different versions of angular core
|
||||||
|
d.target.startsWith('npm:@angular/core')
|
||||||
|
);
|
||||||
|
if (angularCoreDep) {
|
||||||
|
const angularVersion =
|
||||||
|
projectGraph.externalNodes?.[angularCoreDep.target]?.data?.version;
|
||||||
|
if (valid(angularVersion) && lt(angularVersion, '17.2.0')) {
|
||||||
|
return {
|
||||||
|
framework: 'angular',
|
||||||
|
isLegacyVersion: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
framework: 'angular',
|
||||||
|
isLegacyVersion: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFramework(
|
||||||
|
cypressConfig: string,
|
||||||
|
config: ObjectLiteralExpression,
|
||||||
|
projectName: string,
|
||||||
|
projectGraph: ProjectGraph
|
||||||
|
): string | null {
|
||||||
|
const frameworkProperty = tsquery.query<PropertyAssignment>(
|
||||||
|
config,
|
||||||
|
'PropertyAssignment:has(Identifier[name=component]) PropertyAssignment:has(Identifier[name=devServer]) PropertyAssignment:has(Identifier[name=framework])'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (frameworkProperty) {
|
||||||
|
return ts.isStringLiteral(frameworkProperty.initializer)
|
||||||
|
? frameworkProperty.initializer.getText().replace(/['"`]/g, '')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// component might be assigned to an Nx preset function call, so we try to
|
||||||
|
// infer the framework from the Nx preset import
|
||||||
|
const sourceFile = tsquery.ast(cypressConfig);
|
||||||
|
const nxPresetModuleSpecifiers = [
|
||||||
|
'@nx/angular/plugins/component-testing',
|
||||||
|
'@nx/react/plugins/component-testing',
|
||||||
|
'@nx/next/plugins/component-testing',
|
||||||
|
'@nx/remix/plugins/component-testing',
|
||||||
|
];
|
||||||
|
const nxPresetImportModuleSpecifier = sourceFile.statements
|
||||||
|
.find(
|
||||||
|
(s): s is ImportDeclaration =>
|
||||||
|
ts.isImportDeclaration(s) &&
|
||||||
|
nxPresetModuleSpecifiers.includes(
|
||||||
|
s.moduleSpecifier.getText().replace(/['"`]/g, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
?.moduleSpecifier.getText()
|
||||||
|
.replace(/['"`]/g, '');
|
||||||
|
if (nxPresetImportModuleSpecifier) {
|
||||||
|
const plugin = nxPresetImportModuleSpecifier.split('/').at(1);
|
||||||
|
|
||||||
|
return plugin === 'angular' ? 'angular' : 'react';
|
||||||
|
}
|
||||||
|
|
||||||
|
// it might be set to something else, so we fall back to checking the
|
||||||
|
// project dependencies
|
||||||
|
if (
|
||||||
|
projectGraph.dependencies[projectName]?.some((d) =>
|
||||||
|
d.target.startsWith('npm:@angular/core')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'angular';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
projectGraph.dependencies[projectName]?.some(
|
||||||
|
(d) => d.target.startsWith('npm:react') || d.target.startsWith('npm:next')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'react';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.cypress.io/app/references/migration-guide#Angular-1720-CT-no-longer-supported
|
||||||
|
function migrateAngularFramework(
|
||||||
|
tree: Tree,
|
||||||
|
projectConfig: ProjectConfiguration,
|
||||||
|
isLegacyVersion: boolean
|
||||||
|
) {
|
||||||
|
visitNotIgnoredFiles(tree, projectConfig.root, (filePath) => {
|
||||||
|
if (!isJsTsFile(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = tree.read(filePath, 'utf-8');
|
||||||
|
|
||||||
|
let updatedFileContent: string;
|
||||||
|
if (isLegacyVersion) {
|
||||||
|
let needPackage = false;
|
||||||
|
|
||||||
|
updatedFileContent = tsquery.replace(
|
||||||
|
content,
|
||||||
|
'ImportDeclaration',
|
||||||
|
importTransformerFactory(
|
||||||
|
content,
|
||||||
|
'cypress/angular',
|
||||||
|
'@cypress/angular',
|
||||||
|
() => {
|
||||||
|
needPackage = true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (needPackage) {
|
||||||
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{ '@cypress/angular': '^2.1.0' },
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatedFileContent = tsquery.replace(
|
||||||
|
content,
|
||||||
|
'ImportDeclaration',
|
||||||
|
importTransformerFactory(
|
||||||
|
content,
|
||||||
|
'cypress/angular-signals',
|
||||||
|
'cypress/angular'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.write(filePath, updatedFileContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.cypress.io/app/references/migration-guide#React-18-CT-no-longer-supported
|
||||||
|
function migrateReactFramework(
|
||||||
|
tree: Tree,
|
||||||
|
projectConfig: ProjectConfiguration
|
||||||
|
) {
|
||||||
|
visitNotIgnoredFiles(tree, projectConfig.root, (filePath) => {
|
||||||
|
if (!isJsTsFile(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = tree.read(filePath, 'utf-8');
|
||||||
|
const updatedContent = tsquery.replace(
|
||||||
|
content,
|
||||||
|
'ImportDeclaration',
|
||||||
|
importTransformerFactory(content, 'cypress/react18', 'cypress/react')
|
||||||
|
);
|
||||||
|
|
||||||
|
tree.write(filePath, updatedContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function importTransformerFactory(
|
||||||
|
fileContent: string,
|
||||||
|
sourceModuleSpecifier: string,
|
||||||
|
targetModuleSpecifier: string,
|
||||||
|
matchImportCallback?: () => void
|
||||||
|
): Parameters<typeof tsquery.replace>[2] {
|
||||||
|
return (node: ImportDeclaration) => {
|
||||||
|
if (
|
||||||
|
node.moduleSpecifier.getText().replace(/['"`]/g, '') ===
|
||||||
|
sourceModuleSpecifier
|
||||||
|
) {
|
||||||
|
matchImportCallback?.();
|
||||||
|
const updatedImport = ts.factory.updateImportDeclaration(
|
||||||
|
node,
|
||||||
|
node.modifiers,
|
||||||
|
node.importClause,
|
||||||
|
ts.factory.createStringLiteral(targetModuleSpecifier),
|
||||||
|
node.attributes
|
||||||
|
);
|
||||||
|
|
||||||
|
return printer.printNode(
|
||||||
|
ts.EmitHint.Unspecified,
|
||||||
|
updatedImport,
|
||||||
|
tsquery.ast(fileContent)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.getText();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isJsTsFile(filePath: string) {
|
||||||
|
return /\.[cm]?[jt]sx?$/.test(filePath);
|
||||||
|
}
|
||||||
@ -7,8 +7,10 @@ import {
|
|||||||
Tree,
|
Tree,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { Linter, LinterType, lintProjectGenerator } from '@nx/eslint';
|
import { Linter, LinterType, lintProjectGenerator } from '@nx/eslint';
|
||||||
import { installedCypressVersion } from './cypress-version';
|
import {
|
||||||
import { eslintPluginCypressVersion } from './versions';
|
javaScriptOverride,
|
||||||
|
typeScriptOverride,
|
||||||
|
} from '@nx/eslint/src/generators/init/global-eslint-config';
|
||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
addOverrideToLintConfig,
|
addOverrideToLintConfig,
|
||||||
@ -18,11 +20,8 @@ import {
|
|||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
replaceOverridesInLintConfig,
|
replaceOverridesInLintConfig,
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
import {
|
|
||||||
javaScriptOverride,
|
|
||||||
typeScriptOverride,
|
|
||||||
} from '@nx/eslint/src/generators/init/global-eslint-config';
|
|
||||||
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
import { getInstalledCypressMajorVersion, versions } from './versions';
|
||||||
|
|
||||||
export interface CyLinterOptions {
|
export interface CyLinterOptions {
|
||||||
project: string;
|
project: string;
|
||||||
@ -77,15 +76,17 @@ export async function addLinterToCyProject(
|
|||||||
|
|
||||||
options.overwriteExisting = options.overwriteExisting || !eslintFile;
|
options.overwriteExisting = options.overwriteExisting || !eslintFile;
|
||||||
|
|
||||||
|
if (!options.skipPackageJson) {
|
||||||
|
const pkgVersions = versions(tree);
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
!options.skipPackageJson
|
addDependenciesToPackageJson(
|
||||||
? addDependenciesToPackageJson(
|
|
||||||
tree,
|
tree,
|
||||||
{},
|
{},
|
||||||
{ 'eslint-plugin-cypress': eslintPluginCypressVersion }
|
{ 'eslint-plugin-cypress': pkgVersions.eslintPluginCypressVersion }
|
||||||
)
|
)
|
||||||
: () => {}
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isEslintConfigSupported(tree, projectConfig.root) ||
|
isEslintConfigSupported(tree, projectConfig.root) ||
|
||||||
@ -119,7 +120,7 @@ export async function addLinterToCyProject(
|
|||||||
);
|
);
|
||||||
tasks.push(addExtendsTask);
|
tasks.push(addExtendsTask);
|
||||||
}
|
}
|
||||||
const cyVersion = installedCypressVersion();
|
const cyVersion = getInstalledCypressMajorVersion(tree);
|
||||||
/**
|
/**
|
||||||
* We need this override because we enabled allowJS in the tsconfig to allow for JS based Cypress tests.
|
* We need this override because we enabled allowJS in the tsconfig to allow for JS based Cypress tests.
|
||||||
* That however leads to issues with the CommonJS Cypress plugin file.
|
* That however leads to issues with the CommonJS Cypress plugin file.
|
||||||
|
|||||||
@ -2,7 +2,9 @@ import {
|
|||||||
addDefaultCTConfig,
|
addDefaultCTConfig,
|
||||||
addDefaultE2EConfig,
|
addDefaultE2EConfig,
|
||||||
addMountDefinition,
|
addMountDefinition,
|
||||||
|
resolveCypressConfigObject,
|
||||||
} from './config';
|
} from './config';
|
||||||
|
|
||||||
describe('Cypress Config parser', () => {
|
describe('Cypress Config parser', () => {
|
||||||
it('should add CT config to existing e2e config', async () => {
|
it('should add CT config to existing e2e config', async () => {
|
||||||
const actual = await addDefaultCTConfig(
|
const actual = await addDefaultCTConfig(
|
||||||
@ -261,3 +263,133 @@ Cypress.Commands.add('mount', customMount);
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('resolveCypressConfigObject', () => {
|
||||||
|
it('should handle "export default defineConfig()"', async () => {
|
||||||
|
const config = resolveCypressConfigObject(
|
||||||
|
`import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
expect(config.getText()).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle "export default {}"', async () => {
|
||||||
|
const config = resolveCypressConfigObject(
|
||||||
|
`export default {
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
expect(config.getText()).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle "export default <variable>" when <variable> is defined in the file and is an object literal', async () => {
|
||||||
|
const config = resolveCypressConfigObject(
|
||||||
|
`const config = {
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
expect(config.getText()).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle "module.exports = defineConfig()"', async () => {
|
||||||
|
const config = resolveCypressConfigObject(
|
||||||
|
`const { defineConfig } = require('cypress');
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
expect(config.getText()).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle "module.exports = {}"', async () => {
|
||||||
|
const config = resolveCypressConfigObject(
|
||||||
|
`module.exports = {
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
expect(config.getText()).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle "module.exports = <variable>" when <variable> is defined in the file and is an object literal', async () => {
|
||||||
|
const config = resolveCypressConfigObject(
|
||||||
|
`const config = {
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
expect(config.getText()).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
import { glob, joinPathFragments, type Tree } from '@nx/devkit';
|
import { glob, joinPathFragments, type Tree } from '@nx/devkit';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
import type {
|
import type {
|
||||||
|
BinaryExpression,
|
||||||
|
ExportAssignment,
|
||||||
|
Expression,
|
||||||
|
ExpressionStatement,
|
||||||
InterfaceDeclaration,
|
InterfaceDeclaration,
|
||||||
MethodSignature,
|
MethodSignature,
|
||||||
ObjectLiteralExpression,
|
ObjectLiteralExpression,
|
||||||
PropertyAssignment,
|
PropertyAssignment,
|
||||||
PropertySignature,
|
PropertySignature,
|
||||||
|
SourceFile,
|
||||||
} from 'typescript';
|
} from 'typescript';
|
||||||
import type {
|
import type {
|
||||||
NxComponentTestingOptions,
|
NxComponentTestingOptions,
|
||||||
@ -184,3 +190,88 @@ export function getProjectCypressConfigPath(
|
|||||||
|
|
||||||
return cypressConfigPaths[0];
|
return cypressConfigPaths[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveCypressConfigObject(
|
||||||
|
cypressConfigContents: string
|
||||||
|
): ObjectLiteralExpression | null {
|
||||||
|
const ts = ensureTypescript();
|
||||||
|
|
||||||
|
const { tsquery } = <typeof import('@phenomnomnominal/tsquery')>(
|
||||||
|
require('@phenomnomnominal/tsquery')
|
||||||
|
);
|
||||||
|
const sourceFile = tsquery.ast(cypressConfigContents);
|
||||||
|
|
||||||
|
const exportDefaultStatement = sourceFile.statements.find(
|
||||||
|
(statement): statement is ExportAssignment =>
|
||||||
|
ts.isExportAssignment(statement)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (exportDefaultStatement) {
|
||||||
|
return resolveCypressConfigObjectFromExportExpression(
|
||||||
|
exportDefaultStatement.expression,
|
||||||
|
sourceFile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleExportsStatement = sourceFile.statements.find(
|
||||||
|
(
|
||||||
|
statement
|
||||||
|
): statement is ExpressionStatement & { expression: BinaryExpression } =>
|
||||||
|
ts.isExpressionStatement(statement) &&
|
||||||
|
ts.isBinaryExpression(statement.expression) &&
|
||||||
|
statement.expression.left.getText() === 'module.exports'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleExportsStatement) {
|
||||||
|
return resolveCypressConfigObjectFromExportExpression(
|
||||||
|
moduleExportsStatement.expression.right,
|
||||||
|
sourceFile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCypressConfigObjectFromExportExpression(
|
||||||
|
exportExpression: Expression,
|
||||||
|
sourceFile: SourceFile
|
||||||
|
): ObjectLiteralExpression | null {
|
||||||
|
const ts = ensureTypescript();
|
||||||
|
|
||||||
|
if (ts.isObjectLiteralExpression(exportExpression)) {
|
||||||
|
return exportExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ts.isIdentifier(exportExpression)) {
|
||||||
|
// try to locate the identifier in the source file
|
||||||
|
const variableStatements = sourceFile.statements.filter((statement) =>
|
||||||
|
ts.isVariableStatement(statement)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const variableStatement of variableStatements) {
|
||||||
|
for (const declaration of variableStatement.declarationList
|
||||||
|
.declarations) {
|
||||||
|
if (
|
||||||
|
ts.isIdentifier(declaration.name) &&
|
||||||
|
declaration.name.getText() === exportExpression.getText() &&
|
||||||
|
ts.isObjectLiteralExpression(declaration.initializer)
|
||||||
|
) {
|
||||||
|
return declaration.initializer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ts.isCallExpression(exportExpression) &&
|
||||||
|
ts.isIdentifier(exportExpression.expression) &&
|
||||||
|
exportExpression.expression.getText() === 'defineConfig' &&
|
||||||
|
ts.isObjectLiteralExpression(exportExpression.arguments[0])
|
||||||
|
) {
|
||||||
|
return exportExpression.arguments[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
let cypressPackageJson;
|
let cypressPackageJson;
|
||||||
let loadedCypress = false;
|
let loadedCypress = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use the `getInstalledCypressMajorVersion` exported from
|
||||||
|
* `@nx/cypress/src/utils/versions` instead. It will be removed in v22.
|
||||||
|
*/
|
||||||
export function installedCypressVersion() {
|
export function installedCypressVersion() {
|
||||||
if (!loadedCypress) {
|
if (!loadedCypress) {
|
||||||
try {
|
try {
|
||||||
@ -21,6 +25,8 @@ export function installedCypressVersion() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* will not throw if cypress is not installed
|
* will not throw if cypress is not installed
|
||||||
|
* @deprecated use the `assertMinimumCypressVersion` exported from
|
||||||
|
* `@nx/cypress/src/utils/versions` instead. It will be removed in v22.
|
||||||
*/
|
*/
|
||||||
export function assertMinimumCypressVersion(minVersion: number) {
|
export function assertMinimumCypressVersion(minVersion: number) {
|
||||||
const version = installedCypressVersion();
|
const version = installedCypressVersion();
|
||||||
|
|||||||
124
packages/cypress/src/utils/migrations.ts
Normal file
124
packages/cypress/src/utils/migrations.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
getProjects,
|
||||||
|
globAsync,
|
||||||
|
type ProjectConfiguration,
|
||||||
|
type TargetConfiguration,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { posix } from 'path';
|
||||||
|
import type {
|
||||||
|
Expression,
|
||||||
|
ObjectLiteralExpression,
|
||||||
|
PropertyAssignment,
|
||||||
|
} from 'typescript';
|
||||||
|
import type { CypressExecutorOptions } from '../executors/cypress/cypress.impl';
|
||||||
|
import { CYPRESS_CONFIG_FILE_NAME_PATTERN } from './config';
|
||||||
|
|
||||||
|
let ts: typeof import('typescript');
|
||||||
|
|
||||||
|
export async function* cypressProjectConfigs(tree: Tree): AsyncGenerator<{
|
||||||
|
projectName: string;
|
||||||
|
projectConfig: ProjectConfiguration;
|
||||||
|
cypressConfigPath: string;
|
||||||
|
}> {
|
||||||
|
const projects = getProjects(tree);
|
||||||
|
|
||||||
|
for (const [projectName, projectConfig] of projects) {
|
||||||
|
const targetWithExecutor = Object.values(projectConfig.targets ?? {}).find(
|
||||||
|
(target) => target.executor === '@nx/cypress:cypress'
|
||||||
|
);
|
||||||
|
if (targetWithExecutor) {
|
||||||
|
for (const [, options] of allTargetOptions<CypressExecutorOptions>(
|
||||||
|
targetWithExecutor
|
||||||
|
)) {
|
||||||
|
if (options.cypressConfig) {
|
||||||
|
yield {
|
||||||
|
projectName,
|
||||||
|
projectConfig,
|
||||||
|
cypressConfigPath: options.cypressConfig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// might be using the crystal plugin
|
||||||
|
const result = await globAsync(tree, [
|
||||||
|
posix.join(projectConfig.root, CYPRESS_CONFIG_FILE_NAME_PATTERN),
|
||||||
|
]);
|
||||||
|
if (result.length > 0) {
|
||||||
|
yield {
|
||||||
|
projectName,
|
||||||
|
projectConfig,
|
||||||
|
cypressConfigPath: result[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getObjectProperty(
|
||||||
|
config: ObjectLiteralExpression,
|
||||||
|
name: string
|
||||||
|
): PropertyAssignment | undefined {
|
||||||
|
ts ??= ensureTypescript();
|
||||||
|
|
||||||
|
return config.properties.find(
|
||||||
|
(p): p is PropertyAssignment =>
|
||||||
|
ts.isPropertyAssignment(p) && p.name.getText() === name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeObjectProperty(
|
||||||
|
config: ObjectLiteralExpression,
|
||||||
|
property: PropertyAssignment
|
||||||
|
): ObjectLiteralExpression {
|
||||||
|
ts ??= ensureTypescript();
|
||||||
|
|
||||||
|
return ts.factory.updateObjectLiteralExpression(
|
||||||
|
config,
|
||||||
|
config.properties.filter((p) => p !== property)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateObjectProperty(
|
||||||
|
config: ObjectLiteralExpression,
|
||||||
|
property: PropertyAssignment,
|
||||||
|
{ newName, newValue }: { newName?: string; newValue?: Expression }
|
||||||
|
): ObjectLiteralExpression {
|
||||||
|
ts ??= ensureTypescript();
|
||||||
|
|
||||||
|
if (!newName && !newValue) {
|
||||||
|
throw new Error('newName or newValue must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts.factory.updateObjectLiteralExpression(
|
||||||
|
config,
|
||||||
|
config.properties.map((p) =>
|
||||||
|
p === property
|
||||||
|
? ts.factory.updatePropertyAssignment(
|
||||||
|
p,
|
||||||
|
newName ? ts.factory.createIdentifier(newName) : p.name,
|
||||||
|
newValue ? newValue : p.initializer
|
||||||
|
)
|
||||||
|
: p
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function* allTargetOptions<T>(
|
||||||
|
target: TargetConfiguration<T>
|
||||||
|
): Iterable<[string | undefined, T]> {
|
||||||
|
if (target.options) {
|
||||||
|
yield [undefined, target.options];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.configurations) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, options] of Object.entries(target.configurations)) {
|
||||||
|
if (options !== undefined) {
|
||||||
|
yield [name, options];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,111 @@
|
|||||||
|
import { readJson, type Tree } from '@nx/devkit';
|
||||||
|
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||||
|
import { clean, coerce, major } from 'semver';
|
||||||
|
|
||||||
export const nxVersion = require('../../package.json').version;
|
export const nxVersion = require('../../package.json').version;
|
||||||
export const eslintPluginCypressVersion = '^3.5.0';
|
export const eslintPluginCypressVersion = '^3.5.0';
|
||||||
export const typesNodeVersion = '18.16.9';
|
export const typesNodeVersion = '18.16.9';
|
||||||
export const cypressViteDevServerVersion = '^2.2.1';
|
export const cypressViteDevServerVersion = '^6.0.3';
|
||||||
export const cypressVersion = '^13.13.0';
|
export const cypressVersion = '^14.2.1';
|
||||||
export const cypressWebpackVersion = '^3.8.0';
|
export const cypressWebpackVersion = '^4.0.2';
|
||||||
export const viteVersion = '~5.0.0';
|
export const viteVersion = '^6.0.0';
|
||||||
export const htmlWebpackPluginVersion = '^5.5.0';
|
export const htmlWebpackPluginVersion = '^5.5.0';
|
||||||
|
|
||||||
|
const latestVersions: Omit<
|
||||||
|
typeof import('./versions'),
|
||||||
|
'versions' | 'getInstalledCypressMajorVersion' | 'assertMinimumCypressVersion'
|
||||||
|
> = {
|
||||||
|
nxVersion,
|
||||||
|
eslintPluginCypressVersion,
|
||||||
|
typesNodeVersion,
|
||||||
|
cypressViteDevServerVersion,
|
||||||
|
cypressVersion,
|
||||||
|
cypressWebpackVersion,
|
||||||
|
viteVersion,
|
||||||
|
htmlWebpackPluginVersion,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function versions(
|
||||||
|
tree: Tree,
|
||||||
|
cypressMajorVersion = getInstalledCypressMajorVersion(tree)
|
||||||
|
) {
|
||||||
|
if (!cypressMajorVersion) {
|
||||||
|
return latestVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cypressMajorVersion > 14) {
|
||||||
|
throw new Error(`Unsupported Cypress version: ${cypressVersion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cypressMajorVersion === 14) {
|
||||||
|
return latestVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nxVersion,
|
||||||
|
eslintPluginCypressVersion: '^3.5.0',
|
||||||
|
typesNodeVersion: '18.16.9',
|
||||||
|
cypressViteDevServerVersion: '^2.2.1',
|
||||||
|
cypressVersion: '^13.13.0',
|
||||||
|
cypressWebpackVersion: '^3.8.0',
|
||||||
|
viteVersion: '~5.0.0',
|
||||||
|
htmlWebpackPluginVersion: '^5.5.0',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstalledCypressMajorVersion(tree?: Tree): number | null {
|
||||||
|
try {
|
||||||
|
let version: string | null;
|
||||||
|
|
||||||
|
if (tree) {
|
||||||
|
version = getCypressVersionFromTree(tree);
|
||||||
|
} else {
|
||||||
|
version = getCypressVersionFromFileSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
return version ? major(version) : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertMinimumCypressVersion(
|
||||||
|
minVersion: number,
|
||||||
|
tree?: Tree
|
||||||
|
): void {
|
||||||
|
const version = getInstalledCypressMajorVersion(tree);
|
||||||
|
if (version && version < minVersion) {
|
||||||
|
throw new Error(
|
||||||
|
`Cypress version of ${minVersion} or higher is not installed. Expected Cypress v${minVersion}+, found Cypress v${version} instead.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCypressVersionFromTree(tree: Tree): string | null {
|
||||||
|
const packageJson = readJson(tree, 'package.json');
|
||||||
|
const installedVersion =
|
||||||
|
packageJson.devDependencies?.cypress ?? packageJson.dependencies?.cypress;
|
||||||
|
|
||||||
|
if (!installedVersion) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installedVersion === 'latest' || installedVersion === 'next') {
|
||||||
|
return clean(cypressVersion) ?? coerce(cypressVersion)?.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clean(installedVersion) ?? coerce(installedVersion)?.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCypressVersionFromFileSystem(): string | null {
|
||||||
|
let packageJson: PackageJson | undefined;
|
||||||
|
try {
|
||||||
|
packageJson = <PackageJson>require('cypress/package.json');
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (!packageJson) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageJson.version;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,17 +1,26 @@
|
|||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import { readJson, readProjectConfiguration, Tree } from '@nx/devkit';
|
import { readJson, readProjectConfiguration, Tree } from '@nx/devkit';
|
||||||
import { cypressComponentConfiguration } from './cypress-component-configuration';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { setupTailwindGenerator } from '@nx/react';
|
||||||
import { applicationGenerator } from '../application/application';
|
import { applicationGenerator } from '../application/application';
|
||||||
import { libraryGenerator } from '../library/library';
|
import { libraryGenerator } from '../library/library';
|
||||||
import { setupTailwindGenerator } from '@nx/react';
|
import { cypressComponentConfiguration } from './cypress-component-configuration';
|
||||||
import { Linter } from '@nx/eslint';
|
|
||||||
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual<any>('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('cypress-component-configuration generator', () => {
|
describe('cypress-component-configuration generator', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
|
let mockedInstalledCypressMajorVersion: jest.Mock<
|
||||||
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
|
> = getInstalledCypressMajorVersion as never;
|
||||||
// TODO(@leosvelperez): Turn this back to adding the plugin
|
// TODO(@leosvelperez): Turn this back to adding the plugin
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace();
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
mockedInstalledCypressMajorVersion.mockReturnValue(14);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup nextjs app', async () => {
|
it('should setup nextjs app', async () => {
|
||||||
@ -57,7 +66,7 @@ describe('cypress-component-configuration generator', () => {
|
|||||||
`);
|
`);
|
||||||
expect(tree.read('demo/cypress/support/component.ts', 'utf-8'))
|
expect(tree.read('demo/cypress/support/component.ts', 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"import { mount } from 'cypress/react18';
|
"import { mount } from 'cypress/react';
|
||||||
import './styles.ct.css';
|
import './styles.ct.css';
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
// This example support/component.ts is processed and
|
// This example support/component.ts is processed and
|
||||||
@ -104,6 +113,56 @@ describe('cypress-component-configuration generator', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should import "mount" from "cypress/react18" when cypress version is lower than v14', async () => {
|
||||||
|
mockedInstalledCypressMajorVersion.mockReturnValue(13);
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
directory: 'demo',
|
||||||
|
style: 'css',
|
||||||
|
});
|
||||||
|
|
||||||
|
await cypressComponentConfiguration(tree, {
|
||||||
|
generateTests: true,
|
||||||
|
project: 'demo',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('demo/cypress/support/component.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/react18';
|
||||||
|
import './styles.ct.css';
|
||||||
|
// ***********************************************************
|
||||||
|
// This example support/component.ts is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.ts using ES2015 syntax:
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
// add component testing only related command here, such as mount
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace Cypress {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should add styles setup in app', async () => {
|
it('should add styles setup in app', async () => {
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
directory: 'demo',
|
directory: 'demo',
|
||||||
@ -131,7 +190,7 @@ describe('cypress-component-configuration generator', () => {
|
|||||||
it('should setup nextjs lib', async () => {
|
it('should setup nextjs lib', async () => {
|
||||||
await libraryGenerator(tree, {
|
await libraryGenerator(tree, {
|
||||||
directory: 'demo',
|
directory: 'demo',
|
||||||
linter: Linter.EsLint,
|
linter: 'eslint',
|
||||||
style: 'css',
|
style: 'css',
|
||||||
unitTestRunner: 'jest',
|
unitTestRunner: 'jest',
|
||||||
component: true,
|
component: true,
|
||||||
|
|||||||
@ -91,6 +91,10 @@ async function addFiles(
|
|||||||
const { addMountDefinition, addDefaultCTConfig } = await import(
|
const { addMountDefinition, addDefaultCTConfig } = await import(
|
||||||
'@nx/cypress/src/utils/config'
|
'@nx/cypress/src/utils/config'
|
||||||
);
|
);
|
||||||
|
const { getInstalledCypressMajorVersion } = await import(
|
||||||
|
'@nx/cypress/src/utils/versions'
|
||||||
|
);
|
||||||
|
const installedCypressMajorVersion = getInstalledCypressMajorVersion(tree);
|
||||||
|
|
||||||
const ctFile = joinPathFragments(
|
const ctFile = joinPathFragments(
|
||||||
projectConfig.root,
|
projectConfig.root,
|
||||||
@ -102,9 +106,11 @@ async function addFiles(
|
|||||||
const updatedCommandFile = await addMountDefinition(
|
const updatedCommandFile = await addMountDefinition(
|
||||||
tree.read(ctFile, 'utf-8')
|
tree.read(ctFile, 'utf-8')
|
||||||
);
|
);
|
||||||
|
const moduleSpecifier =
|
||||||
|
installedCypressMajorVersion >= 14 ? 'cypress/react' : 'cypress/react18';
|
||||||
tree.write(
|
tree.write(
|
||||||
ctFile,
|
ctFile,
|
||||||
`import { mount } from 'cypress/react18';\nimport './styles.ct.css';\n${updatedCommandFile}`
|
`import { mount } from '${moduleSpecifier}';\nimport './styles.ct.css';\n${updatedCommandFile}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const cyFile = joinPathFragments(projectConfig.root, 'cypress.config.ts');
|
const cyFile = joinPathFragments(projectConfig.root, 'cypress.config.ts');
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import {
|
import {
|
||||||
readJson,
|
readJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
@ -13,12 +13,15 @@ import { Schema } from './schema';
|
|||||||
|
|
||||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||||
// which is v9 while we are testing for the new v10 version
|
// which is v9 while we are testing for the new v10 version
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('next library', () => {
|
describe('next library', () => {
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
it('should use @nx/next images.d.ts file', async () => {
|
it('should use @nx/next images.d.ts file', async () => {
|
||||||
const baseOptions: Schema = {
|
const baseOptions: Schema = {
|
||||||
directory: '',
|
directory: '',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import { readProjectConfiguration, Tree } from '@nx/devkit';
|
import { readProjectConfiguration, Tree } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Linter } from '@nx/eslint';
|
import { Linter } from '@nx/eslint';
|
||||||
@ -8,7 +8,10 @@ import { applicationGenerator } from './application';
|
|||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||||
// which is v9 while we are testing for the new v10 version
|
// which is v9 while we are testing for the new v10 version
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
describe('react app generator (legacy)', () => {
|
describe('react app generator (legacy)', () => {
|
||||||
let appTree: Tree;
|
let appTree: Tree;
|
||||||
let schema: Schema = {
|
let schema: Schema = {
|
||||||
@ -22,8 +25,8 @@ describe('react app generator (legacy)', () => {
|
|||||||
addPlugin: false,
|
addPlugin: false,
|
||||||
};
|
};
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
mockedInstalledCypressVersion.mockReturnValue(10);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import {
|
import {
|
||||||
detectPackageManager,
|
detectPackageManager,
|
||||||
getPackageManagerCommand,
|
getPackageManagerCommand,
|
||||||
@ -20,7 +20,10 @@ import { Schema } from './schema';
|
|||||||
const { load } = require('@zkochan/js-yaml');
|
const { load } = require('@zkochan/js-yaml');
|
||||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||||
// which is v9 while we are testing for the new v10 version
|
// which is v9 while we are testing for the new v10 version
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
let projectGraph: ProjectGraph;
|
let projectGraph: ProjectGraph;
|
||||||
jest.mock('@nx/devkit', () => {
|
jest.mock('@nx/devkit', () => {
|
||||||
@ -49,8 +52,8 @@ describe('app', () => {
|
|||||||
addPlugin: true,
|
addPlugin: true,
|
||||||
};
|
};
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
mockedInstalledCypressVersion.mockReturnValue(10);
|
||||||
appTree = createTreeWithEmptyWorkspace();
|
appTree = createTreeWithEmptyWorkspace();
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { assertMinimumCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { assertMinimumCypressVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import { Tree } from '@nx/devkit';
|
import { Tree } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Linter } from '@nx/eslint';
|
import { Linter } from '@nx/eslint';
|
||||||
import libraryGenerator from '../library/library';
|
import libraryGenerator from '../library/library';
|
||||||
import { componentTestGenerator } from './component-test';
|
import { componentTestGenerator } from './component-test';
|
||||||
|
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions');
|
||||||
describe(componentTestGenerator.name, () => {
|
describe(componentTestGenerator.name, () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedAssertMinimumCypressVersion: jest.Mock<
|
let mockedAssertMinimumCypressVersion: jest.Mock<
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export async function componentTestGenerator(
|
|||||||
) {
|
) {
|
||||||
ensurePackage('@nx/cypress', nxVersion);
|
ensurePackage('@nx/cypress', nxVersion);
|
||||||
const { assertMinimumCypressVersion } = await import(
|
const { assertMinimumCypressVersion } = await import(
|
||||||
'@nx/cypress/src/utils/cypress-version'
|
'@nx/cypress/src/utils/versions'
|
||||||
);
|
);
|
||||||
assertMinimumCypressVersion(10);
|
assertMinimumCypressVersion(10);
|
||||||
// normalize any windows paths
|
// normalize any windows paths
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import {
|
import {
|
||||||
logger,
|
logger,
|
||||||
readJson,
|
readJson,
|
||||||
@ -14,14 +14,17 @@ import { componentGenerator } from './component';
|
|||||||
|
|
||||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||||
// which is v9 while we are testing for the new v10 version
|
// which is v9 while we are testing for the new v10 version
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('component', () => {
|
describe('component', () => {
|
||||||
let appTree: Tree;
|
let appTree: Tree;
|
||||||
let projectName: string;
|
let projectName: string;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
mockedInstalledCypressVersion.mockReturnValue(10);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { assertMinimumCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import {
|
import {
|
||||||
DependencyType,
|
DependencyType,
|
||||||
ProjectGraph,
|
ProjectGraph,
|
||||||
@ -7,6 +7,11 @@ import {
|
|||||||
updateProjectConfiguration,
|
updateProjectConfiguration,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { Linter } from '@nx/eslint';
|
||||||
|
import { applicationGenerator } from '../application/application';
|
||||||
|
import { componentGenerator } from '../component/component';
|
||||||
|
import { libraryGenerator } from '../library/library';
|
||||||
|
import { cypressComponentConfigGenerator } from './cypress-component-configuration';
|
||||||
|
|
||||||
let projectGraph: ProjectGraph;
|
let projectGraph: ProjectGraph;
|
||||||
jest.mock('@nx/devkit', () => ({
|
jest.mock('@nx/devkit', () => ({
|
||||||
@ -16,14 +21,10 @@ jest.mock('@nx/devkit', () => ({
|
|||||||
.fn()
|
.fn()
|
||||||
.mockImplementation(async () => projectGraph),
|
.mockImplementation(async () => projectGraph),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
import { Linter } from '@nx/eslint';
|
...jest.requireActual<any>('@nx/cypress/src/utils/versions'),
|
||||||
import { applicationGenerator } from '../application/application';
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
import { componentGenerator } from '../component/component';
|
}));
|
||||||
import { libraryGenerator } from '../library/library';
|
|
||||||
import { cypressComponentConfigGenerator } from './cypress-component-configuration';
|
|
||||||
|
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
|
||||||
// nested code imports graph from the repo, which might have innacurate graph version
|
// nested code imports graph from the repo, which might have innacurate graph version
|
||||||
jest.mock('nx/src/project-graph/project-graph', () => ({
|
jest.mock('nx/src/project-graph/project-graph', () => ({
|
||||||
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
|
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
|
||||||
@ -32,20 +33,12 @@ jest.mock('nx/src/project-graph/project-graph', () => ({
|
|||||||
|
|
||||||
describe('React:CypressComponentTestConfiguration', () => {
|
describe('React:CypressComponentTestConfiguration', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedAssertCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof assertMinimumCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = assertMinimumCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
// TODO(@jaysoo): Turn this back to adding the plugin
|
// TODO(@jaysoo): Turn this back to adding the plugin
|
||||||
let originalEnv: string;
|
let originalEnv: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
originalEnv = process.env.NX_ADD_PLUGINS;
|
|
||||||
process.env.NX_ADD_PLUGINS = 'false';
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
process.env.NX_ADD_PLUGINS = originalEnv;
|
|
||||||
});
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace();
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
@ -53,6 +46,14 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
nodes: {},
|
nodes: {},
|
||||||
dependencies: {},
|
dependencies: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
originalEnv = process.env.NX_ADD_PLUGINS;
|
||||||
|
process.env.NX_ADD_PLUGINS = 'false';
|
||||||
|
mockedInstalledCypressVersion.mockReturnValue(14);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.NX_ADD_PLUGINS = originalEnv;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@ -60,8 +61,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate cypress config with vite', async () => {
|
it('should generate cypress config with vite', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -116,8 +115,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate cypress component test config with --build-target', async () => {
|
it('should generate cypress component test config with --build-target', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -184,7 +181,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate cypress component test config with project graph', async () => {
|
it('should generate cypress component test config with project graph', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -250,7 +246,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate cypress component test config with webpack', async () => {
|
it('should generate cypress component test config with webpack', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -315,7 +310,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should generate tests for existing tsx components', async () => {
|
it('should generate tests for existing tsx components', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -360,7 +354,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
it('should generate tests for existing js components', async () => {
|
it('should generate tests for existing js components', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -415,7 +408,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error when an invalid --build-target is provided', async () => {
|
it('should throw error when an invalid --build-target is provided', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -470,8 +462,6 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should setup cypress config files correctly', async () => {
|
it('should setup cypress config files correctly', async () => {
|
||||||
mockedAssertCypressVersion.mockReturnValue();
|
|
||||||
|
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
e2eTestRunner: 'none',
|
e2eTestRunner: 'none',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -531,6 +521,95 @@ describe('React:CypressComponentTestConfiguration', () => {
|
|||||||
});
|
});
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
|
expect(tree.read('some-lib/cypress/support/component.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { mount } from 'cypress/react';
|
||||||
|
// ***********************************************************
|
||||||
|
// This example support/component.ts is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.ts using ES2015 syntax:
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
// add component testing only related command here, such as mount
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace Cypress {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
mount: typeof mount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount);
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import "mount" from "cypress/react18" when cypress version is lower than v14', async () => {
|
||||||
|
mockedInstalledCypressVersion.mockReturnValue(13);
|
||||||
|
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
e2eTestRunner: 'none',
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
skipFormat: true,
|
||||||
|
style: 'scss',
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
directory: 'my-app',
|
||||||
|
bundler: 'vite',
|
||||||
|
});
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
directory: 'some-lib',
|
||||||
|
skipFormat: true,
|
||||||
|
skipTsConfig: false,
|
||||||
|
style: 'scss',
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
component: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
'my-app': {
|
||||||
|
name: 'my-app',
|
||||||
|
type: 'app',
|
||||||
|
data: {
|
||||||
|
...readProjectConfiguration(tree, 'my-app'),
|
||||||
|
} as any,
|
||||||
|
},
|
||||||
|
'some-lib': {
|
||||||
|
name: 'some-lib',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
...readProjectConfiguration(tree, 'some-lib'),
|
||||||
|
} as any,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
'my-app': [
|
||||||
|
{ type: DependencyType.static, source: 'my-app', target: 'some-lib' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await cypressComponentConfigGenerator(tree, {
|
||||||
|
project: 'some-lib',
|
||||||
|
generateTests: false,
|
||||||
|
buildTarget: 'my-app:build',
|
||||||
|
});
|
||||||
|
|
||||||
expect(tree.read('some-lib/cypress/support/component.ts', 'utf-8'))
|
expect(tree.read('some-lib/cypress/support/component.ts', 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"import { mount } from 'cypress/react18';
|
"import { mount } from 'cypress/react18';
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { FoundTarget } from '@nx/cypress/src/utils/find-target-options';
|
||||||
import {
|
import {
|
||||||
addDependenciesToPackageJson,
|
addDependenciesToPackageJson,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
@ -7,10 +8,9 @@ import {
|
|||||||
visitNotIgnoredFiles,
|
visitNotIgnoredFiles,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { nxVersion } from 'nx/src/utils/versions';
|
import { nxVersion } from 'nx/src/utils/versions';
|
||||||
|
import { getActualBundler, isComponent } from '../../../utils/ct-utils';
|
||||||
import { componentTestGenerator } from '../../component-test/component-test';
|
import { componentTestGenerator } from '../../component-test/component-test';
|
||||||
import type { CypressComponentConfigurationSchema } from '../schema';
|
import type { CypressComponentConfigurationSchema } from '../schema';
|
||||||
import { getActualBundler, isComponent } from '../../../utils/ct-utils';
|
|
||||||
import { FoundTarget } from '@nx/cypress/src/utils/find-target-options';
|
|
||||||
|
|
||||||
export async function addFiles(
|
export async function addFiles(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
@ -18,11 +18,13 @@ export async function addFiles(
|
|||||||
options: CypressComponentConfigurationSchema,
|
options: CypressComponentConfigurationSchema,
|
||||||
found: FoundTarget
|
found: FoundTarget
|
||||||
) {
|
) {
|
||||||
// must dyanmicaly import to prevent packages not using cypress from erroring out
|
// must dynamicaly import to prevent packages not using cypress from erroring out
|
||||||
// when importing react
|
// when importing react
|
||||||
const { addMountDefinition, addDefaultCTConfig } = await import(
|
const { addMountDefinition } = await import('@nx/cypress/src/utils/config');
|
||||||
'@nx/cypress/src/utils/config'
|
const { getInstalledCypressMajorVersion } = await import(
|
||||||
|
'@nx/cypress/src/utils/versions'
|
||||||
);
|
);
|
||||||
|
const installedCypressMajorVersion = getInstalledCypressMajorVersion(tree);
|
||||||
|
|
||||||
// Specifically undefined to allow Remix workaround of passing an empty string
|
// Specifically undefined to allow Remix workaround of passing an empty string
|
||||||
const actualBundler = await getActualBundler(tree, options, found);
|
const actualBundler = await getActualBundler(tree, options, found);
|
||||||
@ -46,9 +48,11 @@ export async function addFiles(
|
|||||||
const updatedCommandFile = await addMountDefinition(
|
const updatedCommandFile = await addMountDefinition(
|
||||||
tree.read(commandFile, 'utf-8')
|
tree.read(commandFile, 'utf-8')
|
||||||
);
|
);
|
||||||
|
const moduleSpecifier =
|
||||||
|
installedCypressMajorVersion >= 14 ? 'cypress/react' : 'cypress/react18';
|
||||||
tree.write(
|
tree.write(
|
||||||
commandFile,
|
commandFile,
|
||||||
`import { mount } from 'cypress/react18';\n${updatedCommandFile}`
|
`import { mount } from '${moduleSpecifier}';\n${updatedCommandFile}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import {
|
import {
|
||||||
getProjects,
|
getProjects,
|
||||||
readJson,
|
readJson,
|
||||||
@ -18,12 +18,15 @@ import { Schema } from './schema';
|
|||||||
const { load } = require('@zkochan/js-yaml');
|
const { load } = require('@zkochan/js-yaml');
|
||||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||||
// which is v9 while we are testing for the new v10 version
|
// which is v9 while we are testing for the new v10 version
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
describe('lib', () => {
|
describe('lib', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
let defaultSchema: Schema = {
|
let defaultSchema: Schema = {
|
||||||
directory: 'my-lib',
|
directory: 'my-lib',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import { getProjects, readProjectConfiguration, Tree } from '@nx/devkit';
|
import { getProjects, readProjectConfiguration, Tree } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
|
||||||
import { applicationGenerator } from './application';
|
import { applicationGenerator } from './application';
|
||||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||||
// which is v9 while we are testing for the new v10 version
|
// which is v9 while we are testing for the new v10 version
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
jest.mock('@nx/devkit', () => {
|
jest.mock('@nx/devkit', () => {
|
||||||
return {
|
return {
|
||||||
...jest.requireActual('@nx/devkit'),
|
...jest.requireActual('@nx/devkit'),
|
||||||
@ -17,8 +20,8 @@ jest.mock('@nx/devkit', () => {
|
|||||||
describe('web app generator (legacy)', () => {
|
describe('web app generator (legacy)', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
|
|
||||||
let originalEnv: string;
|
let originalEnv: string;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { getInstalledCypressMajorVersion } from '@nx/cypress/src/utils/versions';
|
||||||
import {
|
import {
|
||||||
readNxJson,
|
readNxJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
@ -18,7 +18,10 @@ import { Schema } from './schema';
|
|||||||
import { PackageManagerCommands } from 'nx/src/utils/package-manager';
|
import { PackageManagerCommands } from 'nx/src/utils/package-manager';
|
||||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||||
// which is v9 while we are testing for the new v10 version
|
// which is v9 while we are testing for the new v10 version
|
||||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
jest.mock('@nx/cypress/src/utils/versions', () => ({
|
||||||
|
...jest.requireActual('@nx/cypress/src/utils/versions'),
|
||||||
|
getInstalledCypressMajorVersion: jest.fn(),
|
||||||
|
}));
|
||||||
jest.mock('@nx/devkit', () => {
|
jest.mock('@nx/devkit', () => {
|
||||||
return {
|
return {
|
||||||
...jest.requireActual('@nx/devkit'),
|
...jest.requireActual('@nx/devkit'),
|
||||||
@ -29,8 +32,8 @@ jest.mock('@nx/devkit', () => {
|
|||||||
describe('app', () => {
|
describe('app', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
let mockedInstalledCypressVersion: jest.Mock<
|
let mockedInstalledCypressVersion: jest.Mock<
|
||||||
ReturnType<typeof installedCypressVersion>
|
ReturnType<typeof getInstalledCypressMajorVersion>
|
||||||
> = installedCypressVersion as never;
|
> = getInstalledCypressMajorVersion as never;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
mockedInstalledCypressVersion.mockReturnValue(10);
|
||||||
jest
|
jest
|
||||||
|
|||||||
@ -46,9 +46,11 @@ function check() {
|
|||||||
// which is @angular/core/testing. and the tests check for this
|
// which is @angular/core/testing. and the tests check for this
|
||||||
'packages/cypress/src/migrations/update-15-1-0/cypress-11.spec.ts',
|
'packages/cypress/src/migrations/update-15-1-0/cypress-11.spec.ts',
|
||||||
'packages/cypress/src/migrations/update-15-1-0/cypress-11.ts',
|
'packages/cypress/src/migrations/update-15-1-0/cypress-11.ts',
|
||||||
// this migration looks for projects depending on @angular/core, it doesn't require it
|
// these migrations looks for projects depending on @angular/core, it doesn't require it
|
||||||
'packages/cypress/src/migrations/update-16-4-0/warn-incompatible-angular-cypress.spec.ts',
|
'packages/cypress/src/migrations/update-16-4-0/warn-incompatible-angular-cypress.spec.ts',
|
||||||
'packages/cypress/src/migrations/update-16-4-0/warn-incompatible-angular-cypress.ts',
|
'packages/cypress/src/migrations/update-16-4-0/warn-incompatible-angular-cypress.ts',
|
||||||
|
'packages/cypress/src/migrations/update-20-8-0/update-component-testing-mount-imports.spec.ts',
|
||||||
|
'packages/cypress/src/migrations/update-20-8-0/update-component-testing-mount-imports.ts',
|
||||||
];
|
];
|
||||||
|
|
||||||
const files = [
|
const files = [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user