Skip to content

Commit af6be6f

Browse files
w0wka91mike-plummersemantic-release-botZachJW34yusijs
authored
feat: Add Angular CT Schematic (#24374)
* chore: release @cypress/angular-v1.1.0 [skip ci] * chore: release @cypress/schematic-v2.1.0 [skip ci] * chore: release @cypress/mount-utils-v2.1.0 [skip ci] * chore: release @cypress/react-v6.2.0 [skip ci] * chore: release @cypress/react18-v1.1.0 [skip ci] * chore: release @cypress/svelte-v1.0.0 [skip ci] * chore: release @cypress/vue-v4.2.0 [skip ci] * chore: release @cypress/vue2-v1.1.0 [skip ci] * chore: release @cypress/webpack-dev-server-v2.3.0 [skip ci] * fix(cypress-schematic): suffix template files so they are not ignored (#23645) * chore: release @cypress/schematic-v2.1.1 [skip ci] * fix: Use tsconfig from build if exists (closes #23673) (#23696) Falls back to tsconfig.json if one is not passed in * feat: add support for generating angular component * feat: skip default test generation * feat: generate ct tests only if component was generated * feat: add @cypress/schematic to schematicCollections * feat: add documentation * docs: document component generation * add test * fix generate from component with dir * fix CI * add variable to job defaults * remove v13 support Co-authored-by: Mike Plummer <mike-plummer@users.noreply.github.com> Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: Zachary Williams <ZachJW34@gmail.com> Co-authored-by: Ronnie Laugen <ronnie@rlaugen.no> Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: Jordan <jordan@jpdesigning.com> Co-authored-by: Mark Noonan <mark@cypress.io> Co-authored-by: astone123 <adams@cypress.io> Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> Co-authored-by: Blue F <blue@cypress.io>
1 parent a869d5d commit af6be6f

File tree

11 files changed

+382
-21
lines changed

11 files changed

+382
-21
lines changed

npm/cypress-schematic/README.md

+15-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
## Requirements
3333

34-
- Angular 13+
34+
- Angular 14+
3535

3636
## Usage ⏯
3737

@@ -49,6 +49,8 @@ To install the schematic via cli arguments (installs both e2e and component test
4949
ng add @cypress/schematic --e2e --component
5050
```
5151

52+
The installation will add this schematic to the [default schematic collections](https://angular.io/guide/workspace-config#angular-cli-configuration-options). This allows you to execute the CLI commands without prefixing them with the package name.
53+
5254
To run Cypress in `open` mode within your project:
5355

5456
```shell script
@@ -78,37 +80,43 @@ ng run {project-name}:ct
7880
To generate a new e2e spec file:
7981

8082
```shell script
81-
ng generate @cypress/schematic:spec
83+
ng generate spec
8284
```
8385

8486
or (without cli prompt)
8587

8688
```shell script
87-
ng generate @cypress/schematic:spec {name}
89+
ng generate spec {name}
8890
```
8991

9092
To generate a new component spec file:
9193

9294
```shell script
93-
ng generate @cypress/schematic:spec --component
95+
ng generate spec --component
9496
```
9597

9698
or (without cli prompt)
9799

98100
```shell script
99-
ng generate @cypress/schematic:spec {component name} --component
101+
ng generate spec {component name} --component
100102
```
101103

102104
To generate a new component spec file in a specific folder:
103105

104106
```shell script
105-
ng generate @cypress/schematic:spec {component name} --component --path {path relative to project root}
107+
ng generate spec {component name} --component --path {path relative to project root}
106108
```
107109

108110
To generate new component spec files alongside all component files in a project:
109111

110112
```shell script
111-
ng generate @cypress/schematic:specs-ct
113+
ng generate specs-ct
114+
```
115+
116+
To generate a new, generic component definition with a component spec file in the given or default project. This wraps the [Angular CLI Component Generator](https://angular.io/cli/generate#component) and supports the same arguments.
117+
118+
```shell script
119+
ng generate component {component name}
112120
```
113121

114122
## Builder Options 🛠

npm/cypress-schematic/package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@
1010
"lint": "eslint --ext .ts,.json, ."
1111
},
1212
"dependencies": {
13-
"@angular-devkit/architect": "^0.1402.1",
14-
"@angular-devkit/core": "^14.2.1",
15-
"@angular-devkit/schematics": "^14.2.1",
16-
"@schematics/angular": "^14.2.1",
1713
"jsonc-parser": "^3.0.0",
1814
"rxjs": "~6.6.0"
1915
},
2016
"devDependencies": {
17+
"@angular-devkit/architect": "^0.1402.1",
18+
"@angular-devkit/core": "^14.2.1",
19+
"@angular-devkit/schematics": "^14.2.1",
2120
"@angular-devkit/schematics-cli": "^14.2.1",
2221
"@angular/cli": "^14.2.1",
22+
"@schematics/angular": "^14.2.1",
2323
"@types/chai-enzyme": "0.6.7",
2424
"@types/mocha": "8.0.3",
2525
"@types/node": "^18.0.6",
@@ -28,8 +28,8 @@
2828
"typescript": "^4.7.4"
2929
},
3030
"peerDependencies": {
31-
"@angular/cli": ">=12",
32-
"@angular/core": ">=12"
31+
"@angular/cli": ">=14",
32+
"@angular/core": ">=14"
3333
},
3434
"license": "MIT",
3535
"repository": {

npm/cypress-schematic/src/ct.spec.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const copyAngularMount = async (projectPath: string) => {
3535

3636
const cypressSchematicPackagePath = path.join(__dirname, '..')
3737

38-
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-13', 'angular-14']
38+
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-14', 'angular-15']
3939

4040
describe('ng add @cypress/schematic / e2e and ct', function () {
4141
this.timeout(1000 * 60 * 5)
@@ -49,5 +49,15 @@ describe('ng add @cypress/schematic / e2e and ct', function () {
4949
await copyAngularMount(projectPath)
5050
await runCommandInProject('yarn ng run angular:ct --watch false --spec src/app/app.component.cy.ts', projectPath)
5151
})
52+
53+
it('should generate component alongside component spec', async () => {
54+
const projectPath = await scaffoldAngularProject(project)
55+
56+
await runCommandInProject(`yarn add @cypress/schematic@file:${cypressSchematicPackagePath}`, projectPath)
57+
await runCommandInProject('yarn ng add @cypress/schematic --e2e --component', projectPath)
58+
await copyAngularMount(projectPath)
59+
await runCommandInProject('yarn ng generate c foo', projectPath)
60+
await runCommandInProject('yarn ng run angular:ct --watch false --spec src/app/foo/foo.component.cy.ts', projectPath)
61+
})
5262
}
5363
})

npm/cypress-schematic/src/e2e.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const runCommandInProject = (command: string, projectPath: string) => {
2424

2525
const cypressSchematicPackagePath = path.join(__dirname, '..')
2626

27-
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-13', 'angular-14']
27+
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-14', 'angular-15']
2828

2929
describe('ng add @cypress/schematic / only e2e', function () {
3030
this.timeout(1000 * 60 * 5)

npm/cypress-schematic/src/schematics/collection.json

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515
"description": "Create spec files for all Angular components in a project",
1616
"factory": "./ng-generate/cypress-ct-tests/index",
1717
"schema": "./ng-generate/cypress-ct-tests/schema.json"
18+
},
19+
"component": {
20+
"description": "Creates a new, generic component definition in the given or default project.",
21+
"aliases": [
22+
"c"
23+
],
24+
"factory": "./ng-generate/component/index",
25+
"schema": "./ng-generate/component/schema.json"
1826
}
1927
}
2028
}

npm/cypress-schematic/src/schematics/ng-add/index.spec.ts

+37
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'
44
import { join } from 'path'
55
import { expect } from 'chai'
6+
import { JsonObject } from '@angular-devkit/core'
67

78
describe('@cypress/schematic: ng-add', () => {
89
const schematicRunner = new SchematicTestRunner(
@@ -16,6 +17,7 @@ describe('@cypress/schematic: ng-add', () => {
1617
name: 'workspace',
1718
newProjectRoot: 'projects',
1819
version: '6.0.0',
20+
packageManager: 'yarn',
1921
}
2022

2123
const appOptions = {
@@ -26,6 +28,10 @@ describe('@cypress/schematic: ng-add', () => {
2628
skipPackageJson: false,
2729
}
2830

31+
const readAngularJson = (tree: UnitTestTree) => {
32+
return tree.readJson('/angular.json') as JsonObject
33+
}
34+
2935
beforeEach(async () => {
3036
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise()
3137
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise()
@@ -58,4 +64,35 @@ describe('@cypress/schematic: ng-add', () => {
5864
expect(files).to.contain('/projects/sandbox/cypress/fixtures/example.json')
5965
})
6066
})
67+
68+
it('should add @cypress/schematic to the schemaCollections array', async () => {
69+
const tree = await schematicRunner.runSchematicAsync('ng-add', { 'component': true }, appTree).toPromise()
70+
const angularJson = readAngularJson(tree)
71+
const cliOptions = angularJson.cli as JsonObject
72+
73+
expect(cliOptions).to.eql({
74+
packageManager: 'yarn',
75+
schematicCollections: ['@cypress/schematic', '@schematics/angular'],
76+
})
77+
})
78+
79+
it('should not overwrite existing schemaCollections array', async () => {
80+
let angularJson = readAngularJson(appTree)
81+
82+
appTree.overwrite('./angular.json', JSON.stringify({
83+
...angularJson,
84+
cli: {
85+
schematicCollections: ['@any/schematic'],
86+
},
87+
}))
88+
89+
const tree = await schematicRunner.runSchematicAsync('ng-add', { 'component': true }, appTree).toPromise()
90+
91+
angularJson = readAngularJson(tree)
92+
const cliOptions = angularJson.cli as JsonObject
93+
94+
expect(cliOptions).to.eql({
95+
schematicCollections: ['@cypress/schematic', '@any/schematic', '@schematics/angular'],
96+
})
97+
})
6198
})

npm/cypress-schematic/src/schematics/ng-add/index.ts

+28
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default function (_options: any): Rule {
4545
addCtSpecs(_options),
4646
addCypressTestScriptsToPackageJson(),
4747
modifyAngularJson(_options),
48+
addDefaultSchematic(),
4849
])(tree, _context)
4950
}
5051
}
@@ -306,6 +307,33 @@ function modifyAngularJson (options: any): Rule {
306307
}
307308
}
308309

310+
function addDefaultSchematic (): Rule {
311+
return (tree: Tree, _: SchematicContext) => {
312+
if (tree.exists('./angular.json')) {
313+
const angularJsonVal = getAngularJsonValue(tree)
314+
const angularSchematic = '@schematics/angular'
315+
const cli = {
316+
...angularJsonVal.cli,
317+
schematicCollections: ['@cypress/schematic', ...angularJsonVal?.cli?.schematicCollections ?? []],
318+
}
319+
320+
if (cli.schematicCollections.indexOf('@schematics/angular') === -1) {
321+
cli.schematicCollections.push(angularSchematic)
322+
}
323+
324+
return tree.overwrite(
325+
'./angular.json',
326+
JSON.stringify({
327+
...angularJsonVal,
328+
cli,
329+
}, null, 2),
330+
)
331+
}
332+
333+
throw new SchematicsException('angular.json not found')
334+
}
335+
}
336+
309337
export const getCypressConfigFile = (angularJsonVal: any, projectName: string) => {
310338
const project = angularJsonVal.projects[projectName]
311339
const tsConfig = project?.architect?.lint?.options?.tsConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'
2+
import { expect } from 'chai'
3+
import { join } from 'path'
4+
5+
describe('ng-generate @cypress/schematic:component', () => {
6+
const schematicRunner = new SchematicTestRunner(
7+
'schematics',
8+
join(__dirname, '../../collection.json'),
9+
)
10+
let appTree: UnitTestTree
11+
12+
const workspaceOptions = {
13+
name: 'workspace',
14+
newProjectRoot: 'projects',
15+
version: '12.0.0',
16+
}
17+
18+
const appOptions: Parameters<typeof schematicRunner['runExternalSchematicAsync']>[2] = {
19+
name: 'sandbox',
20+
inlineTemplate: false,
21+
routing: false,
22+
skipTests: false,
23+
skipPackageJson: false,
24+
}
25+
26+
beforeEach(async () => {
27+
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise()
28+
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise()
29+
})
30+
31+
it('should create cypress ct alongside the generated component', async () => {
32+
const tree = await schematicRunner.runSchematicAsync('component', { name: 'foo', project: 'sandbox' }, appTree).toPromise()
33+
34+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/foo.component.ts')
35+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/foo.component.html')
36+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/foo.component.cy.ts')
37+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/foo.component.css')
38+
expect(tree.files).not.to.contain('/projects/sandbox/src/app/foo/foo.component.spec.ts')
39+
})
40+
41+
it('should not generate component which does exist already', async () => {
42+
let tree = await schematicRunner.runSchematicAsync('component', { name: 'foo', project: 'sandbox' }, appTree).toPromise()
43+
44+
tree = await schematicRunner.runSchematicAsync('component', { name: 'foo', project: 'sandbox' }, appTree).toPromise()
45+
46+
expect(tree.files.filter((f) => f === '/projects/sandbox/src/app/foo/foo.component.ts').length).to.eq(1)
47+
expect(tree.files.filter((f) => f === '/projects/sandbox/src/app/foo/foo.component.html').length).to.eq(1)
48+
expect(tree.files.filter((f) => f === '/projects/sandbox/src/app/foo/foo.component.cy.ts').length).to.eq(1)
49+
expect(tree.files.filter((f) => f === '/projects/sandbox/src/app/foo/foo.component.css').length).to.eq(1)
50+
})
51+
52+
it('should generate component given a component containing a directory', async () => {
53+
const tree = await schematicRunner.runSchematicAsync('component', { name: 'foo/bar', project: 'sandbox' }, appTree).toPromise()
54+
55+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/bar/bar.component.ts')
56+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/bar/bar.component.html')
57+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/bar/bar.component.cy.ts')
58+
expect(tree.files).to.contain('/projects/sandbox/src/app/foo/bar/bar.component.css')
59+
})
60+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { chain, externalSchematic, noop, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'
2+
import cypressTest from '../cypress-test'
3+
import path = require('path');
4+
5+
export default function (options: any): Rule {
6+
return (_: Tree, _context: SchematicContext) => {
7+
return chain([
8+
externalSchematic('@schematics/angular', 'component', {
9+
...options,
10+
skipTests: true,
11+
}),
12+
(tree: Tree, _context: SchematicContext) => {
13+
const componentName = path.parse(options.name).name
14+
const componentPath = tree.actions.filter((a) => a.path.includes(`${componentName}.component.ts`))
15+
.map((a) => path.dirname(a.path))
16+
.at(0)
17+
18+
return componentPath ? cypressTest({
19+
...options,
20+
component: true,
21+
path: componentPath,
22+
name: componentName,
23+
}) : noop()
24+
},
25+
])
26+
}
27+
}

0 commit comments

Comments
 (0)