Skip to content

Commit

Permalink
refactor: minor improvements (#218)
Browse files Browse the repository at this point in the history
* refactor: minor improvements

* test: review namings
  • Loading branch information
toomuchdesign authored Dec 11, 2023
1 parent 4d4c82c commit f2cbea6
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 252 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![Npm downloads][npm-downloads-badge]][npm]
[![Test coverage report][coveralls-badge]][coveralls]

From [v5](https://github.com/reduxjs/reselect/releases/tag/v5.0.1), `reselect` provides options to implement natively you custom memoization caching solution via `createSelector` options. Most of the features `re-reselect` used to enable should be now available natively in `reselect`. `re-reselect` will try to support `reselect` v5+ for backward compatibility reasons.
From [v5](https://github.com/reduxjs/reselect/releases/tag/v5.0.1), `reselect` provides the ability to natively implement custom memoization/caching solutions via `createSelector` options. Most of the features `re-reselect` used to enable should be now natively available in `reselect`. `re-reselect` will try to support `reselect` v5+ for backward compatibility reasons.

`re-reselect` is a lightweight wrapper around **[Reselect][reselect]** meant to enhance selectors with **deeper memoization** and **cache management**.

Expand Down Expand Up @@ -455,8 +455,9 @@ Get `keySelector` for utility compositions or testing.

## Todo's

- Improve TS tests readability
- More examples
- Improve tests readability
- Port to native TS based on reselect v5 approach
- Find out whether `re-reselect` should be deprecated in favour of `reselect` memoization/cache options

## Contributors

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:typescript": "tsc --noEmit",
"test:bundles": "npm run test:bundles:snapshot && npm run test:bundles:unit",
"test:bundles:unit": "jest ./src --config ./jest/es.config.js && jest ./src --config ./jest/cjs.config.js && jest ./src --config ./jest/umd.config.js",
"test:bundles:snapshot": "jest ./jest/bundles-snapshot.test.js",
"test:bundles:snapshot": "jest ./jest/bundles-snapshot.test.ts",
"test:source": "npm run test:typescript && npm run test -- --coverage",
"test:update": "npm run compile && npm run test:bundles:snapshot -- -u",
"clean": "rimraf dist",
Expand Down
186 changes: 98 additions & 88 deletions src/__tests__/createCachedSelector.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import * as reselect from 'reselect';
import createCachedSelectorAsDefault, {
createCachedSelector,
FlatObjectCache,
ICacheObject,
} from '../index';
import {createCachedSelector, FlatObjectCache, ICacheObject} from '../index';

// Cannot natively spyOn es module named exports
jest.mock('reselect', () => ({
Expand All @@ -16,73 +12,15 @@ const consoleWarnSpy = jest
.spyOn(global.console, 'warn')
.mockImplementation(() => {});

function selectorWithMockedResultFunc() {
return createCachedSelector([(arg1: string, arg2: string) => null], () => {})(
(arg1, arg2) => arg2 // keySelector
);
}

describe('createCachedSelector', () => {
describe('options', () => {
describe('as single function', () => {
it('accepts keySelector function', () => {
const keySelectorMock = () => {};
const cachedSelector = createCachedSelector(
() => {},
() => {}
)(keySelectorMock);

expect(cachedSelector.keySelector).toBe(keySelectorMock);
});
});

describe('as single object', () => {
it('accepts keySelector, cacheObject and selectorCreator options', () => {
const cachedSelector = createCachedSelector(
(arg1: string, arg2: string) => null,
() => {}
)({
keySelector: (arg1, arg2) => arg2,
cacheObject: new FlatObjectCache(),
selectorCreator: reselect.createSelector,
});

expect(cachedSelector.recomputations()).toBe(0);
cachedSelector('foo', 'bar');
cachedSelector('foo', 'bar');
expect(cachedSelector.recomputations()).toBe(1);
});

it('accepts keySelectorCreator option', () => {
const inputSelector = () => {};
const resultFunc = () => {};
const keySelector = () => {};
const generatedKeySelector = () => {};
const keySelectorCreatorMock = jest.fn(() => generatedKeySelector);

const cachedSelector = createCachedSelector(
inputSelector,
resultFunc
)({
keySelector,
keySelectorCreator: keySelectorCreatorMock,
});

expect(keySelectorCreatorMock).toHaveBeenCalledWith({
inputSelectors: [inputSelector],
resultFunc: resultFunc,
keySelector: keySelector,
});
expect(cachedSelector.keySelector).toBe(generatedKeySelector);
});
});
});

describe('created selector', () => {
describe('cache retention', () => {
describe('calls producing identical cacheKey', () => {
it('creates and use the same cached selector', () => {
const cachedSelector = selectorWithMockedResultFunc();
const cachedSelector = createCachedSelector(
[(state: string, param1: string) => null],
() => {}
)({keySelector: (state, param1) => param1});
cachedSelector('foo', 'bar');
cachedSelector('foo', 'bar');

Expand All @@ -93,7 +31,10 @@ describe('createCachedSelector', () => {

describe('calls producing 2 different cacheKey', () => {
it('creates 2 selectors only and produce 2 recomputations', () => {
const cachedSelector = selectorWithMockedResultFunc();
const cachedSelector = createCachedSelector(
[(state: string, param1: string) => null],
() => {}
)({keySelector: (state, param1) => param1});
cachedSelector('foo', 'bar');
cachedSelector('foo', 'moo');
cachedSelector('foo', 'bar');
Expand Down Expand Up @@ -121,7 +62,7 @@ describe('createCachedSelector', () => {
() => {},
() => {}
)({
keySelector: arg1 => arg1,
keySelector: state => state,
cacheObject: cacheObjectMock,
});

Expand All @@ -143,7 +84,7 @@ describe('createCachedSelector', () => {
() => {},
() => {}
)({
keySelector: arg1 => arg1,
keySelector: state => state,
cacheObject: cacheObjectMock,
});

Expand All @@ -155,7 +96,7 @@ describe('createCachedSelector', () => {
});

describe('returns false', () => {
it('returns "undefined" and call "console.warn"', () => {
it('returns "undefined" and calls "console.warn"', () => {
const cacheObjectMock = new FlatObjectCache();
cacheObjectMock.isValidCacheKey = () => false;
cacheObjectMock.get = jest.fn();
Expand All @@ -164,7 +105,7 @@ describe('createCachedSelector', () => {
() => {},
() => {}
)({
keySelector: arg1 => arg1,
keySelector: state => state,
cacheObject: cacheObjectMock,
});

Expand All @@ -182,9 +123,9 @@ describe('createCachedSelector', () => {
describe('getMatchingSelector()', () => {
it('returns underlying reselect selector for a given cache key', () => {
const cachedSelector = createCachedSelector(
(arg1: string, arg2: number) => {},
(state: string, param1: number) => {},
() => {}
)((arg1, arg2) => arg2);
)((state, param1) => param1);

// Retrieve result from re-reselect cached selector
const actualResult = cachedSelector('foo', 1);
Expand All @@ -197,7 +138,10 @@ describe('createCachedSelector', () => {
});

it('returns "undefined" when given cache key doesn\'t match any cache entry', () => {
const cachedSelector = selectorWithMockedResultFunc();
const cachedSelector = createCachedSelector(
[(state: string, param1: string) => null],
() => {}
)({keySelector: (state, param1) => param1});

const actual = cachedSelector.getMatchingSelector(
'foo',
Expand All @@ -211,7 +155,10 @@ describe('createCachedSelector', () => {

describe('removeMatchingSelector()', () => {
it('sets the matching cache entry to "undefined"', () => {
const cachedSelector = selectorWithMockedResultFunc();
const cachedSelector = createCachedSelector(
[(state: string, param1: string) => null],
() => {}
)({keySelector: (state, param1) => param1});

cachedSelector('foo', 'bar'); // add to cache
cachedSelector('foo', 'moo'); // add to cache
Expand All @@ -233,7 +180,10 @@ describe('createCachedSelector', () => {

describe('clearCache()', () => {
it('resets cache', () => {
const cachedSelector = selectorWithMockedResultFunc();
const cachedSelector = createCachedSelector(
[(state: string, param1: string) => null],
() => {}
)({keySelector: (state, param1) => param1});

cachedSelector('foo', 'bar'); // add to cache
cachedSelector.clearCache();
Expand All @@ -245,7 +195,10 @@ describe('createCachedSelector', () => {

describe('resetRecomputations()', () => {
it('resets recomputations', () => {
const cachedSelector = selectorWithMockedResultFunc();
const cachedSelector = createCachedSelector(
[(state: string, param1: string) => null],
() => {}
)({keySelector: (state, param1) => param1});
cachedSelector('foo', 'bar');

expect(cachedSelector.recomputations()).toBe(1);
Expand All @@ -257,27 +210,27 @@ describe('createCachedSelector', () => {
describe('"dependencies" property', () => {
it('exports an array containing provided inputSelectors', () => {
type State = {a: string};
const dependency1 = (state: State) => state.a;
const dependency2 = (state: State) => state.a;
const inputSelector1 = (state: State) => state.a;
const inputSelector2 = (state: State) => state.a;

const cachedSelector = createCachedSelector(
dependency1,
dependency2,
inputSelector1,
inputSelector2,
() => {}
)(arg1 => arg1);
)(state => state);

const actual = cachedSelector.dependencies;
const expected = [dependency1, dependency2];
const expected = [inputSelector1, inputSelector2];
expect(actual).toEqual(expected);
});
});

describe('"resultFunc" property', () => {
it('points to provided result function', () => {
const resultFunc = () => {};
const cachedSelector = createCachedSelector(() => {}, resultFunc)(
(arg1, arg2) => arg2
);
const cachedSelector = createCachedSelector(() => {}, resultFunc)({
keySelector: (state, param1) => param1,
});
expect(cachedSelector.resultFunc).toBe(resultFunc);
});
});
Expand All @@ -289,7 +242,7 @@ describe('createCachedSelector', () => {
() => {},
() => {}
)({
keySelector: arg1 => arg1,
keySelector: state => state,
cacheObject: currentCacheObject,
});

Expand All @@ -309,4 +262,61 @@ describe('createCachedSelector', () => {
});
});
});

describe('options', () => {
describe('as single function', () => {
it('accepts keySelector function', () => {
const keySelectorMock = () => {};
const cachedSelector = createCachedSelector(
() => {},
() => {}
)(keySelectorMock);

expect(cachedSelector.keySelector).toBe(keySelectorMock);
});
});

describe('as single object', () => {
it('accepts keySelector, cacheObject and selectorCreator options', () => {
const cachedSelector = createCachedSelector(
(state: string, param1: string) => null,
() => {}
)({
keySelector: (state, param1) => param1,
cacheObject: new FlatObjectCache(),
selectorCreator: reselect.createSelector,
});

expect(cachedSelector.recomputations()).toBe(0);
cachedSelector('foo', 'bar');
cachedSelector('foo', 'bar');
expect(cachedSelector.recomputations()).toBe(1);
});

describe('"keySelectorCreator" option', () => {
it('overrides "keySelector" with provided function result', () => {
const inputSelector = () => {};
const resultFunc = () => {};
const keySelector = () => {};
const generatedKeySelector = () => {};
const keySelectorCreatorMock = jest.fn(() => generatedKeySelector);

const cachedSelector = createCachedSelector(
inputSelector,
resultFunc
)({
keySelector,
keySelectorCreator: keySelectorCreatorMock,
});

expect(keySelectorCreatorMock).toHaveBeenCalledWith({
inputSelectors: [inputSelector],
resultFunc: resultFunc,
keySelector: keySelector,
});
expect(cachedSelector.keySelector).toBe(generatedKeySelector);
});
});
});
});
});
Loading

0 comments on commit f2cbea6

Please sign in to comment.