Skip to content

Commit

Permalink
refactor(router-store): change name for full router state serializer (#…
Browse files Browse the repository at this point in the history
…3430)

Closes #3416

BREAKING CHANGES:

The full router state serializer has been renamed.

BEFORE:

The full router state serializer is named `DefaultRouterStateSerializer`

AFTER:

The full router state serializer is named `FullRouterStateSerializer`. A migration is provided to rename the export in affected projects.
  • Loading branch information
david-shortman authored Jun 6, 2022
1 parent 5abf828 commit d443f50
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 51 deletions.
77 changes: 77 additions & 0 deletions modules/router-store/migrations/14_0_0/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Tree } from '@angular-devkit/schematics';
import {
SchematicTestRunner,
UnitTestTree,
} from '@angular-devkit/schematics/testing';
import * as path from 'path';
import { createPackageJson } from '@ngrx/schematics-core/testing/create-package';
import { waitForAsync } from '@angular/core/testing';

describe('Router Store Migration 14_0_0', () => {
let appTree: UnitTestTree;
const collectionPath = path.join(__dirname, '../migration.json');
const pkgName = 'router-store';

beforeEach(() => {
appTree = new UnitTestTree(Tree.empty());
appTree.create(
'/tsconfig.json',
`
{
"include": [**./*.ts"]
}
`
);
createPackageJson('', pkgName, appTree);
});

describe('Rename serializers', () => {
it(
`should rename the DefaultRouterStateSerializer to FullRouterStateSerializer`,
waitForAsync(async () => {
const input = `
import { DefaultRouterStateSerializer } from '@ngrx/router-store';
const fullSerializer: DefaultRouterStateSerializer;
@NgModule({
imports: [
AuthModule,
AppRoutingModule,
StoreRouterConnectingModule.forRoot({ serializer: DefaultRouterStateSerializer, key: 'router' }),
CoreModule,
],
bootstrap: [AppComponent],
})
export class AppModule {}
`;
const expected = `
import { FullRouterStateSerializer } from '@ngrx/router-store';
const fullSerializer: FullRouterStateSerializer;
@NgModule({
imports: [
AuthModule,
AppRoutingModule,
StoreRouterConnectingModule.forRoot({ serializer: FullRouterStateSerializer, key: 'router' }),
CoreModule,
],
bootstrap: [AppComponent],
})
export class AppModule {}
`;

appTree.create('./app.module.ts', input);
const runner = new SchematicTestRunner('schematics', collectionPath);

const newTree = await runner
.runSchematicAsync(`ngrx-${pkgName}-migration-04`, {}, appTree)
.toPromise();
const file = newTree.readContent('app.module.ts');

expect(file).toBe(expected);
})
);
});
});
140 changes: 140 additions & 0 deletions modules/router-store/migrations/14_0_0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import * as ts from 'typescript';
import { Rule, chain, Tree } from '@angular-devkit/schematics';
import {
visitTSSourceFiles,
commitChanges,
createReplaceChange,
ReplaceChange,
} from '../../schematics-core';

const renames = {
DefaultRouterStateSerializer: 'FullRouterStateSerializer',
};

function renameSerializers() {
return (tree: Tree) => {
visitTSSourceFiles(tree, (sourceFile) => {
const routerStoreImports = sourceFile.statements
.filter(ts.isImportDeclaration)
.filter(({ moduleSpecifier }) =>
moduleSpecifier.getText(sourceFile).includes('@ngrx/router-store')
);

if (routerStoreImports.length === 0) {
return;
}

const changes = [
...findSerializerImportDeclarations(sourceFile, routerStoreImports),
...findSerializerReplacements(sourceFile),
];

commitChanges(tree, sourceFile.fileName, changes);
});
};
}

function findSerializerImportDeclarations(
sourceFile: ts.SourceFile,
imports: ts.ImportDeclaration[]
) {
const changes = imports
.map((p) => (p?.importClause?.namedBindings as ts.NamedImports)?.elements)
.reduce(
(imports, curr) => imports.concat(curr ?? []),
[] as ts.ImportSpecifier[]
)
.map((specifier) => {
if (!ts.isImportSpecifier(specifier)) {
return { hit: false };
}

const serializerImports = Object.keys(renames);
if (serializerImports.includes(specifier.name.text)) {
return { hit: true, specifier, text: specifier.name.text };
}

// if import is renamed
if (
specifier.propertyName &&
serializerImports.includes(specifier.propertyName.text)
) {
return { hit: true, specifier, text: specifier.propertyName.text };
}

return { hit: false };
})
.filter(({ hit }) => hit)
.map(({ specifier, text }) =>
!!specifier && !!text
? createReplaceChange(
sourceFile,
specifier,
text,
(renames as any)[text]
)
: undefined
)
.filter((change) => !!change) as Array<ReplaceChange>;

return changes;
}

function findSerializerReplacements(sourceFile: ts.SourceFile) {
const renameKeys = Object.keys(renames);
const changes: ReplaceChange[] = [];
ts.forEachChild(sourceFile, (node) => find(node, changes));
return changes;

function find(node: ts.Node, changes: ReplaceChange[]) {
let change = undefined;

if (
ts.isPropertyAssignment(node) &&
renameKeys.includes(node.initializer.getText(sourceFile))
) {
change = {
node: node.initializer,
text: node.initializer.getText(sourceFile),
};
}

if (
ts.isPropertyAccessExpression(node) &&
renameKeys.includes(node.expression.getText(sourceFile))
) {
change = {
node: node.expression,
text: node.expression.getText(sourceFile),
};
}

if (
ts.isVariableDeclaration(node) &&
node.type &&
renameKeys.includes(node.type.getText(sourceFile))
) {
change = {
node: node.type,
text: node.type.getText(sourceFile),
};
}

if (change) {
changes.push(
createReplaceChange(
sourceFile,
change.node,
change.text,
(renames as any)[change.text]
)
);
}

ts.forEachChild(node, (childNode) => find(childNode, changes));
}
}

