Skip to content

Commit

Permalink
feat: rework updateRecursive to support different parameter combina…
Browse files Browse the repository at this point in the history
…tions for update function objects (i.e. single object, array of objects, and rest parameters)
  • Loading branch information
MrWolfZ committed Apr 15, 2018
1 parent 03ac1b2 commit 96121c3
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 60 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This release requires TypeScript >=2.8.0 for the conditional type support.
* use conditional types to infer the type of child controls
* rework `updateArray` to support different parameter combinations for update functions (i.e. single function, array of functions, and rest parameters) ([f82abf8](https://github.com/MrWolfZ/ngrx-forms/commit/f82abf8))
* rework `updateGroup` to support different parameter combinations for update function objects (i.e. single object, array of objects, and rest parameters) which reduces the probability of false type inference results ([0bb1ca7](https://github.com/MrWolfZ/ngrx-forms/commit/0bb1ca7))
* rework `updateRecursive` to support different parameter combinations for update function objects (i.e. single object, array of objects, and rest parameters)

<a name="2.3.2"></a>
### 2.3.2
Expand Down
104 changes: 101 additions & 3 deletions src/update-function/update-recursive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createFormArrayState, createFormControlState, createFormGroupState } from '../state';
import { AbstractControlState, createFormArrayState, createFormControlState, createFormGroupState } from '../state';
import { FORM_CONTROL_ID, FORM_CONTROL_INNER2_ID, FORM_CONTROL_INNER_ID } from './test-util';
import { updateRecursive } from './update-recursive';
import { ProjectFn2 } from './util';

describe(updateRecursive.name, () => {
it('should apply the provided functions to controls', () => {
Expand Down Expand Up @@ -78,13 +79,110 @@ describe(updateRecursive.name, () => {
const expected1 = { ...state.controls[0], value: 'D' };
const expected2 = { ...state.controls[1], value: 'E' };
const expected3 = { ...state.controls[2], value: 'F' };
let resultState = updateRecursive<typeof state.value>(s => s.value === 'A' ? expected1 : s.value === 'B' ? expected3 : s)(state);
resultState = updateRecursive<typeof state.value>(s => s.value === 'F' ? expected2 : s.value === 'C' ? expected3 : s)(resultState);
const resultState = updateRecursive<typeof state.value>(
s => s.value === 'A' ? expected1 : s.value === 'B' ? expected3 : s,
s => s.value === 'F' ? expected2 : s.value === 'C' ? expected3 : s,
)(state);
expect(resultState.controls[0]).toBe(expected1);
expect(resultState.controls[1]).toBe(expected2);
expect(resultState.controls[2]).toBe(expected3);
});

it('should apply multiple provided functions as param array one after another', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['A', 'B', 'C']);
const expected1 = { ...state.controls[0], value: 'D' };
const expected2 = { ...state.controls[1], value: 'E' };
const expected3 = { ...state.controls[2], value: 'F' };
const updateFunction1: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>> =
s => s.value === 'A' ? expected1 : s.value === 'B' ? expected3 : s;
const updateFunction2: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>> =
s => s.value === 'F' ? expected2 : s.value === 'C' ? expected3 : s;
const resultState = updateRecursive<typeof state.value>(
updateFunction1,
[updateFunction2] as any,
)(state);
expect(resultState.controls[0]).toBe(expected1);
expect(resultState.controls[1]).toBe(expected2);
expect(resultState.controls[2]).toBe(expected3);
});

it('should apply multiple provided functions as array one after another', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['A', 'B', 'C']);
const expected1 = { ...state.controls[0], value: 'D' };
const expected2 = { ...state.controls[1], value: 'E' };
const expected3 = { ...state.controls[2], value: 'F' };
const resultState = updateRecursive<typeof state.value>([
s => s.value === 'A' ? expected1 : s.value === 'B' ? expected3 : s,
s => s.value === 'F' ? expected2 : s.value === 'C' ? expected3 : s,
])(state);
expect(resultState.controls[0]).toBe(expected1);
expect(resultState.controls[1]).toBe(expected2);
expect(resultState.controls[2]).toBe(expected3);
});

it('should apply multiple provided functions one after another uncurried', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['A', 'B', 'C']);
const expected1 = { ...state.controls[0], value: 'D' };
const expected2 = { ...state.controls[1], value: 'E' };
const expected3 = { ...state.controls[2], value: 'F' };
const resultState = updateRecursive<typeof state.value>(
state,
s => s.value === 'A' ? expected1 : s.value === 'B' ? expected3 : s,
s => s.value === 'F' ? expected2 : s.value === 'C' ? expected3 : s,
);
expect(resultState.controls[0]).toBe(expected1);
expect(resultState.controls[1]).toBe(expected2);
expect(resultState.controls[2]).toBe(expected3);
});

it('should apply multiple provided functions as param array one after another uncurried', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['A', 'B', 'C']);
const expected1 = { ...state.controls[0], value: 'D' };
const expected2 = { ...state.controls[1], value: 'E' };
const expected3 = { ...state.controls[2], value: 'F' };
const updateFunction1: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>> =
s => s.value === 'A' ? expected1 : s.value === 'B' ? expected3 : s;
const updateFunction2: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>> =
s => s.value === 'F' ? expected2 : s.value === 'C' ? expected3 : s;
const resultState = updateRecursive<typeof state.value>(
state,
updateFunction1,
[updateFunction2] as any,
);
expect(resultState.controls[0]).toBe(expected1);
expect(resultState.controls[1]).toBe(expected2);
expect(resultState.controls[2]).toBe(expected3);
});

