Skip to content

Commit

Permalink
fix(store): allow primitive types (#2967)
Browse files Browse the repository at this point in the history
Closes #2966
  • Loading branch information
timdeschryver authored Mar 12, 2021
1 parent a33705c commit eecc8ce
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 4 deletions.
37 changes: 36 additions & 1 deletion modules/store/spec/reducer_creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ describe('classes/reducer', function (): void {
describe('base', () => {
const bar = createAction('[foobar] BAR', props<{ bar: number }>());
const foo = createAction('[foobar] FOO', props<{ foo: number }>());
const withDefaultParameter = createAction(
'[foobar] withDefaultParameter',
(foo: number = 4, bar: number = 7) => ({
foo,
bar,
})
);

describe('on', () => {
it('should support reducers with multiple actions', () => {
Expand All @@ -32,7 +39,11 @@ describe('classes/reducer', function (): void {
const fooBarReducer = createReducer(
{} as State,
on(foo, (state, { foo }) => ({ ...state, foo })),
on(bar, (state, { bar }) => ({ ...state, bar }))
on(bar, (state, { bar }) => ({ ...state, bar })),
on(withDefaultParameter, (_state, { type: _, foo, bar }) => ({
foo,
bar,
}))
);

expect(typeof fooBarReducer).toEqual('function');
Expand All @@ -45,6 +56,30 @@ describe('classes/reducer', function (): void {

state = fooBarReducer(state, bar({ bar: 54 }));
expect(state).toEqual({ foo: 42, bar: 54 });

state = fooBarReducer(state, withDefaultParameter());
expect(state).toEqual({ foo: 4, bar: 7 });
});

it('should create a primitive reducer', () => {
const initialState: number = 0;
const setState = createAction('setState', props<{ value: number }>());
const resetState = createAction('resetState');

const primitiveReducer = createReducer(
initialState,
on(setState, (_state, { value }) => value),
on(resetState, () => initialState)
);

let state = primitiveReducer(undefined, { type: 'UNKNOWN' });
expect(state).toEqual(0);

state = primitiveReducer(state, setState({ value: 7 }));
expect(state).toEqual(7);

state = primitiveReducer(state, resetState);
expect(state).toEqual(initialState);
});

it('should support reducers with multiple actions', () => {
Expand Down
2 changes: 1 addition & 1 deletion modules/store/spec/types/action_creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe('createAction()', () => {
);
});

it('should allow foempt', () => {
it('should allow default parameters', () => {
expectSnippet(`
const foo = createAction('FOO', (bar = 3) => ({bar}));
`).toSucceed();
Expand Down
50 changes: 49 additions & 1 deletion modules/store/spec/types/reducer_creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,61 @@ import { compilerOptions } from './utils';
describe('createReducer()', () => {
const expectSnippet = expecter(
(code) => `
import {createAction, props, on} from '@ngrx/store';
import {createAction, createReducer, on, props} from '@ngrx/store';
${code}
`,
compilerOptions()
);

describe('createReducer()', () => {
it('should support objects', () => {
expectSnippet(`
interface State { name: string };
const initialState: State = { name: 'sarah' };
const setAction = createAction('set', props<{ value: State }>());
const resetAction = createAction('reset');
const reducer = createReducer(
initialState,
on(setAction, (_, { value }) => value),
on(resetAction, () => initialState),
);
`).toInfer('reducer', 'ActionReducer<State, Action>');
});

it('should support arrays', () => {
expectSnippet(`
const initialState: string[] = [];
const setAction = createAction('set', props<{ value: string[] }>());
const resetAction = createAction('reset');
const reducer = createReducer(
initialState,
on(setAction, (_, { value }) => value),
on(resetAction, () => initialState),
);
`).toInfer('reducer', 'ActionReducer<string[], Action>');
});

it('should support primitive types', () => {
expectSnippet(`
const initialState: number = 0;
const setAction = createAction('set', props<{ value: number }>());
const resetAction = createAction('reset');
const reducer = createReducer(
initialState,
on(setAction, (_, { value }) => value),
on(resetAction, () => initialState),
);
`).toInfer('reducer', 'ActionReducer<number, Action>');
});
});

describe('on()', () => {
it('should enforce action property types', () => {
expectSnippet(`
Expand Down
4 changes: 3 additions & 1 deletion modules/store/src/reducer_creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export interface ReducerTypes<

// Specialized Reducer that is aware of the Action type it needs to handle
export interface OnReducer<State, Creators extends readonly ActionCreator[]> {
(state: State, action: ActionType<Creators[number]>): { [P in keyof State]: State[P] };
(state: State, action: ActionType<Creators[number]>): State extends object
? { [P in keyof State]: State[P] }
: State;
}

/**
Expand Down

0 comments on commit eecc8ce

Please sign in to comment.