export default function (): Rule {
return chain([renameSerializers()]);
}
5 changes: 5 additions & 0 deletions modules/router-store/migrations/migration.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"description": "The road to v9",
"version": "9-beta",
"factory": "./9_0_0/index"
},
"ngrx-router-store-migration-04": {
"description": "The road to v14",
"version": "14-beta",
"factory": "./14_0_0/index"
}
}
}
20 changes: 10 additions & 10 deletions modules/router-store/spec/router_store_module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
RouterState,
RouterStateSerializer,
MinimalRouterStateSerializer,
DefaultRouterStateSerializer,
FullRouterStateSerializer,
} from '@ngrx/router-store';
import { select, Store, ActionsSubject } from '@ngrx/store';
import { withLatestFrom, filter, skip } from 'rxjs/operators';
Expand Down Expand Up @@ -165,27 +165,27 @@ describe('Router Store Module', () => {
router.navigateByUrl('/');
});

it('should use the minimal router serializer by default', () => {
it('should use the default router serializer by default', () => {
const { serializer } = setup();
expect(serializer).toEqual(new MinimalRouterStateSerializer());
});

it('should use the minimal router serializer if minimal state option is passed in', () => {
it('should use the default router serializer if minimal state option is passed in', () => {
const { serializer } = setup(RouterState.Minimal);
expect(serializer).toEqual(new MinimalRouterStateSerializer());
});

it('should use the default router serializer if full state option is passed in', () => {
it('should use the full router serializer if full state option is passed in', () => {
const { serializer } = setup(RouterState.Full);
expect(serializer).toEqual(new DefaultRouterStateSerializer());
expect(serializer).toEqual(new FullRouterStateSerializer());
});

it('should use the provided serializer if one is provided', () => {
const { serializer } = setup(
RouterState.Full,
MinimalRouterStateSerializer
FullRouterStateSerializer
);
expect(serializer).toEqual(new MinimalRouterStateSerializer());
expect(serializer).toEqual(new FullRouterStateSerializer());
});
});

Expand Down Expand Up @@ -229,15 +229,15 @@ describe('Router Store Module', () => {

it('should use the minimal router serializer', () => {
const { serializer } = setup(RouterState.Minimal);
expect(serializer).toEqual(new MinimalRouterStateSerializer());
expect(serializer).toEqual(new FullRouterStateSerializer());
});

it('should use the provided serializer if one is provided', () => {
const { serializer } = setup(
RouterState.Minimal,
DefaultRouterStateSerializer
MinimalRouterStateSerializer
);
expect(serializer).toEqual(new DefaultRouterStateSerializer());
expect(serializer).toEqual(new MinimalRouterStateSerializer());
});
});
});
Expand Down
10 changes: 5 additions & 5 deletions modules/router-store/spec/serializers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { RouterStateSnapshot } from '@angular/router';
import {
DefaultRouterStateSerializer,
FullRouterStateSerializer,
MinimalRouterStateSerializer,
} from '../src';

describe('default serializer', () => {
describe('full serializer', () => {
it('should serialize all properties', () => {
const serializer = new DefaultRouterStateSerializer();
const serializer = new FullRouterStateSerializer();
const snapshot = createRouteSnapshot();
const routerState = {
url: 'url',
Expand All @@ -22,7 +22,7 @@ describe('default serializer', () => {
});

it('should serialize with an empty routeConfig', () => {
const serializer = new DefaultRouterStateSerializer();
const serializer = new FullRouterStateSerializer();
const snapshot = { ...createRouteSnapshot(), routeConfig: null };
const routerState = {
url: 'url',
Expand All @@ -42,7 +42,7 @@ describe('default serializer', () => {
});

it('should serialize children', () => {
const serializer = new DefaultRouterStateSerializer();
const serializer = new FullRouterStateSerializer();
const snapshot = {
...createRouteSnapshot(),
children: [createRouteSnapshot('child')],
Expand Down
2 changes: 1 addition & 1 deletion modules/router-store/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@angular/router';

import { BaseRouterStoreState } from './serializers/base';
import { SerializedRouterStateSnapshot } from './serializers/default_serializer';
import { SerializedRouterStateSnapshot } from './serializers/full_serializer';
import { createAction, props } from '@ngrx/store';

/**
Expand Down
4 changes: 2 additions & 2 deletions modules/router-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export {
BaseRouterStoreState,
} from './serializers/base';
export {
DefaultRouterStateSerializer,
FullRouterStateSerializer,
SerializedRouterStateSnapshot,
} from './serializers/default_serializer';
} from './serializers/full_serializer';
export {
MinimalActivatedRouteSnapshot,
MinimalRouterStateSnapshot,
Expand Down
2 changes: 1 addition & 1 deletion modules/router-store/src/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
RouterAction,
} from './actions';
import { BaseRouterStoreState } from './serializers/base';
import { SerializedRouterStateSnapshot } from './serializers/default_serializer';
import { SerializedRouterStateSnapshot } from './serializers/full_serializer';

export type RouterReducerState<
T extends BaseRouterStoreState = SerializedRouterStateSnapshot
Expand Down
Loading

0 comments on commit d443f50

Please sign in to comment.