diff --git a/modules/store/spec/selector.spec.ts b/modules/store/spec/selector.spec.ts index 68aefe0635..5862b19c95 100644 --- a/modules/store/spec/selector.spec.ts +++ b/modules/store/spec/selector.spec.ts @@ -119,6 +119,92 @@ describe('Selectors', () => { }); }); + describe('createSelector with arrays', () => { + it('should deliver the value of selectors to the projection function', () => { + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector([incrementOne, incrementTwo], projectFn)( + {} + ); + + expect(projectFn).toHaveBeenCalledWith(countOne, countTwo); + }); + + it('should be possible to test a projector fn independent from the selectors it is composed of', () => { + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector([incrementOne, incrementTwo], projectFn); + + selector.projector('', ''); + + expect(incrementOne).not.toHaveBeenCalled(); + expect(incrementTwo).not.toHaveBeenCalled(); + expect(projectFn).toHaveBeenCalledWith('', ''); + }); + + it('should call the projector function only when the value of a dependent selector change', () => { + const firstState = { first: 'state', unchanged: 'state' }; + const secondState = { second: 'state', unchanged: 'state' }; + const neverChangingSelector = jasmine + .createSpy('unchangedSelector') + .and.callFake((state: any) => { + return state.unchanged; + }); + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector([neverChangingSelector], projectFn); + + selector(firstState); + selector(secondState); + + expect(projectFn).toHaveBeenCalledTimes(1); + }); + + it('should memoize the function', () => { + const firstState = { first: 'state' }; + const secondState = { second: 'state' }; + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector( + [incrementOne, incrementTwo, incrementThree], + projectFn + ); + + selector(firstState); + selector(firstState); + selector(firstState); + selector(secondState); + + expect(incrementOne).toHaveBeenCalledTimes(2); + expect(incrementTwo).toHaveBeenCalledTimes(2); + expect(incrementThree).toHaveBeenCalledTimes(2); + expect(projectFn).toHaveBeenCalledTimes(2); + }); + + it('should allow you to release memoized arguments', () => { + const state = { first: 'state' }; + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector([incrementOne], projectFn); + + selector(state); + selector(state); + selector.release(); + selector(state); + selector(state); + + expect(projectFn).toHaveBeenCalledTimes(2); + }); + + it('should recursively release ancestor selectors', () => { + const grandparent = createSelector([incrementOne], a => a); + const parent = createSelector([grandparent], a => a); + const child = createSelector([parent], a => a); + spyOn(grandparent, 'release').and.callThrough(); + spyOn(parent, 'release').and.callThrough(); + + child.release(); + + expect(grandparent.release).toHaveBeenCalled(); + expect(parent.release).toHaveBeenCalled(); + }); + }); + describe('createFeatureSelector', () => { let featureName = '@ngrx/router-store'; let featureSelector: (state: any) => number; diff --git a/modules/store/src/selector.ts b/modules/store/src/selector.ts index 8f42733dd3..5aaefebb65 100644 --- a/modules/store/src/selector.ts +++ b/modules/store/src/selector.ts @@ -43,17 +43,29 @@ export function createSelector( s1: Selector, projector: (S1: S1) => Result ): MemoizedSelector; +export function createSelector( + selectors: [Selector], + projector: (s1: S1) => Result +): MemoizedSelector; export function createSelector( s1: Selector, s2: Selector, projector: (s1: S1, s2: S2) => Result ): MemoizedSelector; +export function createSelector( + selectors: [Selector, Selector], + projector: (s1: S1, s2: S2) => Result +): MemoizedSelector; export function createSelector( s1: Selector, s2: Selector, s3: Selector, projector: (s1: S1, s2: S2, s3: S3) => Result ): MemoizedSelector; +export function createSelector( + selectors: [Selector, Selector, Selector], + projector: (s1: S1, s2: S2, s3: S3) => Result +): MemoizedSelector; export function createSelector( s1: Selector, s2: Selector, @@ -61,6 +73,15 @@ export function createSelector( s4: Selector, projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result ): MemoizedSelector; +export function createSelector( + selectors: [ + Selector, + Selector, + Selector, + Selector + ], + projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result +): MemoizedSelector; export function createSelector( s1: Selector, s2: Selector, @@ -69,6 +90,16 @@ export function createSelector( s5: Selector, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result ): MemoizedSelector; +export function createSelector( + selectors: [ + Selector, + Selector, + Selector, + Selector, + Selector + ], + projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result +): MemoizedSelector; export function createSelector( s1: Selector, s2: Selector, @@ -78,6 +109,17 @@ export function createSelector( s6: Selector, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result ): MemoizedSelector; +export function createSelector( + selectors: [ + Selector, + Selector, + Selector, + Selector, + Selector, + Selector + ], + projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result +): MemoizedSelector; export function createSelector( s1: Selector, s2: Selector, @@ -88,6 +130,18 @@ export function createSelector( s7: Selector, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result ): MemoizedSelector; +export function createSelector( + selectors: [ + Selector, + Selector, + Selector, + Selector, + Selector, + Selector, + Selector + ], + projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result +): MemoizedSelector; export function createSelector( s1: Selector, s2: Selector, @@ -108,7 +162,35 @@ export function createSelector( s8: S8 ) => Result ): MemoizedSelector; -export function createSelector(...args: any[]): Selector { +export function createSelector( + selectors: [ + Selector, + Selector, + Selector, + Selector, + Selector, + Selector, + Selector, + Selector + ], + projector: ( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + s5: S5, + s6: S6, + s7: S7, + s8: S8 + ) => Result +): MemoizedSelector; +export function createSelector(...input: any[]): Selector { + let args = input; + if (Array.isArray(args[0])) { + const [head, ...tail] = args; + args = [...head, ...tail]; + } + const selectors = args.slice(0, args.length - 1); const projector = args[args.length - 1]; const memoizedSelectors = selectors.filter(