From f2d4ca3e2045f627ea3f39de3d1b25b00a67bbed Mon Sep 17 00:00:00 2001 From: FBerthelot Date: Fri, 21 Jun 2019 22:10:23 +0200 Subject: [PATCH] :sparkles: handle events shallow configuration for react --- .../src/__tests__/shallow-event.spec.js | 26 +++++++ .../__tests__/shallow-querySelector.spec.js | 70 +++++++++++-------- .../src/emptyShallow.js | 9 ++- .../src/emptyShallow.spec.js | 12 ++++ .../src/methods/querySelector.js | 4 +- .../src/methods/querySelector.spec.js | 8 +-- .../component-test-utils-react/src/shallow.js | 27 ++++--- website/docs/shallow/constructor.md | 3 + 8 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 packages/component-test-utils-react/src/__tests__/shallow-event.spec.js diff --git a/packages/component-test-utils-react/src/__tests__/shallow-event.spec.js b/packages/component-test-utils-react/src/__tests__/shallow-event.spec.js new file mode 100644 index 0000000..0ca3b1e --- /dev/null +++ b/packages/component-test-utils-react/src/__tests__/shallow-event.spec.js @@ -0,0 +1,26 @@ +const {shallow} = require('../shallow'); +const React = require('react'); + +describe('shallow - event', () => { + const ComponentParent = ({product, onProductAddedToBasket}) => { + return ( + - - - ); - }; + const initialState = {count: 0}; + const reducer = (state, action) => { + switch (action.type) { + case 'increment': + return {count: state.count + 1}; + case 'decrement': + default: + return {count: state.count - 1}; + } + }; + + const Counter = () => { + const [state, dispatch] = React.useReducer(reducer, initialState); + return ( + <> + Total : {state.count} + + + + ); + }; + it('should work with the useReducer hooks', () => { const cmp = shallow(); expect(cmp.html()).toBe( @@ -52,4 +52,14 @@ describe('shallow - querySelector', () => { '<>Total : 0' ); }); + + it('should display the view of the component when cannot find the element', () => { + const cmp = shallow(); + + expect(() => { + cmp.querySelector('yolo').dispatchEvent('yolo2'); + }).toThrow( + '<>Total : 0' + ); + }); }); diff --git a/packages/component-test-utils-react/src/emptyShallow.js b/packages/component-test-utils-react/src/emptyShallow.js index a47f34d..13866f8 100644 --- a/packages/component-test-utils-react/src/emptyShallow.js +++ b/packages/component-test-utils-react/src/emptyShallow.js @@ -1,7 +1,12 @@ exports.EmptyShallowedComponent = class EmptyShallowedComponent { - constructor(selector) { + constructor(selector, view) { this.nodeNotExistError = new Error( - `The node you try to access with "${selector}" selector does not exist` + ` + The node you try to access with "${selector}" selector does not exist + + ${view ? view : 'Your component render falsy value'} + + ` ); } diff --git a/packages/component-test-utils-react/src/emptyShallow.spec.js b/packages/component-test-utils-react/src/emptyShallow.spec.js index 97016b0..da529e1 100644 --- a/packages/component-test-utils-react/src/emptyShallow.spec.js +++ b/packages/component-test-utils-react/src/emptyShallow.spec.js @@ -30,4 +30,16 @@ describe('EmptyShallowedComponent', () => { new EmptyShallowedComponent('selector1234').setProps('toto') ).toThrow('selector1234'); }); + + it('should show the component view', () => { + expect(() => + new EmptyShallowedComponent('selector1234', 'view4506').setProps('toto') + ).toThrow('view4506'); + }); + + it('should show a special message when the component view is falsy', () => { + expect(() => + new EmptyShallowedComponent('selector1234').setProps('toto') + ).toThrow('Your component render falsy value'); + }); }); diff --git a/packages/component-test-utils-react/src/methods/querySelector.js b/packages/component-test-utils-react/src/methods/querySelector.js index ed3bf1c..4d98b7e 100644 --- a/packages/component-test-utils-react/src/methods/querySelector.js +++ b/packages/component-test-utils-react/src/methods/querySelector.js @@ -17,7 +17,7 @@ const isSelectedObject = (elem, selector) => { return elem.type === selector; }; -exports.querySelector = (shallowedComponent, selector, ShallowRender) => { +exports.querySelector = (shallowedComponent, selector, ShallowRender, getView) => { // When the children is not an array nor an object, impossible to target it ! if ( !shallowedComponent.props || @@ -34,7 +34,7 @@ exports.querySelector = (shallowedComponent, selector, ShallowRender) => { isSelectedObject(children, selector) && children; if (!targetedComponent) { - return new EmptyShallowedComponent(selector); + return new EmptyShallowedComponent(selector, getView()); } const WrapperComponent = () => { diff --git a/packages/component-test-utils-react/src/methods/querySelector.spec.js b/packages/component-test-utils-react/src/methods/querySelector.spec.js index 91045d4..37ac7d0 100644 --- a/packages/component-test-utils-react/src/methods/querySelector.spec.js +++ b/packages/component-test-utils-react/src/methods/querySelector.spec.js @@ -28,27 +28,27 @@ describe('querySelector', () => { it('Should return an EmptyShallowedComponent when component have no props', () => { const component = {}; - const result = querySelector(component, 'tagname', ShallowRender); + const result = querySelector(component, 'tagname', ShallowRender, () => {}); expect(result instanceof EmptyShallowedComponent).toBe(true); }); it('Should return null if component have no children', () => { const component = {props: {}}; - const result = querySelector(component, 'tagname', ShallowRender); + const result = querySelector(component, 'tagname', ShallowRender, () => {}); expect(result instanceof EmptyShallowedComponent).toBe(true); }); it('Should return null when children is not an element but a string', () => { const component = {props: {children: 'toto'}}; - const result = querySelector(component, 'tagname', ShallowRender); + const result = querySelector(component, 'tagname', ShallowRender, () => {}); expect(result instanceof EmptyShallowedComponent).toBe(true); }); it('Should return null when children not correspond', () => { const component = {props: {children: {type: 'toto'}}}; - const result = querySelector(component, 'tagname', ShallowRender); + const result = querySelector(component, 'tagname', ShallowRender, () => {}); expect(result instanceof EmptyShallowedComponent).toBe(true); }); diff --git a/packages/component-test-utils-react/src/shallow.js b/packages/component-test-utils-react/src/shallow.js index 60f2119..605a8e1 100644 --- a/packages/component-test-utils-react/src/shallow.js +++ b/packages/component-test-utils-react/src/shallow.js @@ -16,11 +16,12 @@ const defaultConfig = { }; class ShallowRender { - constructor(component, config = defaultConfig) { + constructor(component, config = defaultConfig, isRoot) { this._unmounted = false; this._component = component; this._config = config; this._dispatcher = createDispatcher(this); + this._isRoot = Boolean(isRoot); this._render(); } @@ -42,11 +43,19 @@ class ShallowRender { (this._instance && this._instance.props) || this._component.props; + const enhancedProps = this._isRoot && this._config.events ? { + ...props, + ...Object.keys(this._config.events).reduce((addedProps, eventKey) => { + addedProps[`on${eventKey[0].toUpperCase()}${eventKey.slice(1)}`] = this._config.events[eventKey]; + return addedProps; + }, {}) + } : props; + if (isClassComponent(this._component.type)) { if (!this._instance) { const updater = new Updater(this); // eslint-disable-next-line new-cap - this._instance = new this._component.type(props, {}, updater); + this._instance = new this._component.type(enhancedProps, {}, updater); this._instance.state = this._instance.state || null; this._instance.updater = updater; firstRender = true; @@ -56,13 +65,13 @@ class ShallowRender { this._instance.state = { ...this._instance.state, ...this._component.type.getDerivedStateFromProps( - props, + enhancedProps, this._instance.state ) }; } - this._instance.props = props; + this._instance.props = enhancedProps; if ( !forceUpdate && @@ -80,11 +89,11 @@ class ShallowRender { } else if (ReactIs.isForwardRef(this._component)) { reactEl = this._component.type.render.call( undefined, - props, + enhancedProps, this._component.ref ); } else { - reactEl = this._component.type.call(undefined, props); + reactEl = this._component.type.call(undefined, enhancedProps); } try { @@ -103,7 +112,7 @@ class ShallowRender { this._handleClassLAfterRenderLifeCycle(firstRender); } - this._prevProps = props; + this._prevProps = enhancedProps; } _handleErrorInRender(error) { @@ -199,7 +208,7 @@ class ShallowRender { querySelector(selector) { this._throwIfUnmounted('querySelector'); - return querySelector(this._rendered, selector, ShallowRender); + return querySelector(this._rendered, selector, ShallowRender, this.html.bind(this)); } unmount() { @@ -214,7 +223,7 @@ class ShallowRender { } exports.shallow = (component, config) => { - return new ShallowRender(component, config); + return new ShallowRender(component, config, true); }; function isClassComponent(Component) { diff --git a/website/docs/shallow/constructor.md b/website/docs/shallow/constructor.md index c5be31d..a86afc8 100644 --- a/website/docs/shallow/constructor.md +++ b/website/docs/shallow/constructor.md @@ -13,6 +13,7 @@ Here is the list of available options: - {Object} `mocks` The key is the property name, and the value his value. - {Boolean} `blackList` If true each component will not be mocked as in default mode (whiteList). In this mode you will have to explicitly give to shallow configuration object the list of components to not mock. +- {Object} `events` The key is the event name, the value should be a spy ### Examples @@ -87,3 +88,5 @@ it('it should only mock ChildrenComponent2', () => { expect(cmp.html()).toBe('
'); }); ``` + +##### Event listening (with jest)