Skip to content

Commit

Permalink
test: Tests and cleanup for with-reselect package (#8)
Browse files Browse the repository at this point in the history
* Rename constructors to be shorter

* Add tests for dynamicSelectorFromReselect

* Add tests for reselectSelectorFromDynamic
  • Loading branch information
spautz committed Oct 26, 2020
1 parent be26b0c commit 1b96993
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 18 deletions.
12 changes: 6 additions & 6 deletions packages/with-reselect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@ Helper functions to make it easy to use [Dynamic Selectors](https://github.com/s

```
import {
createReselectSelectorFromDynamic,
createDynamicSelectorFromReselect,
reselectSelectorFromDynamic,
dynamicSelectorFromReselect,
wrapReselect,
} from '@dynamic-selectors/with-reselect';
```

#### `createReselectSelectorFromDynamic(dynamicSelector, params?)`
#### `reselectSelectorFromDynamic(dynamicSelector, params?)`

```javascript
const originalSelector = createDynamicSelector(...);
const newSelector = createReselectSelectorFromDynamic(originalSelector);
const newSelector = reselectSelectorFromDynamic(originalSelector);
```

#### `createDynamicSelectorFromReselect(reselectSelector)`
#### `dynamicSelectorFromReselect(reselectSelector)`

```javascript
const originalSelector = createSelector(...);
const newSelector = createDynamicSelectorFromReselect(originalSelector);
const newSelector = dynamicSelectorFromReselect(originalSelector);
```

#### `wrapReselect(reselectSelector)`
Expand Down
6 changes: 3 additions & 3 deletions packages/with-reselect/src/dynamicSelectorFromReselect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ const wrapReselect = <StateType = any, ReturnType = any>(
* If you make your own selector creator (via `dynamicSelectorForState`), you would create a similar "create..."
* function around it.
*/
const createDynamicSelectorFromReselect = <StateType = any, ReturnType = any>(
const dynamicSelectorFromReselect = <StateType = any, ReturnType = any>(
reselectSelectorFn: Selector<StateType, ReturnType>,
dynamicSelectorOptions: Parameters<typeof createDynamicSelector>[1],
dynamicSelectorOptions?: Parameters<typeof createDynamicSelector>[1],
) => {
return createDynamicSelector<ReturnType>(
wrapReselect(reselectSelectorFn),
dynamicSelectorOptions,
);
};

export { wrapReselect, createDynamicSelectorFromReselect };
export { wrapReselect, dynamicSelectorFromReselect };
4 changes: 2 additions & 2 deletions packages/with-reselect/src/reselectSelectorFromDynamic.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { DynamicSelectorArgsWithoutState, DynamicSelectorFn } from '@dynamic-selectors/core';

const createReselectSelectorFromDynamic = <StateType = any, ReturnType = any>(
const reselectSelectorFromDynamic = <StateType = any, ReturnType = any>(
dynamicSelectorFn: DynamicSelectorFn<ReturnType>,
...paramsAndOtherArgs: DynamicSelectorArgsWithoutState
) => {
return (state: StateType) => dynamicSelectorFn(state, ...paramsAndOtherArgs);
};

export { createReselectSelectorFromDynamic };
export { reselectSelectorFromDynamic };
7 changes: 0 additions & 7 deletions packages/with-reselect/tests/TODO.test.ts

This file was deleted.

81 changes: 81 additions & 0 deletions packages/with-reselect/tests/dynamicSelectorFromReselect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { createSelector } from 'reselect';

import { dynamicSelectorFromReselect } from '../src';
import DebugInfoCheckUtil from '../../core/tests/util/debugInfoCheckUtil';

describe('dynamicSelectorFromReselect', () => {
test('handles simple selector functions', () => {
const reselectSelector = (state: any) => state.a;
const dynamicSelector = dynamicSelectorFromReselect(reselectSelector);
const selectorCheck = new DebugInfoCheckUtil(dynamicSelector);

let state = { a: 1 };

// Normal run
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('run');

// Now it's cached by state
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('skipped');

state = { a: 1 };

// It's a new state, so Reselect returns a new value, but after we run we realize we didn't need to
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('phantom');

// Now it's cached by state
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('skipped');

state = { a: 2 };

// A full run because the accessed state value is new
expect(dynamicSelector(state)).toEqual(2);
selectorCheck.expectInvoked('run');

// But then it's cached again
expect(dynamicSelector(state)).toEqual(2);
selectorCheck.expectInvoked('skipped');
});

test('handles normal selector functions', () => {
const reselectorDependency = (state: any) => state.a;
const reselectSelector = createSelector(reselectorDependency, (depResult) => depResult);
const dynamicSelector = dynamicSelectorFromReselect(reselectSelector);

const selectorCheck = new DebugInfoCheckUtil(dynamicSelector);

let state = { a: 1 };

// Normal run
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('run');

// Now it's cached by state
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('skipped');

state = { a: 1 };

// It's a new state, so Reselect returns a new value, but after we run we realize we didn't need to
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('phantom');

// Cached again
expect(dynamicSelector(state)).toEqual(1);
selectorCheck.expectInvoked('skipped');

state = { a: 2 };

// Parent invokes child, child is DepChecked and found to be dirty, so parent runs (and invokes child again --
// but it's skipped now)
expect(dynamicSelector(state)).toEqual(2);
selectorCheck.expectInvoked('run');

// Cached again
expect(dynamicSelector(state)).toEqual(2);
selectorCheck.expectInvoked('skipped');
});
});
152 changes: 152 additions & 0 deletions packages/with-reselect/tests/reselectSelectorFromDynamic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { createSelector } from 'reselect';
import { createDynamicSelector } from '@dynamic-selectors/core';

import { reselectSelectorFromDynamic } from '../src';
import DebugInfoCheckUtil from '../../core/tests/util/debugInfoCheckUtil';

describe('reselectSelectorFromDynamic', () => {
test('dynamic selector as a Reselect dependency', () => {
const dynamicSelector = createDynamicSelector((getState) => {
return getState('a');
});
const reselectSelector = createSelector(dynamicSelector, (depResult) => depResult);
const selectorCheck = new DebugInfoCheckUtil(dynamicSelector);

let state = { a: 1 };

// Normal run
expect(reselectSelector(state)).toEqual(1);
selectorCheck.expectInvoked('run');
expect(reselectSelector.recomputations()).toEqual(1);

// Now it's cached by state
expect(reselectSelector(state)).toEqual(1);
selectorCheck.expectUntouched();
expect(reselectSelector.recomputations()).toEqual(1);

state = { a: 1 };

// Reselect invokes dynamic, dynamic is DepChecked, then skipped
expect(reselectSelector(state)).toEqual(1);
selectorCheck.expectInvoked('skipped');
expect(reselectSelector.recomputations()).toEqual(1);

// Cached again
expect(reselectSelector(state)).toEqual(1);
selectorCheck.expectUntouched();
expect(reselectSelector.recomputations()).toEqual(1);

state = { a: 2 };

// Reselect invokes dynamic, dynamic is found to be dirty, so it returns the new value
expect(reselectSelector(state)).toEqual(2);
selectorCheck.expectInvoked('run');
expect(reselectSelector.recomputations()).toEqual(2);

// Cached again
expect(reselectSelector(state)).toEqual(2);
selectorCheck.expectUntouched();
expect(reselectSelector.recomputations()).toEqual(2);
});

test('dynamic selector with params as a Reselect dependency', () => {
const dynamicSelector = createDynamicSelector((getState, path: string) => {
return getState(path);
});
const reselectSelectorA = createSelector(
reselectSelectorFromDynamic(dynamicSelector, 'a'),
(depResult) => depResult,
);
const reselectSelectorB = createSelector(
reselectSelectorFromDynamic(dynamicSelector, 'b'),
(depResult) => depResult,
);
const reselectSelectorC = createSelector(
reselectSelectorFromDynamic(dynamicSelector, 'c'),
(depResult) => depResult,
);
const dynamicSelectorCheckA = new DebugInfoCheckUtil(dynamicSelector, 'a');
const dynamicSelectorCheckB = new DebugInfoCheckUtil(dynamicSelector, 'b');
const dynamicSelectorCheckC = new DebugInfoCheckUtil(dynamicSelector, 'c');

let state = { a: 1, b: 1, c: 1 };

// Normal runs
expect(reselectSelectorA(state)).toEqual(1);
dynamicSelectorCheckA.expectInvoked('run');
dynamicSelectorCheckB.expectUntouched();
dynamicSelectorCheckC.expectUntouched();
expect(reselectSelectorB(state)).toEqual(1);
dynamicSelectorCheckA.expectUntouched();
dynamicSelectorCheckB.expectInvoked('run');
dynamicSelectorCheckC.expectUntouched();
expect(reselectSelectorC(state)).toEqual(1);
dynamicSelectorCheckA.expectUntouched();
dynamicSelectorCheckB.expectUntouched();
dynamicSelectorCheckC.expectInvoked('run');

expect(reselectSelectorA.recomputations()).toEqual(1);
expect(reselectSelectorB.recomputations()).toEqual(1);
expect(reselectSelectorC.recomputations()).toEqual(1);

// Now all are cached by state
expect(reselectSelectorA(state)).toEqual(1);
dynamicSelectorCheckA.expectUntouched();
expect(reselectSelectorB(state)).toEqual(1);
dynamicSelectorCheckB.expectUntouched();
expect(reselectSelectorC(state)).toEqual(1);
dynamicSelectorCheckC.expectUntouched();

expect(reselectSelectorA.recomputations()).toEqual(1);
expect(reselectSelectorB.recomputations()).toEqual(1);
expect(reselectSelectorC.recomputations()).toEqual(1);

state = { a: 1, b: 1, c: 2 };

// A and B are cached because the accessed state value didn't change; C re-ran
expect(reselectSelectorA(state)).toEqual(1);
dynamicSelectorCheckA.expectInvoked('skipped');
expect(reselectSelectorB(state)).toEqual(1);
dynamicSelectorCheckB.expectInvoked('skipped');
expect(reselectSelectorC(state)).toEqual(2);
dynamicSelectorCheckC.expectInvoked('run');

expect(reselectSelectorA.recomputations()).toEqual(1);
expect(reselectSelectorB.recomputations()).toEqual(1);
expect(reselectSelectorC.recomputations()).toEqual(2);

// Now all are cached by state
expect(reselectSelectorA(state)).toEqual(1);
dynamicSelectorCheckA.expectUntouched();
expect(reselectSelectorB(state)).toEqual(1);
dynamicSelectorCheckB.expectUntouched();
expect(reselectSelectorC(state)).toEqual(2);
dynamicSelectorCheckC.expectUntouched();

expect(reselectSelectorA.recomputations()).toEqual(1);
expect(reselectSelectorB.recomputations()).toEqual(1);
expect(reselectSelectorC.recomputations()).toEqual(2);

state = { a: 2, b: 2, c: 3 };

// Full runs because the accessed state value is new
expect(reselectSelectorA(state)).toEqual(2);
dynamicSelectorCheckA.expectInvoked('run');
expect(reselectSelectorB(state)).toEqual(2);
dynamicSelectorCheckB.expectInvoked('run');
expect(reselectSelectorC(state)).toEqual(3);
dynamicSelectorCheckC.expectInvoked('run');

expect(reselectSelectorA.recomputations()).toEqual(2);
expect(reselectSelectorB.recomputations()).toEqual(2);
expect(reselectSelectorC.recomputations()).toEqual(3);

// But now everything is cached again
expect(reselectSelectorA(state)).toEqual(2);
dynamicSelectorCheckA.expectUntouched();
expect(reselectSelectorB(state)).toEqual(2);
dynamicSelectorCheckB.expectUntouched();
expect(reselectSelectorC(state)).toEqual(3);
dynamicSelectorCheckC.expectUntouched();
});
});

0 comments on commit 1b96993

Please sign in to comment.