fix(testing): move to using cy.mount (#12273)

This commit is contained in:
Caleb Ukle 2022-10-12 15:50:41 -05:00 committed by GitHub
parent c2dcfcf127
commit 80635dbae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 902 additions and 78 deletions

View File

@ -175,7 +175,7 @@ import {CommonModule} from '@angular/common';
createFile(
`libs/${buildableLibName}/src/lib/input/input.component.cy.ts`,
`
import { MountConfig, mount } from 'cypress/angular';
import { MountConfig } from 'cypress/angular';
import { InputComponent } from './input.component';
describe(InputComponent.name, () => {
@ -186,12 +186,12 @@ describe(InputComponent.name, () => {
};
it('renders', () => {
mount(InputComponent, config);
cy.mount(InputComponent, config);
// make sure tailwind isn't getting applied
cy.get('label').should('have.css', 'color', 'rgb(0, 0, 0)');
});
it('should be readonly', () => {
mount(InputComponent, {
cy.mount(InputComponent, {
...config,
componentProperties: {
readOnly: true,
@ -206,7 +206,7 @@ describe(InputComponent.name, () => {
createFile(
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
`
import { MountConfig, mount } from 'cypress/angular';
import { MountConfig } from 'cypress/angular';
import { InputStandaloneComponent } from './input-standalone.component';
describe(InputStandaloneComponent.name, () => {
@ -217,12 +217,12 @@ describe(InputStandaloneComponent.name, () => {
};
it('renders', () => {
mount(InputStandaloneComponent, config);
cy.mount(InputStandaloneComponent, config);
// make sure tailwind isn't getting applied
cy.get('label').should('have.css', 'color', 'rgb(0, 0, 0)');
});
it('should be readonly', () => {
mount(InputStandaloneComponent, {
cy.mount(InputStandaloneComponent, {
...config,
componentProperties: {
readOnly: true,

View File

@ -144,17 +144,16 @@ export default Input;
`libs/${buildableLibName}/src/lib/input/input.cy.tsx`,
`
import * as React from 'react'
import { mount } from 'cypress/react'
import Input from './input'
describe(Input.name, () => {
it('renders', () => {
mount(<Input readOnly={false} />)
cy.mount(<Input readOnly={false} />)
cy.get('label').should('have.css', 'color', 'rgb(0, 0, 0)');
})
it('should be read only', () => {
mount(<Input readOnly={true}/>)
cy.mount(<Input readOnly={true}/>)
cy.get('input').should('have.attr', 'readonly');
})
});

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Angular Cypress Component Test Generator should generate a component test 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
@ -12,7 +12,7 @@ describe(MyLibComponent.name, () => {
}
it('renders', () => {
mount(MyLibComponent, {
cy.mount(MyLibComponent, {
...config,
componentProperties: {
type: 'button',
@ -27,7 +27,7 @@ describe(MyLibComponent.name, () => {
`;
exports[`Angular Cypress Component Test Generator should handle component w/o inputs 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
@ -38,14 +38,14 @@ describe(MyLibComponent.name, () => {
}
it('renders', () => {
mount(MyLibComponent, config);
cy.mount(MyLibComponent, config);
})
})
"
`;
exports[`Angular Cypress Component Test Generator should work with standalone components 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
@ -56,7 +56,7 @@ describe(MyLibComponent.name, () => {
}
it('renders', () => {
mount(MyLibComponent, {
cy.mount(MyLibComponent, {
...config,
componentProperties: {
type: 'button',

View File

@ -177,7 +177,7 @@ export class MyLibComponent implements OnInit {
await componentGenerator(tree, { name: 'my-lib', project: 'my-lib' });
const expected = `import { MountConfig, mount } from 'cypress/angular';
const expected = `import { MountConfig } from 'cypress/angular';
import { MyLibComponent } from './my-lib.component';
describe(MyLibComponent.name, () => {
@ -188,7 +188,7 @@ describe(MyLibComponent.name, () => {
}
it('renders', () => {
mount(MyLibComponent, config);
cy.mount(MyLibComponent, config);
})
})
`;

View File

@ -1,4 +1,4 @@
import { MountConfig, mount } from 'cypress/angular';
import { MountConfig } from 'cypress/angular';
import { <%= componentName %> } from './<%= componentFileName %>';
describe(<%= componentName %>.name, () => {
@ -9,7 +9,7 @@ describe(<%= componentName %>.name, () => {
}
it('renders', () => {
mount(<%= componentName %>,<% if(props.length > 0) { %> {
cy.mount(<%= componentName %>,<% if(props.length > 0) { %> {
...config,
componentProperties: {<% for (let prop of props) { %>
<%= prop.name %>: <%- prop.defaultValue %>,<% } %>

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Cypress Component Testing Configuration should work with complex component 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
@ -12,7 +12,7 @@ describe(SomethingOneComponent.name, () => {
}
it('renders', () => {
mount(SomethingOneComponent, {
cy.mount(SomethingOneComponent, {
...config,
componentProperties: {
type: 'button',
@ -27,7 +27,7 @@ describe(SomethingOneComponent.name, () => {
`;
exports[`Cypress Component Testing Configuration should work with complex component 2`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
@ -38,7 +38,7 @@ describe(SomethingTwoComponent.name, () => {
}
it('renders', () => {
mount(SomethingTwoComponent, {
cy.mount(SomethingTwoComponent, {
...config,
componentProperties: {
type: 'button',
@ -53,7 +53,7 @@ describe(SomethingTwoComponent.name, () => {
`;
exports[`Cypress Component Testing Configuration should work with complex component 3`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
@ -64,7 +64,7 @@ describe(SomethingThreeComponent.name, () => {
}
it('renders', () => {
mount(SomethingThreeComponent, {
cy.mount(SomethingThreeComponent, {
...config,
componentProperties: {
type: 'button',
@ -79,7 +79,7 @@ describe(SomethingThreeComponent.name, () => {
`;
exports[`Cypress Component Testing Configuration should work with complex standalone component 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
@ -90,7 +90,7 @@ describe(SomethingOneComponent.name, () => {
}
it('renders', () => {
mount(SomethingOneComponent, {
cy.mount(SomethingOneComponent, {
...config,
componentProperties: {
type: 'button',
@ -105,7 +105,7 @@ describe(SomethingOneComponent.name, () => {
`;
exports[`Cypress Component Testing Configuration should work with complex standalone component 2`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
@ -116,7 +116,7 @@ describe(SomethingTwoComponent.name, () => {
}
it('renders', () => {
mount(SomethingTwoComponent, {
cy.mount(SomethingTwoComponent, {
...config,
componentProperties: {
type: 'button',
@ -131,7 +131,7 @@ describe(SomethingTwoComponent.name, () => {
`;
exports[`Cypress Component Testing Configuration should work with complex standalone component 3`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
@ -142,7 +142,7 @@ describe(SomethingThreeComponent.name, () => {
}
it('renders', () => {
mount(SomethingThreeComponent, {
cy.mount(SomethingThreeComponent, {
...config,
componentProperties: {
type: 'button',
@ -157,7 +157,7 @@ describe(SomethingThreeComponent.name, () => {
`;
exports[`Cypress Component Testing Configuration should work with secondary entry point libs 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { FancyButtonComponent } from './fancy-button.component';
describe(FancyButtonComponent.name, () => {
@ -168,14 +168,14 @@ describe(FancyButtonComponent.name, () => {
}
it('renders', () => {
mount(FancyButtonComponent, config);
cy.mount(FancyButtonComponent, config);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with secondary entry point libs 2`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { StandaloneFancyButtonComponent } from './standalone-fancy-button.component';
describe(StandaloneFancyButtonComponent.name, () => {
@ -186,14 +186,14 @@ describe(StandaloneFancyButtonComponent.name, () => {
}
it('renders', () => {
mount(StandaloneFancyButtonComponent, config);
cy.mount(StandaloneFancyButtonComponent, config);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with simple components 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
@ -204,14 +204,14 @@ describe(SomethingOneComponent.name, () => {
}
it('renders', () => {
mount(SomethingOneComponent, config);
cy.mount(SomethingOneComponent, config);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with simple components 2`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
@ -222,14 +222,14 @@ describe(SomethingTwoComponent.name, () => {
}
it('renders', () => {
mount(SomethingTwoComponent, config);
cy.mount(SomethingTwoComponent, config);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with simple components 3`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
@ -240,14 +240,14 @@ describe(SomethingThreeComponent.name, () => {
}
it('renders', () => {
mount(SomethingThreeComponent, config);
cy.mount(SomethingThreeComponent, config);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with standalone component 1`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingOneComponent } from './something-one.component';
describe(SomethingOneComponent.name, () => {
@ -258,14 +258,14 @@ describe(SomethingOneComponent.name, () => {
}
it('renders', () => {
mount(SomethingOneComponent, config);
cy.mount(SomethingOneComponent, config);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with standalone component 2`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingTwoComponent } from './something-two.component';
describe(SomethingTwoComponent.name, () => {
@ -276,14 +276,14 @@ describe(SomethingTwoComponent.name, () => {
}
it('renders', () => {
mount(SomethingTwoComponent, config);
cy.mount(SomethingTwoComponent, config);
})
})
"
`;
exports[`Cypress Component Testing Configuration should work with standalone component 3`] = `
"import { MountConfig, mount } from 'cypress/angular';
"import { MountConfig } from 'cypress/angular';
import { SomethingThreeComponent } from './something-three.component';
describe(SomethingThreeComponent.name, () => {
@ -294,7 +294,7 @@ describe(SomethingThreeComponent.name, () => {
}
it('renders', () => {
mount(SomethingThreeComponent, config);
cy.mount(SomethingThreeComponent, config);
})
})
"

View File

@ -0,0 +1,42 @@
/// <reference types="cypress" />
import { mount } from 'cypress/angular'
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
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)
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@ -48,6 +48,12 @@
"version": "15.0.0-beta.0",
"description": "Stop hashing cypress spec files and config files for build targets and dependent tasks",
"factory": "./src/migrations/update-15-0-0/add-cypress-inputs"
},
"update-cy-mount-usage": {
"cli": "nx",
"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"
}
},
"packageJsonUpdates": {

View File

@ -0,0 +1,227 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`update cy.mount usage should add the mount command 1`] = `
"import { mount } from 'cypress/angular'
/// <reference types=\\"cypress\\" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
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;
}
}
}
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add(\\"drag\\", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add(\\"dismiss\\", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite(\\"visit\\", (originalFn, url, options) => { ... })
Cypress.Commands.add('mount', mount);"
`;
exports[`update cy.mount usage should update angular ct test file 1`] = `
"
import { MountConfig } from 'cypress/angular';
describe('MyComponent', () => {
it('should work', () => {
cy.mount(MyComponent);
});
it('should work with config', () => {
cy.mount(MyComponent, {...config, componentProperties: {foo: 'bar'}});
});
});
"
`;
exports[`update cy.mount usage should update angular react test file 1`] = `
"
describe('MyComponent', () => {
it('should work', () => {
cy.mount(<MyComponent />);
});
it('should work with config', () => {
cy.mount(<MyComponent title={\\"blah\\"}/>,);
});
});
"
`;
exports[`update cy.mount usage should update angular react18 test file 1`] = `
"
describe('MyComponent', () => {
it('should work', () => {
cy.mount(<MyComponent />);
});
it('should work with config', () => {
cy.mount(<MyComponent title={\\"blah\\"}/>);
});
});
"
`;
exports[`update cy.mount usage should work 1`] = `
"import { mount } from 'cypress/angular'
// eslint-disable-next-line @typescript-eslint/no-namespace
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;
}
}
}
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
Cypress.Commands.add('mount', mount);"
`;
exports[`update cy.mount usage should work 2`] = `
"import { MountConfig } from 'cypress/angular';
describe('MyComponent', () => {
it('should work', () => {
cy.mount(<MyComponent />);
});
it('should work with config', () => {
cy.mount(<MyComponent title={\\"blah\\"}/>,);
});
});
"
`;
exports[`update cy.mount usage should work 3`] = `
"import { MountConfig } from 'cypress/angular';
describe('MyComponent', () => {
it('should work', () => {
cy.mount(<MyComponent />);
});
it('should work with config', () => {
cy.mount(<MyComponent title={\\"blah\\"}/>,);
});
});
"
`;
exports[`update cy.mount usage should work 4`] = `
"import { MountConfig } from 'cypress/angular';
describe('MyComponent', () => {
it('should work', () => {
cy.mount(MyComponent);
});
it('should work with config', () => {
cy.mount(MyComponent, {...config, componentProperties: {foo: 'bar'}});
});
});
"
`;
exports[`update cy.mount usage should work 5`] = `
"
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
mount(c: any): void;
}
}
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
Cypress.Commands.add('mount', (any) => {
console.log(mount);
});
"
`;
exports[`update cy.mount usage should work 6`] = `
"
describe('MyComponent', () => {
it('should work', () => {
cy.mount(<MyComponent />);
});
it('should work with config', () => {
cy.mount(<MyComponent title={\\"blah\\"}/>,);
});
});
"
`;
exports[`update cy.mount usage should work 7`] = `
"
describe('MyComponent', () => {
it('should work', () => {
cy.mount(<MyComponent />);
});
it('should work with config', () => {
cy.mount(<MyComponent title={\\"blah\\"}/>,);
});
});
"
`;
exports[`update cy.mount usage should work 8`] = `
"
describe('MyComponent', () => {
it('should work', () => {
cy.mount(MyComponent);
});
it('should work with config', () => {
cy.mount(MyComponent, {...config, componentProperties: {foo: 'bar'}});
});
});
"
`;

View File

@ -0,0 +1,325 @@
import { installedCypressVersion } from '../../utils/cypress-version';
import {
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import {
addMountCommand,
updateCyFile,
updateCyMountUsage,
} from './update-cy-mount-usage';
import { libraryGenerator } from '@nrwl/workspace';
import { cypressComponentProject } from '../../generators/cypress-component-project/cypress-component-project';
jest.mock('../../utils/cypress-version');
describe('update cy.mount usage', () => {
let tree: Tree;
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
mockedInstalledCypressVersion.mockReturnValue(10);
});
it('should work', async () => {
await setup(tree);
await updateCyMountUsage(tree);
expect(
tree.read('libs/my-lib/cypress/support/commands.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/my-lib/src/lib/my-cmp-one.cy.js', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/my-lib/src/lib/my-cmp-two.cy.tsx', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/my-lib/src/lib/my-cmp-three.cy.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/another-lib/cypress/support/commands.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/another-lib/src/lib/my-cmp-one.cy.js', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/another-lib/src/lib/my-cmp-two.cy.tsx', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('libs/another-lib/src/lib/my-cmp-three.cy.ts', 'utf-8')
).toMatchSnapshot();
});
it('should add the mount command', async () => {
tree.write(
'apps/my-app/cypress/support/commands.ts',
`/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
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;
}
}
}
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
`
);
addMountCommand(tree, 'apps/my-app', 'angular');
expect(
tree.read('apps/my-app/cypress/support/commands.ts', 'utf-8')
).toMatchSnapshot();
});
it('should update angular ct test file', () => {
tree.write(
'my-file.cy.ts',
`
import { MountConfig, mount } from 'cypress/angular';
describe('MyComponent', () => {
it('should work', () => {
mount(MyComponent);
});
it('should work with config', () => {
mount(MyComponent, {...config, componentProperties: {foo: 'bar'}});
});
});
`
);
updateCyFile(tree, 'my-file.cy.ts', 'angular');
expect(tree.read('my-file.cy.ts', 'utf-8')).toMatchSnapshot();
});
it('should update angular react18 test file', () => {
tree.write(
'my-file.cy.ts',
`
import { mount } from 'cypress/react18';
describe('MyComponent', () => {
it('should work', () => {
mount(<MyComponent />);
});
it('should work with config', () => {
mount(<MyComponent title={"blah"}/>);
});
});
`
);
updateCyFile(tree, 'my-file.cy.ts', 'react18');
expect(tree.read('my-file.cy.ts', 'utf-8')).toMatchSnapshot();
});
it('should update angular react test file', () => {
tree.write(
'my-file.cy.ts',
`
import { mount } from 'cypress/react';
describe('MyComponent', () => {
it('should work', () => {
mount(<MyComponent />);
});
it('should work with config', () => {
mount(<MyComponent title={"blah"}/>,);
});
});
`
);
updateCyFile(tree, 'my-file.cy.ts', 'react');
expect(tree.read('my-file.cy.ts', 'utf-8')).toMatchSnapshot();
});
});
async function setup(tree: Tree) {
await libraryGenerator(tree, { name: 'my-lib' });
await libraryGenerator(tree, { name: 'another-lib' });
await cypressComponentProject(tree, {
project: 'my-lib',
skipFormat: false,
});
await cypressComponentProject(tree, {
project: 'another-lib',
skipFormat: false,
});
const myLib = readProjectConfiguration(tree, 'my-lib');
myLib.targets['build'] = {
executor: '@nrwl/angular:webpack-browser',
options: {},
};
myLib.targets['component-test'].options.devServerTarget = 'my-lib:build';
updateProjectConfiguration(tree, 'my-lib', myLib);
const anotherLib = readProjectConfiguration(tree, 'another-lib');
anotherLib.targets['build'] = {
executor: '@nrwl/webpack:webpack',
options: {},
};
anotherLib.targets['component-test'].options.devServerTarget =
'another-lib:build';
updateProjectConfiguration(tree, 'another-lib', anotherLib);
tree.write(
'libs/my-lib/cypress/support/commands.ts',
`
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
}
}
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});`
);
tree.write(
'libs/my-lib/src/lib/my-cmp-one.cy.js',
`const { mount } =require('cypress/react');
describe('MyComponent', () => {
it('should work', () => {
mount(<MyComponent />);
});
it('should work with config', () => {
mount(<MyComponent title={"blah"}/>,);
});
});
`
);
tree.write(
'libs/my-lib/src/lib/my-cmp-two.cy.tsx',
`import { mount } from 'cypress/react18';
describe('MyComponent', () => {
it('should work', () => {
mount(<MyComponent />);
});
it('should work with config', () => {
mount(<MyComponent title={"blah"}/>,);
});
});
`
);
tree.write(
'libs/my-lib/src/lib/my-cmp-three.cy.ts',
`import { mount, MountConfig } from 'cypress/angular';
describe('MyComponent', () => {
it('should work', () => {
mount(MyComponent);
});
it('should work with config', () => {
mount(MyComponent, {...config, componentProperties: {foo: 'bar'}});
});
});
`
);
tree.write(
'libs/another-lib/cypress/support/commands.ts',
`
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
mount(c: any): void;
}
}
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
Cypress.Commands.add('mount', (any) => {
console.log(mount);
});
`
);
tree.write(
'libs/another-lib/src/lib/my-cmp-one.cy.js',
`const { mount } = require('cypress/react');
describe('MyComponent', () => {
it('should work', () => {
mount(<MyComponent />);
});
it('should work with config', () => {
mount(<MyComponent title={"blah"}/>,);
});
});
`
);
tree.write(
'libs/another-lib/src/lib/my-cmp-two.cy.tsx',
`import { mount } from 'cypress/react18';
describe('MyComponent', () => {
it('should work', () => {
mount(<MyComponent />);
});
it('should work with config', () => {
mount(<MyComponent title={"blah"}/>,);
});
});
`
);
tree.write(
'libs/another-lib/src/lib/my-cmp-three.cy.ts',
`import { mount, MountConfig } from 'cypress/angular';
describe('MyComponent', () => {
it('should work', () => {
mount(MyComponent);
});
it('should work with config', () => {
mount(MyComponent, {...config, componentProperties: {foo: 'bar'}});
});
});
`
);
}

View File

@ -0,0 +1,195 @@
import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl';
import { CY_FILE_MATCHER } from '../../utils/ct-helpers';
import { installedCypressVersion } from '../../utils/cypress-version';
import {
formatFiles,
getProjects,
joinPathFragments,
parseTargetString,
readJson,
TargetConfiguration,
Tree,
visitNotIgnoredFiles,
} from '@nrwl/devkit';
import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils';
import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils';
import { tsquery } from '@phenomnomnominal/tsquery';
import { gte } from 'semver';
import * as ts from 'typescript';
export async function updateCyMountUsage(tree: Tree) {
if (installedCypressVersion() < 10) {
return;
}
const projects = getProjects(tree);
forEachExecutorOptions<CypressExecutorOptions>(
tree,
'@nrwl/cypress:cypress',
(options, projectName) => {
if (options.testingType !== 'component' || !options.devServerTarget) {
return;
}
const parsed = parseTargetString(options.devServerTarget);
if (!parsed?.project || !parsed?.target) {
return;
}
const buildProjectConfig = projects.get(parsed.project);
const framework = getFramework(
tree,
parsed.configuration
? buildProjectConfig.targets[parsed.target].configurations[
parsed.configuration
]
: buildProjectConfig.targets[parsed.target]
);
const ctProjectConfig = projects.get(projectName);
addMountCommand(tree, ctProjectConfig.root, framework);
visitNotIgnoredFiles(tree, ctProjectConfig.sourceRoot, (filePath) => {
if (CY_FILE_MATCHER.test(filePath)) {
updateCyFile(tree, filePath, framework);
}
});
}
);
await formatFiles(tree);
}
export function addMountCommand(
tree: Tree,
projectRoot: string,
framework: string
) {
const commandFilePath = joinPathFragments(
projectRoot,
'cypress',
'support',
'commands.ts'
);
if (!tree.exists(commandFilePath)) {
return;
}
const commandFile = tree.read(commandFilePath, 'utf-8');
const mountCommand = tsquery.query<ts.PropertyAccessExpression>(
commandFile,
'CallExpression:has(StringLiteral[value="mount"]) PropertyAccessExpression:has(Identifier[name="add"])'
);
if (mountCommand?.length > 0) {
return;
}
const existingCommands = tsquery.query<
ts.MethodSignature | ts.PropertySignature
>(
commandFile,
'InterfaceDeclaration:has(Identifier[name="Chainable"]) > MethodSignature, InterfaceDeclaration:has(Identifier[name="Chainable"]) > PropertySignature'
);
const isGlobalDeclaration = tsquery.query<ts.ModuleDeclaration>(
commandFile,
'ModuleDeclaration > Identifier[name="global"]'
);
const updatedInterface = tsquery.replace(
commandFile,
'ModuleDeclaration:has(Identifier[name="Cypress"])',
(node: ts.ModuleDeclaration) => {
const newModuleDelcaration = `declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
${existingCommands.map((c) => c.getText()).join('\n')}
mount: typeof mount;
}
}
}`;
/*
* this is to prevent the change being applied twice since
* declare global { 1
* interface Cypress { 2
* }
* }
* matches twice.
* i.e. if there is no global declaration, then add it
* or if the node is the global declaration, then add it,
* but not to the cypress module declaration inside the global declaration
*/
if (
isGlobalDeclaration?.length === 0 ||
node.name.getText() === 'global'
) {
return newModuleDelcaration;
}
}
);
const updatedCommandFile = `import { mount } from 'cypress/${framework}'\n${updatedInterface}\nCypress.Commands.add('mount', mount);`;
tree.write(commandFilePath, updatedCommandFile);
}
function getFramework(
tree: Tree,
target: TargetConfiguration
): 'angular' | 'react' | 'react18' {
if (
target.executor === '@nrwl/angular:webpack-browser' ||
target.executor === '@angular-devkit/build-angular:browser'
) {
return 'angular';
}
const pkgJson = readJson(tree, 'package.json');
const reactDomVersion = pkgJson?.dependencies?.['react-dom'];
const hasReact18 =
reactDomVersion &&
gte(checkAndCleanWithSemver('react-dom', reactDomVersion), '18.0.0');
if (hasReact18) {
return 'react18';
}
return 'react';
}
export function updateCyFile(
tree: Tree,
filePath: string,
framework: 'angular' | 'react' | 'react18'
) {
if (!tree.exists(filePath)) {
return;
}
const contents = tree.read(filePath, 'utf-8');
const withCyMount = tsquery.replace(
contents,
':matches(CallExpression>Identifier[name="mount"])',
(node: ts.CallExpression) => {
return `cy.mount`;
}
);
const withUpdatedImports = tsquery.replace(
withCyMount,
':matches(ImportDeclaration, VariableStatement):has(Identifier[name="mount"]):has(StringLiteral[value="cypress/react"], StringLiteral[value="cypress/angular"], StringLiteral[value="cypress/react18"])',
(node: ts.ImportDeclaration) => {
switch (framework) {
case 'angular':
return `import { MountConfig } from 'cypress/angular';`;
case 'react18':
case 'react':
return ' '; // have to return non falsy string to remove the node
default:
return node.getText().replace('mount', '');
}
}
);
tree.write(filePath, withUpdatedImports);
}
export default updateCyMountUsage;

View File

@ -2,6 +2,7 @@ import type { ExecutorContext } from 'nx/src/config/misc-interfaces';
import { ProjectGraph } from 'nx/src/config/project-graph';
import { join } from 'path';
export const CY_FILE_MATCHER = new RegExp(/\.cy\.[tj]sx?$/);
/**
* return a path to a temp css file
* temp file is scoped to the project root

View File

@ -2,7 +2,6 @@
exports[`componentTestGenerator multiple components per file should handle default export 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import AnotherCmp, { AnotherCmpProps, AnotherCmp2 } from './some-lib'
@ -19,13 +18,13 @@ describe(AnotherCmp.name, () => {
})
it('renders', () => {
mount(<AnotherCmp {...props}/>)
cy.mount(<AnotherCmp {...props}/>)
})
})
describe(AnotherCmp2.name, () => {
it('renders', () => {
mount(<AnotherCmp2 />)
cy.mount(<AnotherCmp2 />)
})
})
@ -34,7 +33,6 @@ describe(AnotherCmp2.name, () => {
exports[`componentTestGenerator multiple components per file should handle named exports 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import { AnotherCmpProps, AnotherCmp, AnotherCmp2 } from './some-lib'
@ -51,13 +49,13 @@ describe(AnotherCmp.name, () => {
})
it('renders', () => {
mount(<AnotherCmp {...props}/>)
cy.mount(<AnotherCmp {...props}/>)
})
})
describe(AnotherCmp2.name, () => {
it('renders', () => {
mount(<AnotherCmp2 />)
cy.mount(<AnotherCmp2 />)
})
})
@ -66,7 +64,6 @@ describe(AnotherCmp2.name, () => {
exports[`componentTestGenerator multiple components per file should handle no props 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import SomeLib, { SomeLibProps, AnotherCmp } from './some-lib'
@ -79,13 +76,13 @@ describe(SomeLib.name, () => {
})
it('renders', () => {
mount(<SomeLib {...props}/>)
cy.mount(<SomeLib {...props}/>)
})
})
describe(AnotherCmp.name, () => {
it('renders', () => {
mount(<AnotherCmp />)
cy.mount(<AnotherCmp />)
})
})
@ -94,7 +91,6 @@ describe(AnotherCmp.name, () => {
exports[`componentTestGenerator multiple components per file should handle props 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import SomeLib, { SomeLibProps, AnotherCmpProps, AnotherCmp } from './some-lib'
@ -107,7 +103,7 @@ describe(SomeLib.name, () => {
})
it('renders', () => {
mount(<SomeLib {...props}/>)
cy.mount(<SomeLib {...props}/>)
})
})
@ -124,7 +120,7 @@ describe(AnotherCmp.name, () => {
})
it('renders', () => {
mount(<AnotherCmp {...props}/>)
cy.mount(<AnotherCmp {...props}/>)
})
})
@ -133,7 +129,6 @@ describe(AnotherCmp.name, () => {
exports[`componentTestGenerator single component per file should handle default export 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import AnotherCmp, { AnotherCmpProps } from './some-lib'
@ -150,7 +145,7 @@ describe(AnotherCmp.name, () => {
})
it('renders', () => {
mount(<AnotherCmp {...props}/>)
cy.mount(<AnotherCmp {...props}/>)
})
})
@ -159,7 +154,6 @@ describe(AnotherCmp.name, () => {
exports[`componentTestGenerator single component per file should handle named exports 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import { AnotherCmpProps, AnotherCmp } from './some-lib'
@ -176,7 +170,7 @@ describe(AnotherCmp.name, () => {
})
it('renders', () => {
mount(<AnotherCmp {...props}/>)
cy.mount(<AnotherCmp {...props}/>)
})
})
@ -185,7 +179,6 @@ describe(AnotherCmp.name, () => {
exports[`componentTestGenerator single component per file should handle no props 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import SomeLib, { SomeLibProps } from './some-lib'
@ -198,7 +191,7 @@ describe(SomeLib.name, () => {
})
it('renders', () => {
mount(<SomeLib {...props}/>)
cy.mount(<SomeLib {...props}/>)
})
})
@ -207,7 +200,6 @@ describe(SomeLib.name, () => {
exports[`componentTestGenerator single component per file should handle props 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import { AnotherCmpProps, AnotherCmp } from './some-lib'
@ -224,7 +216,7 @@ describe(AnotherCmp.name, () => {
})
it('renders', () => {
mount(<AnotherCmp {...props}/>)
cy.mount(<AnotherCmp {...props}/>)
})
})

View File

@ -1,5 +1,4 @@
import * as React from 'react'
import { mount } from 'cypress/react'
<%- importStatement %>
<% for (let cmp of components) { %>
@ -13,10 +12,10 @@ describe(<%= cmp.name %>.name, () => {<% if (cmp.typeName) { %>
})
it('renders', () => {
mount(<<%= cmp.name %> {...props}/>)
cy.mount(<<%= cmp.name %> {...props}/>)
})<% } else { %>
it('renders', () => {
mount(<<%= cmp.name %> />)
cy.mount(<<%= cmp.name %> />)
})<% } %>
})
<% } %>

View File

@ -2,13 +2,12 @@
exports[`React:CypressComponentTestConfiguration should generate tests for existing js components 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import SomeCmp from './some-cmp'
describe(SomeCmp.name, () => {
it('renders', () => {
mount(<SomeCmp />)
cy.mount(<SomeCmp />)
})
})
@ -17,13 +16,12 @@ describe(SomeCmp.name, () => {
exports[`React:CypressComponentTestConfiguration should generate tests for existing js components 2`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import AnotherCmp from './another-cmp'
describe(AnotherCmp.name, () => {
it('renders', () => {
mount(<AnotherCmp />)
cy.mount(<AnotherCmp />)
})
})
@ -32,7 +30,6 @@ describe(AnotherCmp.name, () => {
exports[`React:CypressComponentTestConfiguration should generate tests for existing tsx components 1`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import SomeLib, { SomeLibProps } from './some-lib'
@ -45,7 +42,7 @@ describe(SomeLib.name, () => {
})
it('renders', () => {
mount(<SomeLib {...props}/>)
cy.mount(<SomeLib {...props}/>)
})
})
@ -54,7 +51,6 @@ describe(SomeLib.name, () => {
exports[`React:CypressComponentTestConfiguration should generate tests for existing tsx components 2`] = `
"import * as React from 'react'
import { mount } from 'cypress/react'
import AnotherCmp, { AnotherCmpProps } from './another-cmp'
@ -67,7 +63,7 @@ describe(AnotherCmp.name, () => {
})
it('renders', () => {
mount(<AnotherCmp {...props}/>)
cy.mount(<AnotherCmp {...props}/>)
})
})

View File

@ -0,0 +1,42 @@
/// <reference types="cypress" />
import { mount } from 'cypress/react18'
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
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)
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })