From 7c9ad1204c5c063a847b348eafb11dc9c0e50a56 Mon Sep 17 00:00:00 2001 From: Matteo Basso Date: Fri, 28 Apr 2017 14:28:59 +0200 Subject: [PATCH] handle promises in subscriptions --- docs/api.md | 2 +- package.json | 4 +-- src/RefractionConnector.js | 40 +++++++++++++--------- test/RefractionConnector.spec.js | 57 +++++++++++++++++++++++++------- 4 files changed, 73 insertions(+), 30 deletions(-) diff --git a/docs/api.md b/docs/api.md index 9690621..098b4c9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -58,7 +58,7 @@ class App extends React.Component { `connect` accept a single object with these properties: - `actions` (*Object*): map component properties to refraction publishers. Attributes name of `actions` identify component properties, attributes value identify refraction publishers. If a publisher with that name is unavailable, connect will create a new function that publish the value for you. -- `subscriptions` (*Object*): an object that consist in a series of function that return new properties. Attributes name of `subscriptions` identify channels to handle, attributes value identify a function that must return an object representing new component props. This function accept the value hold by the channel and the actual props of component. **New properties will be merged with the old. Returning an object that has no value for a certain prop means maintain the old one.** +- `subscriptions` (*Object*): an object that consist in a series of function that return new properties. Attributes name of `subscriptions` identify channels to handle, attributes value identify a function that must return an object representing new component props or a Promise that resolve with them. This function accept the value hold by the channel and the actual props of component. **New properties will be merged with the old. Returning an object that has no value for a certain prop means maintain the old one.** #### Returns diff --git a/package.json b/package.json index 1b90cdb..ca89fcc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "refraction-react", - "version": "1.0.3", + "version": "1.1.0", "description": "React bindings for Refraction", "main": "lib/index.js", "files": [ @@ -88,4 +88,4 @@ "loose-envify" ] } -} +} \ No newline at end of file diff --git a/src/RefractionConnector.js b/src/RefractionConnector.js index 3a1c19b..5817515 100644 --- a/src/RefractionConnector.js +++ b/src/RefractionConnector.js @@ -1,7 +1,6 @@ import React from 'react'; export default class RefractionConnector extends React.Component { - static propTypes = { actions: React.PropTypes.object, subscriptions: React.PropTypes.object, @@ -9,39 +8,44 @@ export default class RefractionConnector extends React.Component { React.PropTypes.func, React.PropTypes.string, ]).isRequired, - } + }; static contextTypes = { refraction: React.PropTypes.object.isRequired, - } + }; static defaultProps = { actions: {}, subscriptions: {}, Component: 'div', - } + }; constructor(props, context) { super(props, context); - Object.keys(props.subscriptions).forEach((key) => { - this[key] = (param) => { - this.setState( - Object.assign( - {}, - this.state, - props.subscriptions[key](param, Object.assign({}, this.refs.element.props)) - ) + Object.keys(props.subscriptions).forEach(key => { + this[key] = param => { + const newProps = props.subscriptions[key]( + param, + Object.assign({}, this.refs.element.props) ); + if (newProps && typeof newProps.then === 'function') { + newProps.then(this.mergeState); + } else { + this.mergeState(newProps); + } }; }); this.alias = {}; - Object.keys(props.actions).forEach((key) => { + Object.keys(props.actions).forEach(key => { const { refraction } = this.context; if (refraction[props.actions[key]]) { this.alias[key] = refraction[props.actions[key]].bind(refraction); } else { - this.alias[key] = refraction.publish.bind(refraction, props.actions[key]); + this.alias[key] = refraction.publish.bind( + refraction, + props.actions[key] + ); } }); @@ -53,11 +57,17 @@ export default class RefractionConnector extends React.Component { this.context.refraction.unsubscribe(this); } + mergeState = obj => { + this.setState(Object.assign({}, this.state, obj)); + }; + render() { const props = Object.assign({}, this.props); delete props.actions; delete props.subscriptions; const { Component, ...others } = props; - return ; + return ( + + ); } } diff --git a/test/RefractionConnector.spec.js b/test/RefractionConnector.spec.js index b31b2f5..1405ac9 100644 --- a/test/RefractionConnector.spec.js +++ b/test/RefractionConnector.spec.js @@ -6,15 +6,22 @@ import { Provider } from '../src/'; import { Child, configs } from './common'; import RefractionConnector from '../src/RefractionConnector'; -describe('RefractionConnector', () => { +const wait = () => + new Promise(resolve => { + setTimeout(resolve, 500); + }); + +describe('RefractionConnector', function testRefractionConnector() { + this.timeout(5000); + class CustomRefraction extends Refraction { - onInputChange = (e) => { + onInputChange = e => { this.publish('onInputChange', e.target.value); - } + }; } let refraction = new CustomRefraction(); - const testConnector = (actions, subscriptions) => { + const testConnector = (actions, subscriptions, done) => { const rendered = TestUtils.renderIntoDocument( { /> ); - const connector = TestUtils.findRenderedComponentWithType(rendered, RefractionConnector); + const connector = TestUtils.findRenderedComponentWithType( + rendered, + RefractionConnector + ); expect(connector.props).toEqual({ actions, subscriptions, @@ -47,14 +57,21 @@ describe('RefractionConnector', () => { const input = TestUtils.findRenderedDOMComponentWithTag(rendered, 'input'); TestUtils.Simulate.change(input, { target: { value: 'test1' } }); - expect(connector.state).toEqual({ value: 'test1' }); - expect(connected.props.value).toEqual('test1'); - TestUtils.Simulate.change(input, { target: { value: 'test2' } }); - expect(connector.state).toEqual({ value: 'test1test2' }); - expect(connected.props.value).toEqual('test1test2'); + wait() + .then(() => { + expect(connector.state).toEqual({ value: 'test1' }); + expect(connected.props.value).toEqual('test1'); + TestUtils.Simulate.change(input, { target: { value: 'test2' } }); + return wait(); + }) + .then(() => { + expect(connector.state).toEqual({ value: 'test1test2' }); + expect(connected.props.value).toEqual('test1test2'); - connector.componentWillUnmount(); - expect(refraction.mediator.subscribers.indexOf(connector)).toEqual(-1); + connector.componentWillUnmount(); + expect(refraction.mediator.subscribers.indexOf(connector)).toEqual(-1); + if (done) done(); + }); }; afterEach(() => { @@ -73,4 +90,20 @@ describe('RefractionConnector', () => { }), }); }); + + it('should handle Promises in subscriptions', done => { + refraction = new Refraction(); + testConnector( + configs.actions, + { + onInputChange: (e, props) => + Promise.resolve({ + value: e.target.value + ? props.value + e.target.value + : e.target.value, + }), + }, + done + ); + }); });