feat(testing): support cypress 11 (#13075)

This commit is contained in:
Caleb Ukle 2022-11-08 18:11:52 -06:00 committed by GitHub
parent a218149bca
commit 37c8483db2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 913 additions and 136 deletions

View File

@ -1,19 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Angular Cypress Component Test Generator should generate a component test 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
const config: MountConfig<MyLibComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(MyLibComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(MyLibComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -22,42 +25,51 @@ describe(MyLibComponent.name, () => {
}
});
})
})
"
`;
exports[`Angular Cypress Component Test Generator should handle component w/o inputs 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
const config: MountConfig<MyLibComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(MyLibComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(MyLibComponent, config);
cy.mount(MyLibComponent,);
})
})
"
`;
exports[`Angular Cypress Component Test Generator should work with standalone components 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
const config: MountConfig<MyLibComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(MyLibComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(MyLibComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -66,6 +78,7 @@ describe(MyLibComponent.name, () => {
}
});
})
})
"
`;

View File

@ -177,19 +177,24 @@ export class MyLibComponent implements OnInit {
await componentGenerator(tree, { name: 'my-lib', project: 'my-lib' });
const expected = `import { MountConfig } from 'cypress/angular';
const expected = `import { TestBed } from '@angular/core/testing';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
const config: MountConfig<MyLibComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(MyLibComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(MyLibComponent, config);
cy.mount(MyLibComponent,);
})
})
`;

View File

@ -1,19 +1,23 @@
import { MountConfig } from 'cypress/angular';
import { TestBed } from '@angular/core/testing';
import { <%= componentName %> } from './<%= componentFileName %>';
describe(<%= componentName %>.name, () => {
const config: MountConfig<<%= componentName %>> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(<%= componentName %>, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(<%= componentName %>,<% if(props.length > 0) { %> {
...config,
componentProperties: {<% for (let prop of props) { %>
<%= prop.name %>: <%- prop.defaultValue %>,<% } %>
}
}<% } else { %> config<% } %>);
}<% } %>);
})
})

View File

@ -1,19 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Cypress Component Testing Configuration should work with complex component 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
const config: MountConfig<SomethingOneComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingOneComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingOneComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -22,24 +25,28 @@ describe(SomethingOneComponent.name, () => {
}
});
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with complex component 2`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
const config: MountConfig<SomethingTwoComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingTwoComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingTwoComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -48,24 +55,28 @@ describe(SomethingTwoComponent.name, () => {
}
});
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with complex component 3`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
const config: MountConfig<SomethingThreeComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingThreeComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingThreeComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -74,24 +85,28 @@ describe(SomethingThreeComponent.name, () => {
}
});
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with complex standalone component 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
const config: MountConfig<SomethingOneComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingOneComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingOneComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -100,24 +115,28 @@ describe(SomethingOneComponent.name, () => {
}
});
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with complex standalone component 2`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
const config: MountConfig<SomethingTwoComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingTwoComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingTwoComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -126,24 +145,28 @@ describe(SomethingTwoComponent.name, () => {
}
});
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with complex standalone component 3`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
const config: MountConfig<SomethingThreeComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingThreeComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingThreeComponent, {
...config,
componentProperties: {
type: 'button',
style: 'default',
@ -152,150 +175,191 @@ describe(SomethingThreeComponent.name, () => {
}
});
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with secondary entry point libs 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { FancyButtonComponent } from './fancy-button.component';
describe(FancyButtonComponent.name, () => {
const config: MountConfig<FancyButtonComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(FancyButtonComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(FancyButtonComponent, config);
cy.mount(FancyButtonComponent,);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with secondary entry point libs 2`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { StandaloneFancyButtonComponent } from './standalone-fancy-button.component';
describe(StandaloneFancyButtonComponent.name, () => {
const config: MountConfig<StandaloneFancyButtonComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(StandaloneFancyButtonComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(StandaloneFancyButtonComponent, config);
cy.mount(StandaloneFancyButtonComponent,);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with simple components 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
const config: MountConfig<SomethingOneComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingOneComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingOneComponent, config);
cy.mount(SomethingOneComponent,);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with simple components 2`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
const config: MountConfig<SomethingTwoComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingTwoComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingTwoComponent, config);
cy.mount(SomethingTwoComponent,);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with simple components 3`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
const config: MountConfig<SomethingThreeComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingThreeComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingThreeComponent, config);
cy.mount(SomethingThreeComponent,);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with standalone component 1`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
const config: MountConfig<SomethingOneComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingOneComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingOneComponent, config);
cy.mount(SomethingOneComponent,);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with standalone component 2`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
const config: MountConfig<SomethingTwoComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingTwoComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingTwoComponent, config);
cy.mount(SomethingTwoComponent,);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with standalone component 3`] = `
"import { MountConfig } from 'cypress/angular';
"import { TestBed } from '@angular/core/testing';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
const config: MountConfig<SomethingThreeComponent> = {
declarations: [],
imports: [],
providers: []
}
beforeEach(() => {
TestBed.overrideComponent(SomethingThreeComponent, {
add: {
imports: [],
providers: []
}
})
})
it('renders', () => {
cy.mount(SomethingThreeComponent, config);
cy.mount(SomethingThreeComponent,);
})
})
"
`;

View File

@ -29,6 +29,12 @@
"version": "15.0.0-beta.4",
"description": "Update to using cy.mount in the commands.ts file instead of importing mount for each component test file",
"factory": "./src/migrations/update-15-0-0/update-cy-mount-usage"
},
"update-to-cypress-11": {
"cli": "nx",
"version": "15.1.0-beta.0",
"description": "Update to Cypress v11. This migration will only update if the workspace is already on v10. https://www.cypress.io/blog/2022/11/04/upcoming-changes-to-component-testing/",
"factory": "./src/migrations/update-15-1-0/cypress-11"
}
},
"packageJsonUpdates": {}