it('should apply multiple provided functions as array one after another uncurried', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['A', 'B', 'C']);
const expected1 = { ...state.controls[0], value: 'D' };
const expected2 = { ...state.controls[1], value: 'E' };
const expected3 = { ...state.controls[2], value: 'F' };
const resultState = updateRecursive<typeof state.value>(
state,
[
s => s.value === 'A' ? expected1 : s.value === 'B' ? expected3 : s,
s => s.value === 'F' ? expected2 : s.value === 'C' ? expected3 : s,
],
);
expect(resultState.controls[0]).toBe(expected1);
expect(resultState.controls[1]).toBe(expected2);
expect(resultState.controls[2]).toBe(expected3);
});

it('should not modify state if no update function is provided', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['A', 'B', 'C']);
const resultState = updateRecursive<typeof state.value>([])(state);
expect(resultState).toBe(state);
});

it('should not modify state if no update function is provided uncurried', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['A', 'B', 'C']);
const resultState = updateRecursive<typeof state.value>(state, []);
expect(resultState).toBe(state);
});

it('should pass top level state itself as the second parameter for top level state', () => {
const state = createFormArrayState(FORM_CONTROL_ID, ['', '']);
updateRecursive<typeof state.value>((c, p) => {
Expand Down
104 changes: 47 additions & 57 deletions src/update-function/update-recursive.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import {
AbstractControlState,
FormArrayState,
FormControlState,
FormControlValueTypes,
FormGroupState,
InferredControlState,
isArrayState,
isFormState,
isGroupState,
} from '../state';
import { updateArray } from './update-array';
import { StateUpdateFns, updateGroup } from './update-group';
import { ProjectFn2 } from './util';
import { ensureState, ProjectFn2 } from './util';

function updateRecursiveSingle(parent: AbstractControlState<any>, updateFn: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>) {
return (state: AbstractControlState<any>): AbstractControlState<any> => {
Expand All @@ -32,40 +29,35 @@ function updateRecursiveSingle(parent: AbstractControlState<any>, updateFn: Proj
}

/**
* This update function takes a variable number of update functions and returns
* a projection function that applies all update functions one after another to
* a form state.
* This update function takes a form array state and a variable number of
* update functions applies all update functions one after another to the
* state recursively, i.e. the function is applied to the state's children,
* their children etc.
*
* The following example uses this function to validate all controls in a
* group as required.
* group or array as required.
*
* ```typescript
* const updateFn = updateRecursive(validate(required));
* const updatedState = updateFn(state);
* const updatedState = updateRecursive(
* state,
* validate(required),
* );
* ```
*/
export function updateRecursive<TValue>(
state: AbstractControlState<TValue>,
updateFn: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>,
...updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[]
): (state: InferredControlState<TValue>) => InferredControlState<TValue>;

/**
* This update function takes a form control state and a variable number of
* update functions applies all update functions one after another to the
* state.
*/
export function updateRecursive<TValue extends FormControlValueTypes>(
state: FormControlState<TValue>,
...updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[]
): FormControlState<TValue>;
): InferredControlState<TValue>;

/**
* This update function takes a form array state and a variable number of
* This update function takes a form array state and an array of
* update functions applies all update functions one after another to the
* state recursively, i.e. the function is applied to the state's children,
* their children etc.
*
* The following example uses this function to validate all controls in an
* array as required.
* The following example uses this function to validate all controls in a
* group or array as required.
*
* ```typescript
* const updatedState = updateRecursive(
Expand All @@ -75,62 +67,60 @@ export function updateRecursive<TValue extends FormControlValueTypes>(
* ```
*/
export function updateRecursive<TValue>(
state: FormArrayState<TValue>,
...updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[]
): FormArrayState<TValue>;
state: AbstractControlState<TValue>,
updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[],
): InferredControlState<TValue>;

/**
* This update function takes a form group state and a variable number of
* update functions applies all update functions one after another to the
* state recursively, i.e. the function is applied to the state's children,
* their children etc.
* This update function takes a variable number of update functions and returns
* a projection function that applies all update functions one after another to
* a form state.
*
* The following example uses this function to validate all controls in a
* group as required.
*
* ```typescript
* const updatedState = updateRecursive(
* state,
* validate(required),
* );
* const updateFn = updateRecursive(validate(required));
* const updatedState = updateFn(state);
* ```
*/
export function updateRecursive<TValue>(
state: FormGroupState<TValue>,
updateFn: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>,
...updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[]
): FormGroupState<TValue>;
): (state: AbstractControlState<TValue>) => InferredControlState<TValue>;

/**
* This update function takes a form state and a variable number of update
* functions applies all update functions one after another to the state
* recursively, i.e. the function is applied to the state's children, their
* children etc.
* This update function takes an array of update functions and returns
* a projection function that applies all update functions one after another to
* a form state.
*
* The following example uses this function to validate all controls in a
* group as required.
*
* ```typescript
* const updatedState = updateRecursive(
* state,
* validate(required),
* );
* const updateFn = updateRecursive(validate(required));
* const updatedState = updateFn(state);
* ```
*/
export function updateRecursive<TValue>(
state: InferredControlState<TValue>,
...updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[]
): InferredControlState<TValue>;
updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[],
): (state: AbstractControlState<TValue>) => InferredControlState<TValue>;

export function updateRecursive<TValue>(
stateOrFunction: InferredControlState<TValue> | ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>,
...updateFnArr: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[]
stateOrFunctionOrFunctionArray:
| AbstractControlState<TValue>
| ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>
| ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[],
updateFnOrUpdateFnArr?: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>> | ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[],
...rest: ProjectFn2<AbstractControlState<any>, AbstractControlState<any>>[]
) {
if (typeof stateOrFunction !== 'function') {
const [first, ...rest] = updateFnArr;
return updateRecursive<TValue>(first, ...rest)(stateOrFunction);
if (isFormState(stateOrFunctionOrFunctionArray)) {
const updateFnArr = Array.isArray(updateFnOrUpdateFnArr) ? updateFnOrUpdateFnArr : [updateFnOrUpdateFnArr!];
return updateFnArr.concat(...rest)
.reduce((s, updateFn) => updateRecursiveSingle(stateOrFunctionOrFunctionArray, updateFn)(s), stateOrFunctionOrFunctionArray);
}

return (state: InferredControlState<TValue>): InferredControlState<TValue> => {
return [stateOrFunction as any, ...updateFnArr].reduce((s, updateFn) => updateRecursiveSingle(state, updateFn)(s), state);
};
let updateFnArr = Array.isArray(stateOrFunctionOrFunctionArray) ? stateOrFunctionOrFunctionArray : [stateOrFunctionOrFunctionArray];
updateFnArr = updateFnOrUpdateFnArr === undefined ? updateFnArr : updateFnArr.concat(updateFnOrUpdateFnArr);
return (s: AbstractControlState<TValue>) => updateRecursive<TValue>(ensureState(s), updateFnArr.concat(rest));
}

0 comments on commit 96121c3

Please sign in to comment.