Merge pull request #5341 from abpframework/feat/5340

Created an InternalStore for migration from NGXS
pull/5347/head
Mehmet Erim 5 years ago committed by GitHub
commit 1b77cb5582
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,4 +1,13 @@
import { TemplateRef, Type } from '@angular/core';
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Serializable ? DeepPartial<T[P]> : T[P];
};
type Serializable = Record<
string | number | symbol,
string | number | boolean | Record<string | number | symbol, any>
>;
export type InferredInstanceOf<T> = T extends Type<infer U> ? U : never;
export type InferredContextOf<T> = T extends TemplateRef<infer U> ? U : never;

@ -0,0 +1,108 @@
import clone from 'just-clone';
import { take } from 'rxjs/operators';
import { DeepPartial } from '../models';
import { InternalStore } from '../utils';
const mockInitialState = {
foo: {
bar: {
baz: [() => {}],
qux: null as Promise<any>,
},
n: 0,
},
x: '',
a: false,
};
type MockState = typeof mockInitialState;
const patch1: DeepPartial<MockState> = { foo: { bar: { baz: [() => {}] } } };
const expected1: MockState = clone(mockInitialState);
expected1.foo.bar.baz = patch1.foo.bar.baz;
const patch2: DeepPartial<MockState> = { foo: { bar: { qux: Promise.resolve() } } };
const expected2: MockState = clone(mockInitialState);
expected2.foo.bar.qux = patch2.foo.bar.qux;
const patch3: DeepPartial<MockState> = { foo: { n: 1 } };
const expected3: MockState = clone(mockInitialState);
expected3.foo.n = patch3.foo.n;
const patch4: DeepPartial<MockState> = { x: 'X' };
const expected4: MockState = clone(mockInitialState);
expected4.x = patch4.x;
const patch5: DeepPartial<MockState> = { a: true };
const expected5: MockState = clone(mockInitialState);
expected5.a = patch5.a;
describe('Internal Store', () => {
describe('sliceState', () => {
test.each`
selector | expected
${(state: MockState) => state.a} | ${mockInitialState.a}
${(state: MockState) => state.x} | ${mockInitialState.x}
${(state: MockState) => state.foo.n} | ${mockInitialState.foo.n}
${(state: MockState) => state.foo.bar} | ${mockInitialState.foo.bar}
${(state: MockState) => state.foo.bar.baz} | ${mockInitialState.foo.bar.baz}
${(state: MockState) => state.foo.bar.qux} | ${mockInitialState.foo.bar.qux}
`(
'should return observable $expected when selector is $selector',
async ({ selector, expected }) => {
const store = new InternalStore(mockInitialState);
const value = await store
.sliceState(selector)
.pipe(take(1))
.toPromise();
expect(value).toEqual(expected);
},
);
});
describe('patchState', () => {
test.each`
patch | expected
${patch1} | ${expected1}
${patch2} | ${expected2}
${patch3} | ${expected3}
${patch4} | ${expected4}
${patch5} | ${expected5}
`('should set state as $expected when patch is $patch', ({ patch, expected }) => {
const store = new InternalStore(mockInitialState);
store.patch(patch);
expect(store.state).toEqual(expected);
});
});
describe('sliceUpdate', () => {
it('should return slice of update$ based on selector', done => {
const store = new InternalStore(mockInitialState);
const onQux$ = store.sliceUpdate(state => state.foo.bar.qux);
onQux$.pipe(take(1)).subscribe(value => {
expect(value).toEqual(patch2.foo.bar.qux);
done();
});
store.patch(patch1);
store.patch(patch2);
});
});
describe('reset', () => {
it('should reset state to initialState', () => {
const store = new InternalStore(mockInitialState);
store.patch(patch1);
store.reset();
expect(store.state).toEqual(mockInitialState);
});
});
});

@ -6,6 +6,7 @@ export * from './factory-utils';
export * from './form-utils';
export * from './generator-utils';
export * from './initial-utils';
export * from './internal-store-utils';
export * from './lazy-load-utils';
export * from './localization-utils';
export * from './multi-tenancy-utils';

@ -0,0 +1,36 @@
import compare from 'just-compare';
import { BehaviorSubject, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
import { DeepPartial } from '../models';
import { deepMerge } from './object-utils';
export class InternalStore<State> {
private state$ = new BehaviorSubject<State>(this.initialState);
private update$ = new Subject<DeepPartial<State>>();
get state() {
return this.state$.value;
}
sliceState = <Slice>(
selector: (state: State) => Slice,
compareFn: (s1: Slice, s2: Slice) => boolean = compare,
) => this.state$.pipe(map(selector), distinctUntilChanged(compareFn));
sliceUpdate = <Slice>(
selector: (state: DeepPartial<State>) => Slice,
filterFn = (x: Slice) => x !== undefined,
) => this.update$.pipe(map(selector), filter(filterFn));
constructor(private initialState: State) {}
patch(state: DeepPartial<State>) {
this.state$.next(deepMerge(this.state, state));
this.update$.next(state);
}
reset() {
this.patch(this.initialState);
}
}
Loading…
Cancel
Save