Skip to content

Commit 5974913

Browse files
feat(effects): move createEffect migration to ng-update migration (#3074)
BREAKING CHANGE: The create-effect-migration migration is removed BEFORE: The Effect decorator removal and migration are done manually through schematics. AFTER: The Effect decorator removal and migration are performed automatically on upgrade to version 13 of NgRx Effects.
1 parent 8cc930b commit 5974913

File tree

7 files changed

+100
-81
lines changed

7 files changed

+100
-81
lines changed

modules/schematics/src/create-effect-migration/index.spec.ts renamed to modules/effects/migrations/13_0_0/index.spec.ts

Lines changed: 91 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@ import {
66
import * as path from 'path';
77
import { createWorkspace } from '@ngrx/schematics-core/testing';
88

9-
describe('Creator migration', () => {
10-
const schematicRunner = new SchematicTestRunner(
11-
'@ngrx/schematics',
12-
path.join(__dirname, '../../collection.json')
13-
);
14-
15-
let appTree: UnitTestTree;
16-
17-
beforeEach(async () => {
18-
appTree = await createWorkspace(schematicRunner, appTree);
19-
});
20-
21-
it('should use createEffect for non-dispatching effects', async () => {
22-
const input = tags.stripIndent`
9+
describe('Effects Migration 13_0_0', () => {
10+
describe('@Effect to createEffect', () => {
11+
const collectionPath = path.join(__dirname, '../migration.json');
12+
const schematicRunner = new SchematicTestRunner(
13+
'schematics',
14+
collectionPath
15+
);
16+
17+
let appTree: UnitTestTree;
18+
19+
beforeEach(async () => {
20+
appTree = await createWorkspace(schematicRunner, appTree);
21+
});
22+
23+
it('migrates to createEffect for dispatching effects', async () => {
24+
const input = tags.stripIndent`
2325
@Injectable()
2426
export class SomeEffectsClass {
2527
constructor(private actions$: Actions) {}
@@ -31,7 +33,7 @@ describe('Creator migration', () => {
3133
}
3234
`;
3335

34-
const output = tags.stripIndent`
36+
const output = tags.stripIndent`
3537
@Injectable()
3638
export class SomeEffectsClass {
3739
constructor(private actions$: Actions) {}
@@ -43,11 +45,11 @@ describe('Creator migration', () => {
4345
}
4446
`;
4547

46-
await runTest(input, output);
47-
});
48+
await runTest(input, output);
49+
});
4850

49-
it('should use createEffect for non-dispatching effects', async () => {
50-
const input = tags.stripIndent`
51+
it('migrates to createEffect for non-dispatching effects', async () => {
52+
const input = tags.stripIndent`
5153
@Injectable()
5254
export class SomeEffectsClass {
5355
constructor(private actions$: Actions) {}
@@ -59,7 +61,7 @@ describe('Creator migration', () => {
5961
}
6062
`;
6163

62-
const output = tags.stripIndent`
64+
const output = tags.stripIndent`
6365
@Injectable()
6466
export class SomeEffectsClass {
6567
constructor(private actions$: Actions) {}
@@ -71,11 +73,11 @@ describe('Creator migration', () => {
7173
}
7274
`;
7375

74-
await runTest(input, output);
75-
});
76+
await runTest(input, output);
77+
});
7678

77-
it('should use createEffect for effects as functions', async () => {
78-
const input = tags.stripIndent`
79+
it('migrates to createEffect for effects as functions', async () => {
80+
const input = tags.stripIndent`
7981
@Injectable()
8082
export class SomeEffectsClass {
8183
constructor(private actions$: Actions) {}
@@ -87,7 +89,7 @@ describe('Creator migration', () => {
8789
}
8890
`;
8991

90-
const output = tags.stripIndent`
92+
const output = tags.stripIndent`
9193
@Injectable()
9294
export class SomeEffectsClass {
9395
constructor(private actions$: Actions) {}
@@ -99,11 +101,11 @@ describe('Creator migration', () => {
99101
}
100102
`;
101103

102-
await runTest(input, output);
103-
});
104+
await runTest(input, output);
105+
});
104106

105-
it('should stay off of other decorators', async () => {
106-
const input = tags.stripIndent`
107+
it('keeps other decorators', async () => {
108+
const input = tags.stripIndent`
107109
@Injectable()
108110
export class SomeEffectsClass {
109111
constructor(private actions$: Actions) {}
@@ -122,7 +124,7 @@ describe('Creator migration', () => {
122124
}
123125
`;
124126

125-
const output = tags.stripIndent`
127+
const output = tags.stripIndent`
126128
@Injectable()
127129
export class SomeEffectsClass {
128130
constructor(private actions$: Actions) {}
@@ -141,31 +143,31 @@ describe('Creator migration', () => {
141143
}
142144
`;
143145

144-
await runTest(input, output);
145-
});
146+
await runTest(input, output);
147+
});
146148

147-
it('should import createEffect', async () => {
148-
const input = tags.stripIndent`
149+
it('imports createEffect from effects', async () => {
150+
const input = tags.stripIndent`
149151
import { Actions, ofType, Effect } from '@ngrx/effects';
150152
@Injectable()
151153
export class SomeEffectsClass {
152154
constructor(private actions$: Actions) {}
153155
}
154156
`;
155157

156-
const output = tags.stripIndent`
158+
const output = tags.stripIndent`
157159
import { Actions, ofType, createEffect } from '@ngrx/effects';
158160
@Injectable()
159161
export class SomeEffectsClass {
160162
constructor(private actions$: Actions) {}
161163
}
162164
`;
163165

164-
await runTest(input, output);
165-
});
166+
await runTest(input, output);
167+
});
166168

167-
it('should not import createEffect if already imported', async () => {
168-
const input = tags.stripIndent`
169+
it('does not import createEffect if already imported', async () => {
170+
const input = tags.stripIndent`
169171
import { Actions, Effect, createEffect, ofType } from '@ngrx/effects';
170172
@Injectable()
171173
export class SomeEffectsClass {
@@ -178,7 +180,7 @@ describe('Creator migration', () => {
178180
}
179181
`;
180182

181-
const output = tags.stripIndent`
183+
const output = tags.stripIndent`
182184
import { Actions, createEffect, ofType } from '@ngrx/effects';
183185
@Injectable()
184186
export class SomeEffectsClass {
@@ -191,11 +193,11 @@ describe('Creator migration', () => {
191193
}
192194
`;
193195

194-
await runTest(input, output);
195-
});
196+
await runTest(input, output);
197+
});
196198

197-
it('should not run the schematic if the createEffect syntax is already used', async () => {
198-
const input = tags.stripIndent`
199+
it('does not migrate if the createEffect syntax is already used', async () => {
200+
const input = tags.stripIndent`
199201
import { Actions, createEffect, ofType } from '@ngrx/effects';
200202
@Injectable()
201203
export class SomeEffectsClass {
@@ -207,23 +209,50 @@ describe('Creator migration', () => {
207209
}
208210
`;
209211

210-
await runTest(input, input);
212+
await runTest(input, input);
213+
});
214+
215+
it('removes the @Effect decorator', async () => {
216+
const input = tags.stripIndent`
217+
import { Actions, createEffect, ofType } from '@ngrx/effects';
218+
@Injectable()
219+
export class SomeEffectsClass {
220+
@Effect()
221+
logout$ = createEffect(() => this.actions$.pipe(
222+
ofType('LOGOUT'),
223+
map(() => ({ type: 'LOGGED_OUT' }))
224+
));
225+
constructor(private actions$: Actions) {}
226+
}
227+
`;
228+
const output = tags.stripIndent`
229+
import { Actions, createEffect, ofType } from '@ngrx/effects';
230+
@Injectable()
231+
export class SomeEffectsClass {
232+
logout$ = createEffect(() => this.actions$.pipe(
233+
ofType('LOGOUT'),
234+
map(() => ({ type: 'LOGGED_OUT' }))
235+
));
236+
constructor(private actions$: Actions) {}
237+
}
238+
`;
239+
await runTest(input, output);
240+
});
241+
242+
async function runTest(input: string, expected: string) {
243+
const effectPath = '/some.effects.ts';
244+
appTree.create(effectPath, input);
245+
246+
const tree = await schematicRunner
247+
.runSchematicAsync(`ngrx-effects-migration-03`, {}, appTree)
248+
.toPromise();
249+
250+
const actual = tree.readContent(effectPath);
251+
252+
const removeEmptyLines = (value: string) =>
253+
value.replace(/^\s*$(?:\r\n?|\n)/gm, '');
254+
255+
expect(removeEmptyLines(actual)).toBe(removeEmptyLines(expected));
256+
}
211257
});
212-
213-
async function runTest(input: string, expected: string) {
214-
const options = {};
215-
const effectPath = '/some.effects.ts';
216-
appTree.create(effectPath, input);
217-
218-
const tree = await schematicRunner
219-
.runSchematicAsync('create-effect-migration', options, appTree)
220-
.toPromise();
221-
222-
const actual = tree.readContent(effectPath);
223-
224-
const removeEmptyLines = (value: string) =>
225-
value.replace(/^\s*$(?:\r\n?|\n)/gm, '');
226-
227-
expect(removeEmptyLines(actual)).toBe(removeEmptyLines(expected));
228-
}
229258
});

modules/schematics/src/create-effect-migration/index.ts renamed to modules/effects/migrations/13_0_0/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ function replaceEffectDecorators(
5656
effects: ts.PropertyDeclaration[]
5757
) {
5858
const inserts = effects
59-
.filter((effect) => !!effect.initializer)
6059
.map((effect) => {
6160
if (!effect.initializer) {
6261
return [];
@@ -65,6 +64,9 @@ function replaceEffectDecorators(
6564
if (!decorator) {
6665
return [];
6766
}
67+
if (effect.initializer.getText().includes('createEffect')) {
68+
return [];
69+
}
6870
const effectArguments = getDispatchProperties(
6971
host,
7072
sourceFile.text,

modules/effects/migrations/migration.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
"description": "The road to v9",
1111
"version": "9-beta",
1212
"factory": "./9_0_0/index"
13+
},
14+
"ngrx-effects-migration-03": {
15+
"description": "The road to v13",
16+
"version": "13",
17+
"factory": "./13_0_0/index"
1318
}
1419
}
1520
}

modules/schematics/collection.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@
2222
"description": "Add side effect class"
2323
},
2424

25-
"create-effect-migration": {
26-
"aliases": ["cefm"],
27-
"factory": "./src/create-effect-migration",
28-
"schema": "./src/create-effect-migration/schema.json",
29-
"description": "Migrated usages of @Effect() to createEffect()"
30-
},
31-
3225
"entity": {
3326
"aliases": ["en"],
3427
"factory": "./src/entity",

modules/schematics/src/create-effect-migration/schema.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

modules/schematics/src/create-effect-migration/schema.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

projects/ngrx.io/content/guide/migration/v11.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ login$ = createEffect(() => {
163163
});
164164
```
165165

166-
To automatically migrate `@Effect` usages to the `createEffect` method, run the following NgRx migration:
166+
To automatically migrate `@Effect` usages to the `createEffect` method, run the following NgRx migration (this migration is only available in v11 and v12):
167167

168168
```sh
169169
ng generate @ngrx/schematics:create-effect-migration

0 commit comments

Comments
 (0)