Skip to content

Commit

Permalink
feat: allow properties in selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
timdeschryver committed Jul 19, 2018
1 parent 8f05f1f commit a3662b7
Show file tree
Hide file tree
Showing 3 changed files with 471 additions and 40 deletions.
199 changes: 199 additions & 0 deletions modules/store/spec/selector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,106 @@ describe('Selectors', () => {
});
});

describe('createSelector with props', () => {
it('should deliver the value of selectors to the projection function', () => {
const projectFn = jasmine.createSpy('projectionFn');

const selector = createSelector(
incrementOne,
incrementTwo,
(state: any, props: any) => props.value,
projectFn
);

selector({}, { value: 47 });
expect(projectFn).toHaveBeenCalledWith(countOne, countTwo, 47);
});

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,
(state: any, props: any) => {
fail(`Shouldn't be called`);
return props.value;
},
projectFn
);
selector.projector('', '', 47);

expect(incrementOne).not.toHaveBeenCalled();
expect(incrementTwo).not.toHaveBeenCalled();
expect(projectFn).toHaveBeenCalledWith('', '', 47);
});

it('should call the projector function when the property changes', () => {
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
incrementOne,
(state: any, props: any) => props.value,
projectFn
);

const state1 = { foo: 'bar' };
const props1 = { foo: 'bar' };
selector(state1, props1);
selector(state1, props1);
expect(projectFn).toHaveBeenCalledTimes(1);

const props2 = { foo: 'bar2' };
selector(state1, props2);
expect(projectFn).toHaveBeenCalledTimes(2);
});

it('should memoize the function', () => {
let counter = 0;

const firstState = { first: 'state' };
const firstProps = { foo: 'first' };
const secondProps = { foo: 'second' };

const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
incrementOne,
incrementTwo,
(state: any, props: any) => {
counter++;
return props;
},
projectFn
);

selector(firstState, firstProps);
selector(firstState, firstProps);
selector(firstState, firstProps);
selector(firstState, secondProps);
selector(firstState, secondProps);

expect(counter).toBe(2);
expect(projectFn).toHaveBeenCalledTimes(2);
});

it('should allow you to release memoized arguments', () => {
const state = { first: 'state' };
const props = { first: 'props' };
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
incrementOne,
(state: any, props: any) => props,
projectFn
);

selector(state, props);
selector(state, props);
selector.release();
selector(state, props);
selector(state, props);

expect(projectFn).toHaveBeenCalledTimes(2);
});
});

describe('createSelector with arrays', () => {
it('should deliver the value of selectors to the projection function', () => {
const projectFn = jasmine.createSpy('projectionFn');
Expand Down Expand Up @@ -209,6 +309,105 @@ describe('Selectors', () => {
});
});

describe('createSelector with arrays and props', () => {
it('should deliver the value of selectors to the projection function', () => {
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
[incrementOne, incrementTwo, (state: any, props: any) => props.value],
projectFn
)({}, { value: 47 });

expect(projectFn).toHaveBeenCalledWith(countOne, countTwo, 47);
});

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,
(state: any, props: any) => {
fail(`Shouldn't be called`);
return props.value;
},
],
projectFn
);

selector.projector('', '', 47);

expect(incrementOne).not.toHaveBeenCalled();
expect(incrementTwo).not.toHaveBeenCalled();
expect(projectFn).toHaveBeenCalledWith('', '', 47);
});

it('should call the projector function when the property changes', () => {
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
[incrementOne, (state: any, props: any) => props.value],
projectFn
);

const state1 = { foo: 'bar' };
const props1 = { foo: 'bar' };
selector(state1, props1);
selector(state1, props1);
expect(projectFn).toHaveBeenCalledTimes(1);

const props2 = { foo: 'bar2' };
selector(state1, props2);
expect(projectFn).toHaveBeenCalledTimes(2);
});

it('should memoize the function', () => {
let counter = 0;

const firstState = { first: 'state' };
const firstProps = { foo: 'first' };
const secondProps = { foo: 'second' };

const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
[
incrementOne,
incrementTwo,
(state: any, props: any) => {
counter++;
return props;
},
],
projectFn
);

selector(firstState, firstProps);
selector(firstState, firstProps);
selector(firstState, firstProps);
selector(firstState, secondProps);
selector(firstState, secondProps);

expect(counter).toBe(2);
expect(projectFn).toHaveBeenCalledTimes(2);
});

it('should allow you to release memoized arguments', () => {
const state = { first: 'state' };
const props = { first: 'props' };
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
[incrementOne, (state: any, props: any) => props],
projectFn
);

selector(state, props);
selector(state, props);
selector.release();
selector(state, props);
selector(state, props);

expect(projectFn).toHaveBeenCalledTimes(2);
});
});

describe('createFeatureSelector', () => {
let featureName = '@ngrx/router-store';
let featureSelector: (state: any) => number;
Expand Down
9 changes: 6 additions & 3 deletions modules/store/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export interface StoreFeature<T, V extends Action = Action> {
metaReducers?: MetaReducer<T, V>[];
}

export interface Selector<T, V> {
(state: T): V;
}
export type Selector<State, Result> = (state: State) => Result;

export type SelectorWithProperty<State, Property, Result> = (
state: State,
props: Property
) => Result;
Loading

0 comments on commit a3662b7

Please sign in to comment.