View File

@ -53,7 +53,7 @@
"webpack-node-externals": "^3.0.0"
},
"peerDependencies": {
"cypress": ">= 3 < 11"
"cypress": ">= 3 < 12"
},
"peerDependenciesMeta": {
"cypress": {

View File

@ -0,0 +1,178 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Cypress 11 Migration should migrate to v11 1`] = `
"
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
})
describe('again', () => {
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
})
})"
`;
exports[`Cypress 11 Migration should migrate to v11 2`] = `
"/** TODO: mountHook is deprecate.
* Use a wrapper component instead.
* See post for details: https://www.cypress.io/blog/2022/11/04/upcoming-changes-to-component-testing/#reactmounthook-removed
* */
import { mountHook, getContainerEl } from 'cypress/react18'
import ReactDom from 'react-dom'
import { useCounter } from ./useCounter
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
describe('blah', () => {
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
})
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
describe('again', () => {
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
})"
`;
exports[`Cypress 11 Migration should migrate to v11 3`] = `
"/** TODO: mountHook is deprecate.
* Use a wrapper component instead.
* See post for details: https://www.cypress.io/blog/2022/11/04/upcoming-changes-to-component-testing/#reactmounthook-removed
* */
import { mountHook, getContainerEl } from 'cypress/react'
import ReactDom from 'react-dom'
import { useCounter } from ./useCounter
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
describe('blah', () => {
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
})
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
describe('again', () => {
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
})"
`;
exports[`Cypress 11 Migration should migrate to v11 4`] = `
"import {TestBed} from '@angular/core/testing;
import { MyComponent } from './my.component';
describe('MyComponent', () => {
const config = {
imports: [],
declarations: [],
providers: [{provide: 'foo', useValue: 'bar'}]
};
it('direct usage', () => {
TestBed.overrideComponent(MyComponent, {add: { providers: config.providers}});
cy.mount(MyComponent, config);
});
it('spread usage', () => {
TestBed.overrideComponent(MyComponent, { add: { providers: [{provide: 'foo', useValue: 'bar'}] }});
cy.mount(MyComponent, {...config, providers: undefined });
});
it('inlined usage', () => {
TestBed.overrideComponent(MyComponent, { add: { providers: [{provide: 'foo', useValue: 'bar'}] }});
cy.mount(MyComponent, {imports: [], declarations: [], providers: undefined});
});
"
`;
exports[`Cypress 11 Migration should migrate to v11 5`] = `
"import { MountConfig } from 'cypress/angular';
import { MyComponent } from './my.component';
import {TestBed} from '@angular/core/testing';
describe('MyComponent', () => {
const config: MountConfig = {
imports: [],
declarations: [],
providers: [{provide: 'foo', useValue: 'bar'}]
};
it('direct usage', () => {
TestBed.overrideComponent(MyComponent, {add: { providers: config.providers}});
cy.mount(MyComponent, config);
});
it('spread usage', () => {
TestBed.overrideComponent(MyComponent, { add: { providers: [{provide: 'foo', useValue: 'bar'}] }});
cy.mount(MyComponent, {...config, providers: undefined });
});
it('inlined usage', () => {
TestBed.overrideComponent(MyComponent, { add: { providers: [{provide: 'foo', useValue: 'bar'}] }});
cy.mount(MyComponent, {imports: [], declarations: [], providers: undefined});
});
"
`;

View File

@ -0,0 +1,325 @@
import { addProjectConfiguration, Tree } from '@nrwl/devkit';
import { libraryGenerator } from '@nrwl/workspace';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import updateToCypress11 from './cypress-11';
import { installedCypressVersion } from '../../utils/cypress-version';
jest.mock('../../utils/cypress-version');
import { cypressComponentProject } from '../../generators/cypress-component-project/cypress-component-project';
describe('Cypress 11 Migration', () => {
let tree: Tree;
let mockInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
jest.resetAllMocks();
});
it('should not update if cypress <v10 is used', async () => {
// setup called the component setup. mock to v10 so it doesn't throw.
mockInstalledCypressVersion.mockReturnValue(10);
await setup(tree);
mockInstalledCypressVersion.mockReturnValue(9);
const beforeReact = tree.read(
'libs/my-react-lib/src/lib/no-import.cy.ts',
'utf-8'
);
const beforeNg = tree.read(
'libs/my-ng-lib/src/lib/no-import.component.cy.ts',
'utf-8'
);
await updateToCypress11(tree);
const actualReact = tree.read(
'libs/my-react-lib/src/lib/no-import.cy.ts',
'utf-8'
);
const actualNg = tree.read(
'libs/my-ng-lib/src/lib/no-import.component.cy.ts',
'utf-8'
);
expect(actualReact).toEqual(beforeReact);
expect(actualNg).toEqual(beforeNg);
});
it('should migrate to v11', async () => {
mockInstalledCypressVersion.mockReturnValue(10);
await setup(tree);
await updateToCypress11(tree);
expect(
tree.read('libs/my-react-lib/src/lib/no-import.cy.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read(
'libs/my-react-lib/src/lib/with-import-18.component.cy.ts',
'utf-8'
)
).toMatchSnapshot();
expect(
tree.read(
'libs/my-react-lib/src/lib/with-import.component.cy.ts',
'utf-8'
)
).toMatchSnapshot();
expect(
tree.read('libs/my-ng-lib/src/lib/no-import.component.cy.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/my-ng-lib/src/lib/with-import.component.cy.ts', 'utf-8')
).toMatchSnapshot();
});
it('should only update component projects', async () => {
addProjectConfiguration(tree, 'my-e2e-app', {
projectType: 'application',
root: 'apps/my-e2e-app',
sourceRoot: 'apps/my-e2e-app/src',
targets: {
e2e: {
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'apps/my-e2e-app/cypress.config.ts',
},
},
},
});
const content = `import {MountConfig} from 'cypress/angular';
import { MyComponent } from './my.component';
describe('MyComponent', () => {
const config:MountConfig = {
imports: [],
declarations: [],
providers: [{provide: 'foo', useValue: 'bar'}]
};
it('direct usage', () => {
cy.mount(MyComponent, config);
});
`;
tree.write('apps/my-e2e-app/src/somthing.component.cy.ts', content);
await updateToCypress11(tree);
expect(
tree.read('apps/my-e2e-app/src/somthing.component.cy.ts', 'utf-8')
).toEqual(content);
});
});
async function setup(tree: Tree) {
await libraryGenerator(tree, {
name: 'my-react-lib',
});
await cypressComponentProject(tree, {
project: 'my-react-lib',
skipFormat: true,
});
tree.write(
'libs/my-react-lib/cypress/support/commands.ts',
`/// <reference types="cypress" />
import { mount } from 'cypress/react18'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
mount: typeof mount;
}
}
}
Cypress.Commands.add('mount', mount)
`
);
tree.write(
'libs/my-react-lib/src/lib/no-import.cy.ts',
`
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
})
describe('again', () => {
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
})
})`
);
tree.write(
'libs/my-react-lib/src/lib/with-import.component.cy.ts',
`import { mountHook, unmount } from 'cypress/react'
import { useCounter } from ./useCounter
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
describe('blah', () => {
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
})
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
unmount()
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
describe('again', () => {
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
unmount()
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
})`
);
tree.write(
'libs/my-react-lib/src/lib/with-import-18.component.cy.ts',
`import { mountHook, unmount } from 'cypress/react18'
import { useCounter } from ./useCounter
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
describe('blah', () => {
it('increments the count', () => {
mountHook(() => useCounter()).then((result) => {
expect(result.current.count).to.equal(0)
result.current.increment()
expect(result.current.count).to.equal(1)
result.current.increment()
expect(result.current.count).to.equal(2)
})
})
})
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
unmount()
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
describe('again', () => {
it('calls the prop', () => {
cy.mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
unmount()
cy.contains('My component').should('not.exist')
cy.get('@onUnmount').should('have.been.calledOnce')
})
})`
);
await libraryGenerator(tree, {
name: 'my-ng-lib',
});
await cypressComponentProject(tree, {
project: 'my-ng-lib',
skipFormat: true,
});
tree.write(
'libs/my-ng-lib/cypress/support/commands.ts',
`/// <reference types="cypress" />
import { mount } from 'cypress/angular'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
mount: typeof mount;
}
}
}
Cypress.Commands.add('mount', mount)
`
);
tree.write(
'libs/my-ng-lib/src/lib/with-import.component.cy.ts',
`import { MountConfig } from 'cypress/angular';
import { MyComponent } from './my.component';
import {TestBed} from '@angular/core/testing';
describe('MyComponent', () => {
const config: MountConfig = {
imports: [],
declarations: [],
providers: [{provide: 'foo', useValue: 'bar'}]
};
it('direct usage', () => {
cy.mount(MyComponent, config);
});
it('spread usage', () => {
cy.mount(MyComponent, {...config, providers: [{provide: 'foo', useValue: 'bar'}] });
});
it('inlined usage', () => {
cy.mount(MyComponent, {imports: [], declarations: [], providers: [{provide: 'foo', useValue: 'bar'}]});
});
`
);
tree.write(
'libs/my-ng-lib/src/lib/no-import.component.cy.ts',
`
import { MyComponent } from './my.component';
describe('MyComponent', () => {
const config = {
imports: [],
declarations: [],
providers: [{provide: 'foo', useValue: 'bar'}]
};
it('direct usage', () => {
cy.mount(MyComponent, config);
});
it('spread usage', () => {
cy.mount(MyComponent, {...config, providers: [{provide: 'foo', useValue: 'bar'}] });
});
it('inlined usage', () => {
cy.mount(MyComponent, {imports: [], declarations: [], providers: [{provide: 'foo', useValue: 'bar'}]});
});
`
);
}

View File

@ -0,0 +1,178 @@
import { CY_FILE_MATCHER } from '../../utils/ct-helpers';
import {
addDependenciesToPackageJson,
formatFiles,
getProjects,
joinPathFragments,
Tree,
visitNotIgnoredFiles,
} from '@nrwl/devkit';
import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils';
import { tsquery } from '@phenomnomnominal/tsquery';
import { extname } from 'path';
import * as ts from 'typescript';
import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl';
import { installedCypressVersion } from '../../utils/cypress-version';
import { cypressVersion } from '../../utils/versions';
export async function updateToCypress11(tree: Tree) {
const installedVersion = installedCypressVersion();
if (installedVersion < 10) {
return;
}
const projects = getProjects(tree);
forEachExecutorOptions<CypressExecutorOptions>(
tree,
'@nrwl/cypress:cypress',
(options, projectName, targetName, configurationName) => {
if (
options.testingType !== 'component' ||
!(options.cypressConfig && tree.exists(options.cypressConfig))
) {
return;
}
const projectConfig = projects.get(projectName);
const commandsFile = joinPathFragments(
projectConfig.root,
'cypress',
'support',
'commands.ts'
);
const framework = getFramework(
tree.exists(commandsFile)
? tree.read(commandsFile, 'utf-8')
: tree.read(options.cypressConfig, 'utf-8')
);
visitNotIgnoredFiles(tree, projectConfig.sourceRoot, (filePath) => {
if (!CY_FILE_MATCHER.test(filePath)) {
return;
}
const frameworkFromFile = getFramework(tree.read(filePath, 'utf-8'));
if (framework === 'react' || frameworkFromFile === 'react') {
updateUnmountUsage(tree, filePath);
updateMountHookUsage(tree, filePath);
}
if (framework === 'angular' || frameworkFromFile === 'angular') {
updateProviderUsage(tree, filePath);
}
});
}
);
const installTask = addDependenciesToPackageJson(
tree,
{},
{ cypress: cypressVersion }
);
await formatFiles(tree);
return () => {
installTask();
};
}
export function updateMountHookUsage(tree: Tree, filePath: string) {
const originalContents = tree.read(filePath, 'utf-8');
const commentedMountHook = tsquery.replace(
originalContents,
':matches(ImportDeclaration, VariableStatement):has(Identifier[name="mountHook"]):has(StringLiteral[value="cypress/react"], StringLiteral[value="cypress/react18"])',
(node) => {
return `/** TODO: mountHook is deprecate.
* Use a wrapper component instead.
* See post for details: https://www.cypress.io/blog/2022/11/04/upcoming-changes-to-component-testing/#reactmounthook-removed
* */\n${node.getText()}`;
}
);
tree.write(filePath, commentedMountHook);
}
export function updateUnmountUsage(tree: Tree, filePath: string) {
const reactDomImport = extname(filePath).includes('ts')
? `import ReactDom from 'react-dom'`
: `const ReactDom = require('react-dom')`;
const originalContents = tree.read(filePath, 'utf-8');
const updatedImports = tsquery.replace(
originalContents,
':matches(ImportDeclaration, VariableStatement):has(Identifier[name="unmount"]):has(StringLiteral[value="cypress/react"], StringLiteral[value="cypress/react18"])',
(node) => {
return `${node.getText().replace('unmount', 'getContainerEl')}
${reactDomImport}`;
}
);
const updatedUnmountApi = tsquery.replace(
updatedImports,
'ExpressionStatement > CallExpression:has(Identifier[name="unmount"])',
(node: ts.ExpressionStatement) => {
if (node.expression.getText() === 'unmount') {
return `cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))`;
}
}
);
tree.write(filePath, updatedUnmountApi);
}
export function updateProviderUsage(tree: Tree, filePath: string) {
const originalContents = tree.read(filePath, 'utf-8');
const isTestBedImported =
tsquery.query(
originalContents,
':matches(ImportDeclaration, VariableStatement):has(Identifier[name="TestBed"]):has(StringLiteral[value="@angular/core/testing"])'
)?.length > 0;
let updatedProviders = tsquery.replace(
originalContents,
'CallExpression:has(PropertyAccessExpression:has(Identifier[name="mount"]))',
(node: ts.CallExpression) => {
const expressionName = node.expression.getText();
if (expressionName === 'cy.mount' && node?.arguments?.length > 1) {
const component = node.arguments[0].getText();
if (ts.isObjectLiteralExpression(node.arguments[1])) {
const providers = node.arguments[1]?.properties
?.find((p) => p.name?.getText() === 'providers')
?.getText();
const noProviders = tsquery.replace(
node.getText(),
'PropertyAssignment:has(Identifier[name="providers"])',
(n) => {
// set it to undefined so we don't run into a hanging comma causing invalid syntax
return 'providers: undefined';
}
);
return `TestBed.overrideComponent(${component}, { add: { ${providers} }});\n${noProviders}`;
} else {
return `TestBed.overrideComponent(${component}, {add: { providers: ${node.arguments[1].getText()}.providers}});\n${node.getText()}`;
}
}
}
);
tree.write(
filePath,
`${
isTestBedImported ? '' : "import {TestBed} from '@angular/core/testing;\n"
}${updatedProviders}`
);
}
function getFramework(contents: string): 'react' | 'angular' | null {
if (contents.includes('cypress/react') || contents.includes('@nrwl/react')) {
return 'react';
}
if (
contents.includes('cypress/angular') ||
contents.includes('@nrwl/angular')
) {
return 'angular';
}
return null;
}
export default updateToCypress11;

View File

@ -1,6 +1,6 @@
export const nxVersion = require('../../package.json').version;
export const eslintPluginCypressVersion = '^2.10.3';
export const typesNodeVersion = '16.11.7';
export const cypressVersion = '^10.7.0';
export const cypressVersion = '^11.0.0';
export const cypressWebpackVersion = '^2.0.0';
export const webpackHttpPluginVersion = '^5.5.0';

View File

@ -41,6 +41,10 @@ function check() {
'packages/workspace/src/tasks-runner/task-orchestrator.ts',
'packages/nest/src/generators/init/lib/add-dependencies.ts',
'packages/nest/src/migrations/update-13-2-0/update-to-nest-8.ts',
// cypress v11 migration checks if the TestBed is imported by looking for the import
// 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.ts',
];
const files = [