Skip to content

Commit

Permalink
fix(store): merge options with default options (ngxs#1686)
Browse files Browse the repository at this point in the history
* test: add selector that uses container injection

* test: add text for missing container injection

* feat: add option merging to ngxsmodule

* chore: remove debugging code

* test: add launch option for jest

* test: fix selectoroptions in test

* chore: remove comment

* chore: remove useless var

* test: add launch config for jest current file

* chore: add more documentation to code

* test: add tests for mergeDeep

* fix: change rest parameter type

* chore: change bundlesize to fit changes
  • Loading branch information
lukaskurz authored Nov 22, 2020
1 parent 8deef29 commit 3ecc3b4
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 7 deletions.
41 changes: 40 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,45 @@
"outFiles": [
"${workspaceFolder}/**/*.js"
]
},
{
"type": "node",
"request": "launch",
"name": "Jest All",
"program": "${workspaceFolder}/node_modules/cross-env/dist/bin/cross-env.js",
"cwd": "${workspaceFolder}",
"args": [
"CI=true",
"ng",
"test",
"--project",
"ngxs",
"--colors",
"--runInBand"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
},
{
"type": "node",
"request": "launch",
"name": "Jest Current File",
"program": "${workspaceFolder}/node_modules/cross-env/dist/bin/cross-env.js",
"cwd": "${workspaceFolder}",
"args": [
"CI=true",
"ng",
"test",
"--project",
"ngxs",
"--testPathPattern=${fileBasenameNoExtension}",
"--colors",
"--runInBand"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
}
]
}
}
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
"path": "./@ngxs/store/fesm2015/ngxs-store.js",
"package": "@ngxs/store",
"target": "es2015",
"maxSize": "112.12KB",
"maxSize": "113.25KB",
"compression": "none"
},
{
"path": "./@ngxs/store/fesm5/ngxs-store.js",
"package": "@ngxs/store",
"target": "es5",
"maxSize": "131.77KB",
"maxSize": "133.10KB",
"compression": "none"
},
{
Expand Down
1 change: 1 addition & 0 deletions integration/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class="todo-list">
<div>
<h3>Reactive Form</h3>
<p>{{ injected$ | async }}</p>
<form [formGroup]="pizzaForm" novalidate (ngSubmit)="onSubmit()" ngxsForm="todos.pizza">
Toppings: <input type="text" formControlName="toppings" /> <br />
Crust <input type="text" formControlName="crust" /> <br />
Expand Down
2 changes: 2 additions & 0 deletions integration/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import { Extras, Pizza, Todo } from '@integration/store/todos/todos.model';
export class AppComponent implements OnInit {
public allExtras: Extras[];
public pizzaForm: FormGroup;
public greeting: string;
@Select(TodoState) public todos$: Observable<Todo[]>;
@Select(TodoState.pandas) public pandas$: Observable<Todo[]>;
@Select(TodosState.pizza) public pizza$: Observable<Pizza>;
@Select(TodosState.injected) public injected$: Observable<string>;

constructor(private store: Store, private formBuilder: FormBuilder) {}

Expand Down
5 changes: 4 additions & 1 deletion integration/app/store/store.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { environment as env } from '../../environments/environment';
NgxsLoggerPluginModule.forRoot({ logger: console, collapsed: false }),
NgxsReduxDevtoolsPluginModule.forRoot({ disabled: env.production }),
NgxsRouterPluginModule.forRoot(),
NgxsModule.forRoot([TodosState, TodoState], { developmentMode: !env.production })
NgxsModule.forRoot([TodosState, TodoState], {
developmentMode: !env.production,
selectorOptions: {} // empty object to test option merging
})
],
exports: [
NgxsFormPluginModule,
Expand Down
9 changes: 9 additions & 0 deletions integration/app/store/todos/todos.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TodoState } from '@integration/store/todos/todo/todo.state';
import { Pizza, TodoStateModel } from '@integration/store/todos/todos.model';
import { LoadData, SetPrefix } from '@integration/store/todos/todos.actions';
import { Injectable } from '@angular/core';
import { ListState } from '@integration/list/list.state';

const TODOS_TOKEN: StateToken<TodoStateModel> = new StateToken('todos');

Expand All @@ -26,6 +27,14 @@ export class TodosState {
return state.pizza;
}

@Selector([ListState.hello])
public static injected(state: TodoStateModel, hello: string): string {
if (state.todo == null || hello == null) {
return 'container injection failed or is disabled';
}
return `${hello}! i have ${state.todo.length} todos`;
}

@Action(SetPrefix)
public setPrefix({ setState }: StateContext<TodoStateModel>) {
setState(
Expand Down
3 changes: 2 additions & 1 deletion packages/store/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { DispatchOutsideZoneNgxsExecutionStrategy } from './execution/dispatch-o
import { InternalNgxsExecutionStrategy } from './execution/internal-ngxs-execution-strategy';
import { HostEnvironment } from './host-environment/host-environment';
import { ConfigValidator } from './internal/config-validator';
import { mergeDeep } from './utils/utils';

/**
* Ngxs Module
Expand Down Expand Up @@ -152,7 +153,7 @@ export class NgxsModule {
}

private static ngxsConfigFactory(options: NgxsModuleOptions): NgxsConfig {
return Object.assign(new NgxsConfig(), options);
return mergeDeep(new NgxsConfig(), options);
}

private static appBootstrapListenerFactory(bootstrapper: NgxsBootstrapper): Function {
Expand Down
37 changes: 37 additions & 0 deletions packages/store/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,40 @@ export const setValue = (obj: any, prop: string, val: any) => {
*/
export const getValue = (obj: any, prop: string): any =>
prop.split('.').reduce((acc: any, part: string) => acc && acc[part], obj);

/**
* Simple object check.
*
* isObject({a:1}) //=> true
* isObject(1) //=> false
*
* @ignore
*/
export const isObject = (item: any) => {
return item && typeof item === 'object' && !Array.isArray(item);
};

/**
* Deep merge two objects.
*
* mergeDeep({a:1, b:{x: 1, y:2}}, {b:{x: 3}, c:4}) //=> {a:1, b:{x:3, y:2}, c:4}
*
* @param base base object onto which `sources` will be applied
*/
export const mergeDeep = (base: any, ...sources: any[]): any => {
if (!sources.length) return base;
const source = sources.shift();

if (isObject(base) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!base[key]) Object.assign(base, { [key]: {} });
mergeDeep(base[key], source[key]);
} else {
Object.assign(base, { [key]: source[key] });
}
}
}

return mergeDeep(base, ...sources);
};
2 changes: 1 addition & 1 deletion packages/store/tests/selector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ describe('Selector', () => {
TestBed.configureTestingModule({
imports: [
NgxsModule.forRoot([ContactsState], {
selectorOptions: { suppressErrors: false }
selectorOptions: { suppressErrors: false, injectContainerState: false }
})
]
});
Expand Down
72 changes: 71 additions & 1 deletion packages/store/tests/utils/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PlainObject } from '@ngxs/store/internals';
import { propGetter } from '../../src/internal/internals';
import { setValue } from '../../src/utils/utils';
import { setValue, isObject, mergeDeep } from '../../src/utils/utils';
import { NgxsConfig } from '../../src/symbols';

describe('utils', () => {
Expand Down Expand Up @@ -79,4 +79,74 @@ describe('utils', () => {
expect(propGetter(['a', 'b'], config)(target)).toEqual({ c: 100 });
});
});

describe('isObject', () => {
it('should correctly identify objects', () => {
const object = { a: 1 };
const constructedObject = new Object(1);
const array = [1, 2, 3];
const string = 'asdasd';
const number = 12;

expect(isObject(object)).toBeTruthy();
expect(isObject(constructedObject)).toBeTruthy();
expect(isObject(array)).toBeFalsy();
expect(isObject(string)).toBeFalsy();
expect(isObject(number)).toBeFalsy();

expect(isObject(null)).toBeFalsy();
expect(isObject(undefined)).toBeFalsy();
});
});

describe('mergeDeep', () => {
it('should merge properties from two objects', () => {
const base = { a: 1, b: 2 };
const source = { c: 3 };

expect(mergeDeep(base, source)).toEqual({ a: 1, b: 2, c: 3 });
});

it('should merge properties from multiple objects', () => {
const base = { a: 1, b: 2 };
const sources = [{ c: 3 }, { d: 4 }, { e: 5 }];

expect(mergeDeep(base, sources[0], sources[1], sources[2])).toEqual({
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
});
});

it('should merge subproperties from two objects ', () => {
const base = { a: 1, b: { x: 2, z: { c: 1 } } };
const source = { b: { y: 3, z: { d: 2 }, w: { f: 1 } } };

expect(mergeDeep(base, source)).toEqual({
a: 1,
b: { x: 2, y: 3, z: { c: 1, d: 2 }, w: { f: 1 } }
});
});

it('should overwrite properties of base object with source object', () => {
const base = { a: 1, b: 2 };
const source = { c: 3, b: 4 };

expect(mergeDeep(base, source)).toEqual({ a: 1, b: 4, c: 3 });
});

it('should overwrite properties in the correct order', () => {
const base = { a: 1, b: 2 };
const sources = [{ c: 3, b: 4, d: 6 }, { c: 5 }, { b: 7 }];

expect(mergeDeep(base, sources[0], sources[1], sources[2])).toEqual({
a: 1,
b: 7,
c: 5,
d: 6
});
});
});
});

0 comments on commit 3ecc3b4

Please sign in to comment.