Comparing version 2.4.0 to 2.5.0
@@ -17,2 +17,4 @@ /* | ||
import shallowequal from 'shallowequal'; | ||
import config, { | ||
@@ -28,2 +30,8 @@ configureIguazu, | ||
describe('stateChangeComparator', () => { | ||
it('should return shallow equal', () => { | ||
expect(config.stateChangeComparator).toBe(shallowequal); | ||
}); | ||
}); | ||
describe('stateChangeLimiter', () => { | ||
@@ -30,0 +38,0 @@ it('should return the same function provided', () => { |
@@ -32,2 +32,3 @@ /* | ||
jest.mock('../src/config', () => ({ | ||
stateChangeComparator: jest.fn(require('shallowequal')), | ||
stateChangeLimiter: jest.fn(func => func), | ||
@@ -99,2 +100,40 @@ })); | ||
it('reduced data should not be overridden by local props', () => { | ||
const localAsyncData = ''; | ||
const props = mount(<Container | ||
myAsyncData={localAsyncData} | ||
/>, { context: { store } }).find(Presentation).props(); | ||
expect(props.myAsyncData).toEqual('populated data'); | ||
expect(props.myAsyncData).not.toBe(localAsyncData); | ||
}); | ||
it('loadStatus should not be overridden by local props', () => { | ||
const localLoadStatus = {}; | ||
const props = mount(<Container | ||
loadStatus={localLoadStatus} | ||
/>, { context: { store } }).find(Presentation).props(); | ||
expect(props.loadStatus).toEqual({ all: 'complete', myAsyncData: 'complete' }); | ||
expect(props.loadStatus).not.toBe(localLoadStatus); | ||
}); | ||
it('should pass reduced data and load errors as props', () => { | ||
const error = new Error('load error'); | ||
myAsyncLoadFunction.mockImplementationOnce(() => ({ error, data: error })); | ||
const props = mount(<Container />, { context: { store } }).find(Presentation).props(); | ||
expect(props.myAsyncData).toEqual(error); | ||
expect(props.loadErrors).toEqual({ any: true, myAsyncData: error }); | ||
expect(myAsyncLoadFunction).toHaveBeenCalledTimes(1); | ||
}); | ||
it('loadErrors should not be overridden by local props', () => { | ||
const localLoadErrors = {}; | ||
const error = new Error('load error'); | ||
myAsyncLoadFunction.mockImplementationOnce(() => ({ error, data: error })); | ||
const props = mount(<Container | ||
loadErrors={localLoadErrors} | ||
/>, { context: { store } }).find(Presentation).props(); | ||
expect(props.loadErrors).toEqual({ any: true, myAsyncData: error }); | ||
expect(props.loadStatus).not.toBe(localLoadErrors); | ||
}); | ||
it('should update data and load status when parent props change', () => { | ||
@@ -173,2 +212,43 @@ const wrapper = mount(<Container />, { context: { store } }); | ||
describe('stateChangeComparator', () => { | ||
it('applies a globally provided comparator when parent props change', () => { | ||
const ContainerComparator = connectAsync({ | ||
loadDataAsProps, | ||
})(Presentation); | ||
const wrapper = mount(<ContainerComparator irrelevant="old" />, { context: { store } }); | ||
wrapper.setProps({ irrelevant: 'new' }); | ||
expect(config.stateChangeComparator).toHaveBeenCalledTimes(3); | ||
}); | ||
it('applies a locally provided comparator when parent props change', () => { | ||
const localStateChangeLimiter = jest.fn(() => true); | ||
const ContainerComparator = connectAsync({ | ||
loadDataAsProps, | ||
stateChangeComparator: localStateChangeLimiter, | ||
})(Presentation); | ||
const wrapper = mount(<ContainerComparator irrelevant="old" />, { context: { store } }); | ||
wrapper.setProps({ irrelevant: 'new' }); | ||
expect(config.stateChangeComparator).not.toHaveBeenCalled(); | ||
expect(localStateChangeLimiter).toHaveBeenCalledTimes(3); | ||
}); | ||
it('applies a globally provided comparator when the store has updated', () => { | ||
const ContainerComparator = connectAsync({ | ||
loadDataAsProps, | ||
})(Presentation); | ||
mount(<ContainerComparator irrelevant="old" />, { context: { store } }); | ||
store.dispatch({ type: 'UPDATE_PARAM', param: 'y' }); | ||
expect(config.stateChangeComparator).toHaveBeenCalledTimes(3); | ||
}); | ||
it('applies a locally provided comparator when the store has updated', () => { | ||
const localStateChangeLimiter = jest.fn(() => true); | ||
const ContainerComparator = connectAsync({ | ||
loadDataAsProps, | ||
stateChangeComparator: localStateChangeLimiter, | ||
})(Presentation); | ||
mount(<ContainerComparator irrelevant="old" />, { context: { store } }); | ||
store.dispatch({ type: 'UPDATE_PARAM', param: 'y' }); | ||
expect(config.stateChangeComparator).not.toHaveBeenCalled(); | ||
expect(localStateChangeLimiter).toHaveBeenCalledTimes(3); | ||
}); | ||
}); | ||
describe('stateChangeLimiter', () => { | ||
@@ -202,2 +282,10 @@ it('applies a globally provided limiter on redux state change', () => { | ||
it('should not be overridden by local props', () => { | ||
const localIsLoading = jest.fn(); | ||
const wrapper = mount(<Container isLoading={localIsLoading} />, { context: { store } }); | ||
const props = wrapper.find(Presentation).props(); | ||
expect(props.isLoading).toBe(wrapper.instance().isLoading); | ||
expect(props.isLoading).not.toBe(localIsLoading); | ||
}); | ||
describe('no props of interest provided', () => { | ||
@@ -241,2 +329,12 @@ it('should return true when any of the async props are in the middle of loading', () => { | ||
it('should not be overridden by local props', () => { | ||
const localLoadedWithErrors = jest.fn(); | ||
const wrapper = mount(<Container | ||
loadedWithErrors={localLoadedWithErrors} | ||
/>, { context: { store } }); | ||
const props = wrapper.find(Presentation).props(); | ||
expect(props.loadedWithErrors).toBe(wrapper.instance().loadedWithErrors); | ||
expect(props.loadedWithErrors).not.toBe(localLoadedWithErrors); | ||
}); | ||
describe('no props of interest provided', () => { | ||
@@ -243,0 +341,0 @@ it('should return true if any of the async props had an error while loading', () => { |
@@ -9,2 +9,6 @@ "use strict"; | ||
var _shallowequal = _interopRequireDefault(require("shallowequal")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/* | ||
@@ -26,2 +30,3 @@ * Copyright 2017 American Express | ||
var config = { | ||
stateChangeComparator: _shallowequal.default, | ||
stateChangeLimiter: function stateChangeLimiter(onStateChange) { | ||
@@ -28,0 +33,0 @@ return onStateChange; |
@@ -54,3 +54,4 @@ "use strict"; | ||
var loadDataAsProps = _ref.loadDataAsProps, | ||
localStateChangeLimiter = _ref.stateChangeLimiter; | ||
localStateChangeLimiter = _ref.stateChangeLimiter, | ||
localStateChangeComparator = _ref.stateChangeComparator; | ||
var ssrEnabled = loadDataAsProps.ssr; | ||
@@ -169,5 +170,7 @@ | ||
value: function setStateIfNecessary(newState) { | ||
var dataIsEqual = (0, _shallowequal.default)(this.state.data, newState.data); | ||
var statusIsEqual = (0, _shallowequal.default)(this.state.status, newState.status); | ||
var errorsAreEqual = (0, _shallowequal.default)(this.state.errors, newState.errors); | ||
var globalStateChangeComparator = _config.default.stateChangeComparator; | ||
var stateChangeComparator = localStateChangeComparator || globalStateChangeComparator; | ||
var dataIsEqual = stateChangeComparator(this.state.data, newState.data); | ||
var statusIsEqual = stateChangeComparator(this.state.status, newState.status); | ||
var errorsAreEqual = stateChangeComparator(this.state.errors, newState.errors); | ||
@@ -215,3 +218,3 @@ if (!dataIsEqual || !statusIsEqual || !errorsAreEqual) { | ||
errors = _this$state.errors; | ||
return _react.default.createElement(WrappedComponent, _extends({ | ||
return _react.default.createElement(WrappedComponent, _extends({}, this.props, { | ||
loadStatus: status, | ||
@@ -221,3 +224,3 @@ isLoading: this.isLoading, | ||
loadedWithErrors: this.loadedWithErrors | ||
}, data, this.props)); | ||
}, data)); | ||
} | ||
@@ -224,0 +227,0 @@ }]); |
@@ -20,4 +20,6 @@ "use strict"; | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
@@ -24,0 +26,0 @@ |
{ | ||
"name": "iguazu", | ||
"version": "2.4.0", | ||
"version": "2.5.0", | ||
"description": "An asynchronous data flow solution for React/Redux applications", | ||
@@ -11,2 +11,6 @@ "author": "Maia Teegarden", | ||
"license": "Apache-2.0", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/americanexpress/iguazu.git" | ||
}, | ||
"main": "lib/index.js", | ||
@@ -13,0 +17,0 @@ "scripts": { |
@@ -306,2 +306,25 @@ # Iguazu | ||
### Updating | ||
Iguazu processes updates on Redux state changes by comparing the previous and next responses from `loadDataAsProps` using | ||
[shallowequal](https://www.npmjs.com/package/shallowequal) by default. You are able to declare a comparator function when | ||
calling `connectAsync` to manage how the previous and next responses from `loadDataAsProps` are compared. | ||
```javascript | ||
import { deepEqual } from 'fast-equals'; | ||
function loadDataAsProps({ store, ownProps }) { | ||
const { dispatch, getState } = store; | ||
return { | ||
myData: () => dispatch(queryToDeeplyNestedData(ownProps.someParam)), | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)) | ||
} | ||
} | ||
export default connectAsync({ | ||
loadDataAsProps, | ||
stateChangeComparator: deepEqual, | ||
})(MyContainer); | ||
``` | ||
### Limiting | ||
@@ -393,5 +416,7 @@ | ||
```javascript | ||
import { shallowEqual, deepEqual } from 'fast-equals'; | ||
import { configureIguazu } from 'iguazu'; | ||
configureIguazu({ | ||
stateChangeComparator: shallowEqual, // applied globally. | ||
stateChangeLimiter: onStateChange => debounce(onStateChange, 100), // applied globally. | ||
@@ -405,4 +430,4 @@ }); | ||
return { | ||
myData: () => dispatch(expensiveQueryToData(ownProps.someParam)), | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)) | ||
myData: () => dispatch(queryToDeeplyNestedData(ownProps.someParam)),, | ||
myOtherData: () => dispatch(expensiveQueryToData(ownProps.someParam)) | ||
} | ||
@@ -413,2 +438,3 @@ } | ||
loadDataAsProps, | ||
stateChangeComparator: deepEqual, // override global setting. | ||
stateChangeLimiter: onStateChange => debounce(onStateChange, 500), // override global setting. | ||
@@ -415,0 +441,0 @@ })(MyContainer); |
@@ -16,4 +16,6 @@ /* | ||
*/ | ||
import shallowequal from 'shallowequal'; | ||
const config = { | ||
stateChangeComparator: shallowequal, | ||
stateChangeLimiter: onStateChange => onStateChange, | ||
@@ -20,0 +22,0 @@ }; |
@@ -33,2 +33,3 @@ /* | ||
stateChangeLimiter: localStateChangeLimiter, | ||
stateChangeComparator: localStateChangeComparator, | ||
}) { | ||
@@ -107,5 +108,7 @@ const ssrEnabled = loadDataAsProps.ssr; | ||
setStateIfNecessary(newState) { | ||
const dataIsEqual = shallowequal(this.state.data, newState.data); | ||
const statusIsEqual = shallowequal(this.state.status, newState.status); | ||
const errorsAreEqual = shallowequal(this.state.errors, newState.errors); | ||
const { stateChangeComparator: globalStateChangeComparator } = config; | ||
const stateChangeComparator = localStateChangeComparator || globalStateChangeComparator; | ||
const dataIsEqual = stateChangeComparator(this.state.data, newState.data); | ||
const statusIsEqual = stateChangeComparator(this.state.status, newState.status); | ||
const errorsAreEqual = stateChangeComparator(this.state.errors, newState.errors); | ||
if (!dataIsEqual || !statusIsEqual || !errorsAreEqual) { | ||
@@ -143,2 +146,3 @@ this.setState(newState); | ||
<WrappedComponent | ||
{...this.props} | ||
loadStatus={status} | ||
@@ -149,3 +153,2 @@ isLoading={this.isLoading} | ||
{...data} | ||
{...this.props} | ||
/> | ||
@@ -152,0 +155,0 @@ ); |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 7 instances in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
539762
61
1717
474
0
7
60
1