diff --git a/.changeset/modern-cars-hope.md b/.changeset/modern-cars-hope.md new file mode 100644 index 00000000000..6a2d84e49c2 --- /dev/null +++ b/.changeset/modern-cars-hope.md @@ -0,0 +1,14 @@ +--- +'@talend/react-cmf': major +'@talend/react-cmf-cqrs': major +'@talend/react-cmf-router': major +'@talend/react-forms': major +'@talend/react-containers': major +'@talend/react-datagrid': major +'@talend/react-sagas': major +'@talend/react-stepper': major +'@talend/react-storybook-cmf': major +'@talend/react-flow-designer': major +--- + +Redux major upgrade with saga diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 7a38181566b..a11a292d66c 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -11,6 +11,9 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@v2 + with: + persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token + fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - name: Use Node.js 14 uses: actions/setup-node@v2 @@ -60,3 +63,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} message: "chore: apply prettier on modified files" + branch: ${{ github.head_ref }} diff --git a/.github/workflows/yarn-deduplicate.yml b/.github/workflows/yarn-deduplicate.yml index 84840910682..7652234a9fa 100644 --- a/.github/workflows/yarn-deduplicate.yml +++ b/.github/workflows/yarn-deduplicate.yml @@ -3,7 +3,7 @@ name: yarn-deduplicate on: pull_request: paths: - - 'yarn.lock' + - "yarn.lock" jobs: dedupe: @@ -22,9 +22,9 @@ jobs: uses: actions/setup-node@v2 with: node-version: 14 - registry-url: 'https://registry.npmjs.org/' - scope: '@talend' - cache: 'yarn' + registry-url: "https://registry.npmjs.org/" + scope: "@talend" + cache: "yarn" - name: yarn-deduplicate id: deduplicate @@ -38,5 +38,5 @@ jobs: uses: actions-js/push@master with: github_token: ${{ secrets.GITHUB_TOKEN }} - message: 'chore: yarn-deduplicate' + message: "chore: yarn-deduplicate" branch: ${{ github.head_ref }} diff --git a/packages/cmf-cqrs/package.json b/packages/cmf-cqrs/package.json index f39c9d457ef..427cd40883b 100644 --- a/packages/cmf-cqrs/package.json +++ b/packages/cmf-cqrs/package.json @@ -33,7 +33,7 @@ "dependencies": { "@talend/react-cmf": "^6.39.1", "immutable": "^3.8.2", - "redux-saga": "^0.15.6" + "redux-saga": "^1.1.3" }, "devDependencies": { "@talend/scripts-core": "^11.3.0", diff --git a/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.js b/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.js index 0f3ceaf1f3f..0dfa4ea52e3 100644 --- a/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.js +++ b/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.js @@ -42,7 +42,7 @@ describe('Container ACKDispatcher', () => { } registry['actionCreator:myActionCreator'] = myActionCreator; - mount(, { context: { registry } }); + mount(, mock.Provider.getEnzymeOption({ registry })); expect(mockProcessACK).toHaveBeenCalled(); }); it('should processACK call dispatch', () => { diff --git a/packages/cmf-router/package.json b/packages/cmf-router/package.json index 7b87fe27b3f..af43fbeed0e 100644 --- a/packages/cmf-router/package.json +++ b/packages/cmf-router/package.json @@ -22,16 +22,17 @@ "lodash": "^4.17.21", "path-to-regexp": "^2.4.0", "prop-types": "^15.8.1", - "react-redux": "^5.1.2", + "react-redux": "^7.2.2", "react-router": "^3.2.6", "react-router-redux": "^4.0.8", - "redux-saga": "^0.15.6" + "redux-saga": "^1.1.3" }, "peerDependencies": { "react": "^16.8.6", "react-dom": "^16.8.6" }, "devDependencies": { + "@redux-saga/testing-utils": "^1.1.3", "@talend/scripts-core": "^11.3.0", "@talend/scripts-preset-react-lib": "^9.9.3", "enzyme": "^3.11.0", diff --git a/packages/cmf-router/src/index.test.js b/packages/cmf-router/src/index.test.js index df432ac65f9..8c88f14cad1 100644 --- a/packages/cmf-router/src/index.test.js +++ b/packages/cmf-router/src/index.test.js @@ -33,7 +33,7 @@ describe('getModule', () => { const generator = mod.cmfModule.saga(); generator.next(); const result = generator.next(); - expect(result.value.FORK.args[1]).toEqual({ + expect(result.value.payload.args[1]).toEqual({ '/foo': config.sagaRouterConfig['/foo'], '/foo/bar': configBis.sagaRouterConfig['/foo/bar'], }); diff --git a/packages/cmf-router/src/sagaRouter.test.js b/packages/cmf-router/src/sagaRouter.test.js index 55eea1e7922..c12cbbf1aa8 100644 --- a/packages/cmf-router/src/sagaRouter.test.js +++ b/packages/cmf-router/src/sagaRouter.test.js @@ -1,5 +1,5 @@ import { spawn, take, cancel } from 'redux-saga/effects'; -import { createMockTask } from 'redux-saga/utils'; +import { createMockTask } from '@redux-saga/testing-utils'; import sagaRouter from './sagaRouter'; describe('sagaRouter import', () => { diff --git a/packages/cmf/__tests__/Dispatcher.test.js b/packages/cmf/__tests__/Dispatcher.test.js index d01ef28e17f..588803e3052 100644 --- a/packages/cmf/__tests__/Dispatcher.test.js +++ b/packages/cmf/__tests__/Dispatcher.test.js @@ -1,43 +1,24 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { shallow, mount } from 'enzyme'; -import { Dispatcher } from '../src/Dispatcher'; +import { mount } from 'enzyme'; +import { mock } from '../src'; +import ConnectedDispatcher, { Dispatcher } from '../src/Dispatcher'; import CONST from '../src/constant'; -const mockContext = { - registry: { - [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:actionCreator:id`]: jest.fn(), - [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:another:actionCreator:id`]: jest.fn(), - }, -}; - -jest.mock('../src/action', () => ({ - getOnProps() { - return ['onClick', 'onDoubleClick']; - }, -})); -jest.mock('../src/actionCreator', () => ({ - get(context, id) { - if ( - id !== 'existingActionCreator:id' && - id !== 'actionCreator:id' && - id !== 'noOp' && - id !== 'another:actionCreator:id' - ) { - throw new Error(`action not found id: ${id}`); - } - }, -})); +const noopRId = `${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:noOp`; describe('Testing ', () => { - function replacer(k, v) { - let val = v; - if (typeof v === 'function') { - val = '[Function]'; - } - return val; - } - const noOp = () => {}; + let registry; + const onError = jest.fn(); + beforeEach(() => { + registry = { + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:existingActionCreator:id`]: jest.fn(), + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:actionCreator:id`]: jest.fn(), + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:noOp`]: jest.fn(), + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:another:actionCreator:id`]: jest.fn(), + }; + + jest.resetAllMocks(); + }); it('should inject dispatchable on(event) props into its children', () => { const dispatchActionCreator = jest.fn(); @@ -47,85 +28,63 @@ describe('Testing ', () => { onDoubleClick="another:actionCreator:id" dispatchActionCreator={dispatchActionCreator} > - , - { - context: mockContext, - }, - ); - expect( - JSON.stringify(wrapper.find('button').props(), replacer).replace(/(\\t|\\n)/g, ''), - ).toEqual( - JSON.stringify({ onClick: noOp, onDoubleClick: noOp }, replacer).replace(/(\\t|\\n)/g, ''), + mock.Provider.getEnzymeOption({ registry, onError }), ); + expect(typeof wrapper.find('button').props().onClick).toEqual('function'); + expect(typeof wrapper.find('button').props().onDoubleClick).toEqual('function'); }); it('should throw with unknown action', () => { - expect(() => { - shallow( + const opts = mock.Provider.getEnzymeOption({ registry }); + mount( + - + + , + opts, + ); + + expect(onError).toHaveBeenCalled(); + expect(onError.mock.calls[0][0].message).toBe( + 'actionCreator not found in the registry: unknnown:actionCreator:id', + ); }); it('should have its method onEvent called when children handle an event', () => { - const wrapper = shallow( - - + , + mock.Provider.getEnzymeOption({ registry, onError }), ); - const buttonWrapper = wrapper.find('button').at(0); - const instance = wrapper.instance(); - spyOn(instance, 'onEvent'); + const buttonWrapper = wrapper.find('button'); buttonWrapper.simulate('click'); - expect(instance.onEvent).toHaveBeenCalled(); - expect(instance.onEvent).toHaveBeenCalledWith(undefined, 'onClick'); + expect(registry[noopRId]).toHaveBeenCalled(); }); it( 'should call cmf.actionCreator.get and reThrow at mount time' + "if action info bind onto on[eventName] can't be found in settings", () => { - expect(() => { - mount( + mount( + - + + , + mock.Provider.getEnzymeOption({ registry }), + ); + expect(onError).toHaveBeenCalled(); + expect(onError.mock.calls[0][0].message).toBe( + 'actionCreator not found in the registry: error:actionCreator:id', ); - expect(() => { - wrapper.setProps({ onClick: 'error:another:actionCreator:id' }); - }).toThrowError('action not found id: error:another:actionCreator:id'); }, ); @@ -142,12 +101,7 @@ describe('Testing ', () => { , - { - context: mockContext, - childContextTypes: { - registry: PropTypes.object.isRequired, - }, - }, + mock.Provider.getEnzymeOption({ registry, onError }), ); wrapper.find('a').simulate('click'); expect(onClick).toHaveBeenCalled(); @@ -167,12 +121,7 @@ describe('Testing ', () => { , - { - context: mockContext, - childContextTypes: { - registry: PropTypes.object.isRequired, - }, - }, + mock.Provider.getEnzymeOption({ registry, onError }), ); wrapper.find('a').simulate('click'); expect(onClick).not.toHaveBeenCalled(); @@ -184,13 +133,11 @@ describe('Testing ', () => { type: 'click', preventDefault: jest.fn(), }; - const wrapper = shallow( + const wrapper = mount( , - { - context: mockContext, - }, + mock.Provider.getEnzymeOption({ registry, onError }), ); wrapper.find('a').simulate('click', event); expect(event.preventDefault).toHaveBeenCalled(); @@ -210,10 +157,15 @@ describe('Testing ', () => { extra: 'foo', children: , }; - const wrapper = shallow(, { - context: mockContext, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption({ registry, onError }), + ); wrapper.find('a').simulate('click', event); - expect(dispatchActionCreator).toHaveBeenCalledWith('noOp', event, props); + expect(dispatchActionCreator).toHaveBeenCalledWith( + 'noOp', + expect.objectContaining(event), + expect.objectContaining(props), + ); }); }); diff --git a/packages/cmf/__tests__/Inject.test.js b/packages/cmf/__tests__/Inject.test.js index 9382a88d12f..d8ad4abe718 100644 --- a/packages/cmf/__tests__/Inject.test.js +++ b/packages/cmf/__tests__/Inject.test.js @@ -1,40 +1,47 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import Inject from '../src/Inject.component'; +import { mock } from '../src'; describe('Inject', () => { it('should render', () => { // given - const MyComponent = jest.fn(); + const MyComponent = jest.fn(() => Hello); MyComponent.displayName = 'MyComponent'; const context = { registry: { '_.route.component:MyComponent': MyComponent, }, }; + const Provider = mock.Provider; // when - const wrapper = shallow(, { context }); + const wrapper = mount( + , + Provider.getEnzymeOption(context), + ); // then - expect(wrapper.equals()).toBe(true); + expect(wrapper.find(MyComponent).equals()).toBe(true); }); it('should render error if component not found', () => { // given const MyComponent = jest.fn(); MyComponent.displayName = 'MyComponent'; - const context = { registry: {} }; + const Provider = mock.Provider; // when - const wrapper = shallow(, { context }); + const wrapper = mount(, Provider.getEnzymeOption()); // then - expect(wrapper.equals( - - )).toBe(true); + expect( + wrapper + .find(Inject.NotFoundComponent) + .equals( + , + ), + ).toBe(true); }); }); diff --git a/packages/cmf/__tests__/action.test.js b/packages/cmf/__tests__/action.test.js index bcc3b1c6187..6d70df4c024 100644 --- a/packages/cmf/__tests__/action.test.js +++ b/packages/cmf/__tests__/action.test.js @@ -1,5 +1,5 @@ import actionAPI from '../src/action'; -import mock from '../src/mock'; +import { mock } from '../src'; describe('CMF action', () => { let state; @@ -8,11 +8,11 @@ describe('CMF action', () => { let settings; beforeEach(() => { - settings = mock.settings(); - state = mock.state(); + settings = mock.store.settings(); + state = mock.store.state(); state.cmf.settings = settings; - context = mock.context(state); - emptyContext = mock.emptyContext(); + context = mock.store.context(state); + emptyContext = mock.store.emptyContext(); }); it('getActionsById should return action from settings', () => { const actions = actionAPI.getActionsById(context); diff --git a/packages/cmf/__tests__/actionCreator.test.js b/packages/cmf/__tests__/actionCreator.test.js index cfb42c3300e..93c3b3eacdb 100644 --- a/packages/cmf/__tests__/actionCreator.test.js +++ b/packages/cmf/__tests__/actionCreator.test.js @@ -1,12 +1,11 @@ -import mock from '../src/mock'; +import { mock } from '../src'; import actionCreatorAPI from '../src/actionCreator'; -import { create } from 'domain'; describe('CMF action', () => { let context; beforeEach(() => { - context = mock.context(); + context = mock.store.context(); }); it('get should return a function', () => { diff --git a/packages/cmf/__tests__/bootstrap.test.js b/packages/cmf/__tests__/bootstrap.test.js index 7dec37febff..01ed0f1ee49 100644 --- a/packages/cmf/__tests__/bootstrap.test.js +++ b/packages/cmf/__tests__/bootstrap.test.js @@ -34,6 +34,7 @@ jest.mock('../src/onError', () => ({ })); jest.mock('../src/registry', () => ({ registerMany: jest.fn(), + getRegistry: jest.fn(), })); jest.mock('../src/actionCreator', () => ({ registerMany: jest.fn(), diff --git a/packages/cmf/__tests__/cmfConnect.test.js b/packages/cmf/__tests__/cmfConnect.test.js index 63c07177d44..13d2c743770 100644 --- a/packages/cmf/__tests__/cmfConnect.test.js +++ b/packages/cmf/__tests__/cmfConnect.test.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { fromJS, Map } from 'immutable'; -import { shallow, mount } from 'enzyme'; -import uuid from 'uuid'; +import { mount } from 'enzyme'; + +// eslint-disable-next-line import omit from 'lodash/omit'; import expression from '../src/expression'; -import mock from '../src/mock'; +import { mock } from '../src'; import { mapStateToViewProps } from '../src/settings'; import cmfConnect, { @@ -53,7 +54,7 @@ describe('cmfConnect', () => { describe('#getStateToProps', () => { it('should call getStateProps', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.components = fromJS({ TestComponent: { testId: { @@ -71,7 +72,7 @@ describe('cmfConnect', () => { }); it('should inject view settings using props.view', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.components = fromJS({}); const props = getStateToProps({ componentId: 'testId', @@ -83,7 +84,7 @@ describe('cmfConnect', () => { }); it('should inject view settings using displayName and componentId', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.components = fromJS({}); state.cmf.settings.props['TestComponent#default'] = { foo: 'from-displayName' }; state.cmf.settings.props['TestComponent#props-id'] = { foo: 'from-props-componentId' }; @@ -114,7 +115,7 @@ describe('cmfConnect', () => { delete state.cmf.settings.props['TestComponent#connect-id']; }); it('should evaluate expression using all props', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.components = fromJS({}); expression.register('hasModel', ({ payload }) => payload.model !== undefined); const props = getStateToProps({ @@ -129,7 +130,7 @@ describe('cmfConnect', () => { expect(props.model).toBeUndefined(); }); it('should pass view settings together with own props when calling mapStateToProps', () => { - const state = mock.state(); + const state = mock.store.state(); const mapStateToProps = jest.fn(); const ownProps = { view: 'simple' }; getStateToProps({ @@ -215,19 +216,19 @@ describe('cmfConnect', () => { }); it('should create a connected component', () => { - const TestComponent = jest.fn(); + const TestComponent = jest.fn(() => null); TestComponent.displayName = 'TestComponent'; mapStateToViewProps.cache.clear(); const CMFConnected = cmfConnect({})(TestComponent); expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); - const wrapper = shallow(, { context: mock.context() }); - expect(wrapper.props()).toMatchSnapshot(); + const wrapper = mount(, mock.Provider.getEnzymeOption(mock.store.context())); + expect(wrapper.find('CMF(TestComponent)').props()).toMatchSnapshot(); }); it('should expose getState static function to get the state', () => { expect(typeof CMFConnectedButton.getState).toBe('function'); - const state = mock.state(); + const state = mock.store.state(); state.cmf.components = fromJS({ Button: { default: { foo: 'bar' }, @@ -259,7 +260,7 @@ describe('cmfConnect', () => { it('should expose setStateAction static function to get the redux action to setState', () => { expect(typeof CMFConnectedButton.setStateAction).toBe('function'); - const state = mock.state(); + const state = mock.store.state(); state.cmf.components = fromJS({ Button: { default: { foo: 'foo' }, @@ -294,7 +295,7 @@ describe('cmfConnect', () => { }); it('should support no context in dispatchActionCreator', () => { const TestComponent = props => { - const rest = Object.assign({}, omit(props, cmfConnect.INJECTED_PROPS)); + const rest = { ...omit(props, cmfConnect.INJECTED_PROPS) }; return
; }; TestComponent.displayName = 'TestComponent'; @@ -304,8 +305,11 @@ describe('cmfConnect', () => { const props = { dispatchActionCreator: jest.fn(), }; - const context = mock.context(); - const wrapper = mount(, { context }); + const context = mock.store.context(); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); const injectedProps = wrapper.find(TestComponent).props(); expect(injectedProps.dispatchActionCreator).not.toBe(props.dispatchActionCreator); const event = {}; @@ -317,7 +321,7 @@ describe('cmfConnect', () => { expect(call[1]).toBe(event); expect(call[2]).toBe(data); expect(call[3].registry).toBe(context.registry); - expect(call[3].store).toBe(context.store); + expect(call[3].store).toMatchObject(context.store); }); it('should pass defaultState when there is no component state in store', () => { @@ -326,18 +330,13 @@ describe('cmfConnect', () => { const defaultState = new Map({ toto: 'lol' }); const CMFConnected = cmfConnect({ defaultState })(TestComponent); - const wrapper = mount(, { - context: mock.context(), - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount(, mock.Provider.getEnzymeOption(mock.store.context())); expect(wrapper.find(TestComponent).props().state).toBe(defaultState); }); it('should componentDidMount initState and dispatchActionCreator after the saga', () => { - const TestComponent = jest.fn(); + const TestComponent = jest.fn(() => null); TestComponent.displayName = 'TestComponent'; const STATE = new Map(); const CMFConnected = cmfConnect({})(TestComponent); @@ -349,9 +348,8 @@ describe('cmfConnect', () => { foo: 'bar', saga: 'saga', }; - const context = mock.context(); - const instance = new CMFConnected.CMFContainer(props, context); - instance.componentDidMount(); + const context = mock.store.context(); + mount(, mock.Provider.getEnzymeOption(context)); expect(props.dispatchActionCreator).toHaveBeenCalled(); const callSagaActionCreator = props.dispatchActionCreator.mock.calls[0]; const callDidMountActionCreator = props.dispatchActionCreator.mock.calls[1]; @@ -362,25 +360,24 @@ describe('cmfConnect', () => { }); expect(callDidMountActionCreator[0]).toBe('hello'); expect(callDidMountActionCreator[1]).toBe(null); - expect(callDidMountActionCreator[2]).toBe(props); - expect(callDidMountActionCreator[3].registry).toBe(instance.context.registry); - expect(callDidMountActionCreator[3].store).toBe(instance.context.store); + expect(callDidMountActionCreator[2]).toEqual(props); + expect(callDidMountActionCreator[3].registry).toBe(context.registry); + expect(callDidMountActionCreator[3].store).toMatchObject(context.store); expect(props.initState).toHaveBeenCalled(); expect(props.initState.mock.calls[0][0]).toBe(props.initialState); }); it('should componentDidMount support saga', () => { - const TestComponent = jest.fn(); + const TestComponent = jest.fn(() => null); TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); const props = { saga: 'hello', dispatchActionCreator: jest.fn(), }; - const context = mock.context(); - const instance = new CMFConnected.CMFContainer(props, context); - instance.componentDidMount(); + const context = mock.store.context(); + mount(, mock.Provider.getEnzymeOption(context)); expect(props.dispatchActionCreator).toHaveBeenCalledWith( 'cmf.saga.start', { type: 'DID_MOUNT', componentId: '42' }, @@ -388,12 +385,15 @@ describe('cmfConnect', () => { componentId: 'default', saga: 'hello', }), - instance.context, + expect.objectContaining({ + store: context.store, + registry: context.registry, + }), ); }); it('should componentWillUnmount support saga', () => { - const TestComponent = jest.fn(); + const TestComponent = jest.fn(() => null); TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); const props = { @@ -401,19 +401,25 @@ describe('cmfConnect', () => { dispatchActionCreator: jest.fn(), deleteState: jest.fn(), }; - const context = mock.context(); - const instance = new CMFConnected.CMFContainer(props, context); - instance.componentWillUnmount(); + const context = mock.store.context(); + const instance = mount( + , + mock.Provider.getEnzymeOption(context), + ); + instance.unmount(); expect(props.dispatchActionCreator).toHaveBeenCalledWith( 'cmf.saga.stop', { type: 'WILL_UNMOUNT', componentId: '42' }, - instance.props, - instance.context, + props, + expect.objectContaining({ + store: context.store, + registry: context.registry, + }), ); }); it('should componentWillUnMount dispatchActionCreator', () => { - const TestComponent = jest.fn(); + const TestComponent = jest.fn(() => null); TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); const props = { @@ -422,16 +428,22 @@ describe('cmfConnect', () => { deleteState: jest.fn(), foo: 'bar', }; - const context = mock.context(); - const instance = new CMFConnected.CMFContainer(props, context); - instance.componentWillUnmount(); + const context = mock.store.context(); + context.registry = { + 'actionCreator:bye': jest.fn(), + }; + const instance = mount( + , + mock.Provider.getEnzymeOption(context), + ); + instance.unmount(); expect(props.dispatchActionCreator).toHaveBeenCalled(); const call = props.dispatchActionCreator.mock.calls[0]; expect(call[0]).toBe('bye'); expect(call[1]).toBe(null); - expect(call[2]).toBe(props); - expect(call[3].registry).toBe(instance.context.registry); - expect(call[3].store).toBe(instance.context.store); + expect(call[2]).toEqual(props); + expect(call[3].registry).toBe(context.registry); + expect(call[3].store).toBe(context.store); expect(props.deleteState).toHaveBeenCalled(); expect(props.deleteState.mock.calls[0][0]).toBe(); @@ -446,15 +458,10 @@ describe('cmfConnect', () => { })(TestComponent); expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount(, mock.Provider.getEnzymeOption(context)); // when wrapper.unmount(); @@ -474,15 +481,10 @@ describe('cmfConnect', () => { })(TestComponent); expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount(, mock.Provider.getEnzymeOption(context)); // when wrapper.unmount(); @@ -501,15 +503,13 @@ describe('cmfConnect', () => { })(TestComponent); expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); // when wrapper.unmount(); @@ -528,15 +528,13 @@ describe('cmfConnect', () => { })(TestComponent); expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); // when wrapper.unmount(); @@ -558,15 +556,10 @@ describe('cmfConnect', () => { const TestComponent = () =>
; TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount(, mock.Provider.getEnzymeOption(context)); const props = wrapper.find(TestComponent).props(); // then @@ -579,10 +572,17 @@ describe('cmfConnect', () => { it('should expose displayName', () => { const ArrowComponent = () =>
; + ArrowComponent.displayName = 'ArrowComponent'; function FunctionComponent() { return
; } - class ClassComponent extends React.Component {} + FunctionComponent.displayName = 'FunctionComponent'; + + // eslint-disable-next-line react/prefer-stateless-function + class ClassComponent extends React.Component { + static displayName = 'ClassComponent'; + } + const CMFConnectedArrow = cmfConnect({})(ArrowComponent); const CMFConnectedFunction = cmfConnect({})(FunctionComponent); const CMFConnectedClass = cmfConnect({})(ClassComponent); @@ -594,15 +594,13 @@ describe('cmfConnect', () => { const onClickDispatch = { type: 'MY_BUTTON_CLICKED', }; - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); const props = wrapper.find(Button).props(); expect(props.onClickDispatch).toBeUndefined(); expect(props.onClick).toBeDefined(); @@ -617,18 +615,16 @@ describe('cmfConnect', () => { }); it('should transform onEventActionCreator props to onEvent handler', () => { const onClickActionCreator = 'myactionCreator'; - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); context.registry = { 'actionCreator:myactionCreator': event => ({ type: 'FETCH_STUFF', event }), }; - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); const props = wrapper.find(Button).props(); expect(props.onClick).toBeDefined(); expect(props.onClickActionCreator).toBeUndefined(); @@ -649,7 +645,7 @@ describe('cmfConnect', () => { cmf: { collectionId: 'foo' }, }, }; - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); context.registry = { 'actionCreator:myfetch': (event, data) => ({ @@ -659,12 +655,10 @@ describe('cmfConnect', () => { }), }; - const wrapper = mount(, { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); const props = wrapper.find(Button).props(); expect(props.onClick).toBeDefined(); expect(context.store.dispatch).not.toHaveBeenCalled(); @@ -680,17 +674,12 @@ describe('cmfConnect', () => { const config = { disabled: true, }; - const context = mock.context(); + const context = mock.store.context(); context.store.dispatch = jest.fn(); const wrapper = mount( , - { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }, + mock.Provider.getEnzymeOption(context), ); const props = wrapper.find(Button).props(); expect(props.onClick).toBeDefined(); @@ -712,8 +701,8 @@ describe('cmfConnect', () => { }); it('should spread cmf state when onEventSetState is set', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); context.store.getState = () => { return { cmf: { @@ -731,21 +720,19 @@ describe('cmfConnect', () => { const wrapper = mount( , - { - context, - childContextTypes: { - registry: PropTypes.object, - }, - }, + mock.Provider.getEnzymeOption(context), ); const props = wrapper.find(Button).props(); expect(props.inProgress).toBe(false); }); it('should check that component will not be rendered if renderIf equals false', () => { - const context = mock.context(); + const context = mock.store.context(); const CMFConnected = cmfConnect({})(Button); - const mounted = mount(); + const mounted = mount( + , + mock.Provider.getEnzymeOption(), + ); expect(mounted.html()).toBe(''); }); @@ -774,15 +761,17 @@ describe('cmfConnect', () => { }); describe('#omitCMFProps', () => { it('should cmfConnect({ omitCMFProps: false }) keep compatibility', () => { - const context = mock.context(); + const store = mock.store.store(); const TestComponent = props =>
; + TestComponent.displayName = 'TestComponent'; const WithCMFProps = cmfConnect({ omitCMFProps: false })(TestComponent); - const wrapperWithCMFProps = shallow( - shallow(, { - context: { store: context.store }, - }).getElement(), - ); + const wrapperWithCMFProps = mount( + , + mock.Provider.getEnzymeOption(), + ).find('div'); + expect(Object.keys(wrapperWithCMFProps.props())).toEqual([ + 'store', 'className', 'id', 'setState', @@ -796,34 +785,37 @@ describe('cmfConnect', () => { ]); }); it('should cmfConnect({ omitCMFProps: true }) remove all internals', () => { - const context = mock.context(); const TestComponent = props =>
; + TestComponent.displayName = 'TestComponent'; const WithoutCMFProps = cmfConnect({ omitCMFProps: true })(TestComponent); - const wrapperWithoutCMFProps = shallow( - shallow(, { - context: { store: context.store }, - }).getElement(), - ); + const store = mock.store.store(); + const wrapperWithoutCMFProps = mount( + , + mock.Provider.getEnzymeOption(), + ).find('div'); expect(wrapperWithoutCMFProps.props()).toEqual({ className: 'foo', id: 'bar', + store, }); }); it('should cmfConnect({ omitCMFProps: true, withComponentRegistry: true }) add getComponent', () => { - const context = mock.context(); const TestComponent = props =>
; + TestComponent.displayName = 'TestComponent'; const WithoutCMFProps = cmfConnect({ omitCMFProps: true, withComponentRegistry: true })( TestComponent, ); - const wrapperWithoutCMFProps = shallow( - shallow(, { - context: { store: context.store }, - }).getElement(), - ); + const store = mock.store.store(); + const wrapperWithoutCMFProps = mount( + , + mock.Provider.getEnzymeOption(), + ).find('div'); + expect(wrapperWithoutCMFProps.props()).toEqual({ className: 'foo', id: 'bar', getComponent: component.get, + store, }); }); }); diff --git a/packages/cmf/__tests__/cmfModule.merge.test.js b/packages/cmf/__tests__/cmfModule.merge.test.js index 5e6802c7730..9402e4d3f20 100644 --- a/packages/cmf/__tests__/cmfModule.merge.test.js +++ b/packages/cmf/__tests__/cmfModule.merge.test.js @@ -6,6 +6,22 @@ import { mount } from 'enzyme'; import mergeModules from '../src/cmfModule.merge'; describe('mergeModule', () => { + // eslint-disable-next-line no-console + const originalLog = console; + beforeEach(() => { + // eslint-disable-next-line no-console + global.console = { + warn: jest.fn(), + log: jest.fn(), + }; + }); + afterEach( () => { + // eslint-disable-next-line no-console + global.console = originalLog; + }); + afterAll(() => { + console.log('test ####'); + }); it('should merge components config', () => { const a = { components: { diff --git a/packages/cmf/__tests__/expressions/index.test.js b/packages/cmf/__tests__/expressions/index.test.js index 759eb3cec48..f13a854920d 100644 --- a/packages/cmf/__tests__/expressions/index.test.js +++ b/packages/cmf/__tests__/expressions/index.test.js @@ -1,6 +1,6 @@ import Immutable from 'immutable'; import expressions from '../../src/expressions'; -import mock from '../../src/mock'; +import { mock } from '../../src'; describe('expressions', () => { it('should export some expressions', () => { @@ -11,8 +11,8 @@ describe('expressions', () => { }); describe('cmf.collections.get', () => { it('should get collection content', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ title: 'my title', @@ -24,8 +24,8 @@ describe('expressions', () => { ); }); it("should return default value if collection doesn't exists", () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); context.store.getState = () => state; state.cmf.collections = new Immutable.Map({}); expect(expressions['cmf.collections.get']({ context }, 'article.title', 'no title')).toBe( @@ -36,8 +36,8 @@ describe('expressions', () => { describe('cmf.collections.includes', () => { it('should return true if the value is present in the list', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ title: 'title', @@ -50,8 +50,8 @@ describe('expressions', () => { ); }); it('should return false if the value is not present in the list', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ title: 'title', @@ -64,8 +64,8 @@ describe('expressions', () => { ); }); it("should return false if collection doesn't exists", () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); context.store.getState = () => state; state.cmf.collections = new Immutable.Map({}); expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test')).toBe( @@ -75,8 +75,8 @@ describe('expressions', () => { }); describe('cmf.collections.oneOf', () => { it('should return true if one of the values is present in the list', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ title: 'title', @@ -89,8 +89,8 @@ describe('expressions', () => { ).toBe(true); }); it('should return false if all values are not present in the list', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ title: 'title', @@ -103,8 +103,8 @@ describe('expressions', () => { ).toBe(false); }); it("should return false if collection doesn't exist", () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); context.store.getState = () => state; state.cmf.collections = new Immutable.Map({}); expect( @@ -112,8 +112,8 @@ describe('expressions', () => { ).toBe(false); }); it('should throw an error if values are not an array', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); context.store.getState = () => state; state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ @@ -128,8 +128,8 @@ describe('expressions', () => { }); describe('cmf.collections.allOf', () => { it('should return true if all of the values are present in the list', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ title: 'title', @@ -146,8 +146,8 @@ describe('expressions', () => { ).toBe(true); }); it('should return false if not all values are not present in the list', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ title: 'title', @@ -160,8 +160,8 @@ describe('expressions', () => { ).toBe(false); }); it("should return false if collection doesn't exist", () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); context.store.getState = () => state; state.cmf.collections = new Immutable.Map({}); expect( @@ -169,8 +169,8 @@ describe('expressions', () => { ).toBe(false); }); it('should throw an error if values are not an array', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); context.store.getState = () => state; state.cmf.collections = new Immutable.Map({ article: new Immutable.Map({ @@ -186,8 +186,8 @@ describe('expressions', () => { describe('cmf.components.get', () => { it('should get component state', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.components = new Immutable.Map({ MyComponent: new Immutable.Map({ default: new Immutable.Map({ @@ -201,8 +201,8 @@ describe('expressions', () => { ).toBe(true); }); it('should return default value if no component state', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.components = new Immutable.Map({}); context.store.getState = () => state; expect( @@ -213,8 +213,8 @@ describe('expressions', () => { describe('cmf.components.includes', () => { it('should return true if the value is present in the list', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.components = new Immutable.Map({ MyComponent: new Immutable.Map({ default: new Immutable.Map({ @@ -229,8 +229,8 @@ describe('expressions', () => { ).toBe(true); }); it('should return default false if there is no component state', () => { - const context = mock.context(); - const state = mock.state(); + const context = mock.store.context(); + const state = mock.store.state(); state.cmf.components = new Immutable.Map({}); context.store.getState = () => state; expect( diff --git a/packages/cmf/__tests__/onEvent.test.js b/packages/cmf/__tests__/onEvent.test.js index 0092d0682af..4d7a0d3d769 100644 --- a/packages/cmf/__tests__/onEvent.test.js +++ b/packages/cmf/__tests__/onEvent.test.js @@ -17,37 +17,37 @@ describe('onEvent', () => { currentHandler = jest.fn(); }); it('should return a function', () => { - const handler = onEvent.getOnEventSetStateHandler(instance, config, currentHandler); + const handler = onEvent.getOnEventSetStateHandler(instance, {}, config, currentHandler); expect(typeof handler).toBe('function'); }); it('the handler should call the currentHandler with the same args', () => { - const handler = onEvent.getOnEventSetStateHandler(instance, config, currentHandler); + const handler = onEvent.getOnEventSetStateHandler(instance, {}, config, currentHandler); const args = [{ type: 'click' }, { foo: 'bar' }]; handler(...args); expect(currentHandler).toHaveBeenCalledWith(...args); }); it('the handler should call props.setState with static value', () => { - const handler = onEvent.getOnEventSetStateHandler(instance, config, currentHandler); + const handler = onEvent.getOnEventSetStateHandler(instance, {}, config, currentHandler); config.foo = true; handler(); expect(instance.props.setState).toHaveBeenCalledWith(config); }); it('the handler should call props.setState with parsed arguments', () => { - const handler = onEvent.getOnEventSetStateHandler(instance, config, currentHandler); + const handler = onEvent.getOnEventSetStateHandler(instance, {}, config, currentHandler); const args = [{ type: 'click' }, { foo: 'bar' }]; config.inProgress = [1, 'foo']; handler(...args); expect(instance.props.setState).toHaveBeenCalledWith({ inProgress: 'bar' }); }); it('the handler should call props.setState with parsed arguments', () => { - const handler = onEvent.getOnEventSetStateHandler(instance, config, currentHandler); + const handler = onEvent.getOnEventSetStateHandler(instance, {}, config, currentHandler); const args = [{ type: 'click' }, { foo: 'bar' }]; config.inProgress = [1]; handler(...args); expect(instance.props.setState).toHaveBeenCalledWith({ inProgress: args[1] }); }); it('the handler should call props.setState and toggle a value', () => { - const handler = onEvent.getOnEventSetStateHandler(instance, config, currentHandler); + const handler = onEvent.getOnEventSetStateHandler(instance, {}, config, currentHandler); const args = [{ type: 'click' }, { foo: 'bar' }]; config.docked = 'toggle'; handler(...args); diff --git a/packages/cmf/__tests__/reduxstorage/reduxLocalStorage.test.js b/packages/cmf/__tests__/reduxstorage/reduxLocalStorage.test.js index 5731bbb8cc8..b5d84d87c8f 100644 --- a/packages/cmf/__tests__/reduxstorage/reduxLocalStorage.test.js +++ b/packages/cmf/__tests__/reduxstorage/reduxLocalStorage.test.js @@ -1,6 +1,19 @@ import reduxLocalStorage from '../../src/reduxstorage/reduxLocalStorage'; describe('reduxLocalStorage', () => { + // eslint-disable-next-line no-console + const originalLog = console; + beforeEach(() => { + // eslint-disable-next-line no-console + global.console = { + warn: jest.fn(), + log: jest.fn(), + }; + }); + afterEach( () => { + // eslint-disable-next-line no-console + global.console = originalLog; + }); it('should expose API', () => { expect(typeof reduxLocalStorage.loadInitialState).toBe('function'); expect(typeof reduxLocalStorage.saveOnReload).toBe('function'); diff --git a/packages/cmf/__tests__/sagas/collection.test.js b/packages/cmf/__tests__/sagas/collection.test.js index 4432d9c19d0..3cc85213091 100644 --- a/packages/cmf/__tests__/sagas/collection.test.js +++ b/packages/cmf/__tests__/sagas/collection.test.js @@ -1,5 +1,4 @@ -import { call, select } from 'redux-saga/effects'; -import { delay } from 'redux-saga'; +import { delay, call, select } from 'redux-saga/effects'; import Immutable from 'immutable'; import selectors from '../../src/selectors'; import { @@ -16,7 +15,7 @@ describe('waitFor', () => { const withCollection = withoutCollection.cmf.collections.set('foo', new Immutable.Map({})); const gen = waitFor('foo'); expect(gen.next().value).toEqual(select(selectors.collections.get, 'foo')); - expect(gen.next().value).toEqual(call(delay, 10)); + expect(gen.next().value).toEqual(delay(10)); expect(gen.next().value).toEqual(select(selectors.collections.get, 'foo')); expect(gen.next(withCollection).value).toBeUndefined(); }); diff --git a/packages/cmf/__tests__/sagas/component.test.js b/packages/cmf/__tests__/sagas/component.test.js index cab23cb18a2..abd36ef0adf 100644 --- a/packages/cmf/__tests__/sagas/component.test.js +++ b/packages/cmf/__tests__/sagas/component.test.js @@ -1,5 +1,5 @@ import { spawn, takeEvery, cancel } from 'redux-saga/effects'; -import { createMockTask } from 'redux-saga/utils'; +import { createMockTask } from '@redux-saga/testing-utils'; import registry from '../../src/registry'; import { onSagaStart, handle } from '../../src/sagas/component'; import CONST from '../../src/constant'; @@ -23,9 +23,9 @@ describe('sagas.component', () => { // then expect(gen.next().value).toEqual(spawn(saga, { componentId: 'myComponent' })); const next = gen.next(task).value; - expect(next.TAKE).toBeDefined(); + expect(next.payload).toBeDefined(); expect( - next.TAKE.pattern({ + next.payload.pattern({ type: `${CONST.WILL_UNMOUNT_SAGA_STOP}_my-saga`, event: { componentId: 41, @@ -33,7 +33,7 @@ describe('sagas.component', () => { }), ).toBeFalsy(); expect( - next.TAKE.pattern({ + next.payload.pattern({ type: `${CONST.WILL_UNMOUNT_SAGA_STOP}_my-saga2`, event: { componentId: 42, @@ -41,7 +41,7 @@ describe('sagas.component', () => { }), ).toBeFalsy(); expect( - next.TAKE.pattern({ + next.payload.pattern({ type: `${CONST.WILL_UNMOUNT_SAGA_STOP}_my-saga`, event: { componentId: 42, @@ -70,7 +70,7 @@ describe('sagas.component', () => { // then expect(gen.next().value).toEqual(spawn(saga, { componentId: 'myComponent' })); const next = gen.next(task).value; - expect(next.TAKE).toBeDefined(); + expect(next.payload).toBeDefined(); expect(gen.next({ event: { componentId: 42 } }).value).toEqual(cancel(task)); }); diff --git a/packages/cmf/__tests__/settings.test.js b/packages/cmf/__tests__/settings.test.js index 7e4029c53e8..b8e9304953f 100644 --- a/packages/cmf/__tests__/settings.test.js +++ b/packages/cmf/__tests__/settings.test.js @@ -1,30 +1,40 @@ import React from 'react'; import { mount } from 'enzyme'; +import { Provider } from 'react-redux'; import { generateDefaultViewId, mapStateToViewProps, WaitForSettings } from '../src/settings'; -import mock, { store } from '../src/mock'; - +import { mock } from '../src'; describe('settings', () => { describe('mapStateToViewProps', () => { it('should apply default props from displayName if no view are passed', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.settings.props.MyComponent = { foo: 'bar' }; const props = mapStateToViewProps(state, { views: undefined }, 'MyComponent'); expect(props.foo).toBe('bar'); }); it('should apply default props from displayName and componentId if no view are passed', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.settings.props.MyComponent = { foo: 'bar' }; state.cmf.settings.props['MyComponent#my-component-id'] = { foo: 'baz' }; - const props = mapStateToViewProps(state, { view: undefined }, 'MyComponent', 'my-component-id'); + const props = mapStateToViewProps( + state, + { view: undefined }, + 'MyComponent', + 'my-component-id', + ); expect(props.foo).toBe('baz'); }); it('should apply default props from displayName and componentId without HOC', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.settings.props.MyComponent = { foo: 'bar' }; state.cmf.settings.props['MyComponent#my-component-id'] = { foo: 'baz' }; - const props = mapStateToViewProps(state, { view: undefined }, 'Translate(Container(MyComponent))', 'my-component-id'); + const props = mapStateToViewProps( + state, + { view: undefined }, + 'Translate(Container(MyComponent))', + 'my-component-id', + ); expect(props.foo).toBe('baz'); }); }); @@ -42,9 +52,7 @@ describe('settings', () => { }); it('return componentName if the only given parameter', () => { - expect(generateDefaultViewId(undefined, 'componentName')).toBe( - 'componentName', - ); + expect(generateDefaultViewId(undefined, 'componentName')).toBe('componentName'); }); it('return undefined if all parameter are undefined', () => { @@ -57,32 +65,29 @@ describe('settings', () => { }); describe('WaitForSettings', () => { it('should display using loader if state settings is not initialized', () => { - const state = mock.state(); + const state = mock.store.state(); const wrapper = mount(Hello, { - context: { - store: store.store(state), - }, + wrappingComponent: Provider, + wrappingComponentProps: { store: mock.store.store(state) }, }); expect(wrapper.text()).toBe('loading'); }); it('should display loading using AppLoader', () => { const AppLoader = () =>

custom loader

; - const state = mock.state(); + const state = mock.store.state(); const wrapper = mount(Hello, { - context: { - store: store.store(state), - }, + wrappingComponent: Provider, + wrappingComponentProps: { store: mock.store.store(state) }, }); expect(wrapper.text()).not.toBe('loading'); expect(wrapper.text()).toBe('custom loader'); }); it('should display children when settings are initialized', () => { - const state = mock.state(); + const state = mock.store.state(); state.cmf.settings.initialized = true; const wrapper = mount(Hello, { - context: { - store: store.store(state), - }, + wrappingComponent: Provider, + wrappingComponentProps: { store: mock.store.store(state) }, }); expect(wrapper.text()).not.toBe('loading'); expect(wrapper.text()).toBe('Hello'); diff --git a/packages/cmf/package.json b/packages/cmf/package.json index fd1abae1f3a..7478c10419d 100644 --- a/packages/cmf/package.json +++ b/packages/cmf/package.json @@ -47,11 +47,11 @@ "path-to-regexp": "^2.4.0", "prop-types": "^15.8.1", "react-immutable-proptypes": "^2.2.0", - "react-redux": "^5.1.2", - "redux": "^3.7.2", - "redux-batched-actions": "^0.2.1", + "react-redux": "^7.2.2", + "redux": "^4.0.5", + "redux-batched-actions": "^0.5.0", "redux-batched-subscribe": "^0.1.6", - "redux-saga": "^0.15.6", + "redux-saga": "^1.1.3", "redux-storage": "^4.1.2", "redux-storage-decorator-filter": "^1.1.8", "redux-storage-decorator-immutablejs": "^1.0.4", @@ -60,6 +60,7 @@ "uuid": "^3.4.0" }, "devDependencies": { + "@redux-saga/testing-utils": "^1.1.3", "@talend/scripts-core": "^11.3.0", "@talend/scripts-preset-react-lib": "^9.9.3", "enzyme": "^3.11.0", diff --git a/packages/cmf/src/App.js b/packages/cmf/src/App.js index 0076f6c1140..967b568985f 100644 --- a/packages/cmf/src/App.js +++ b/packages/cmf/src/App.js @@ -23,7 +23,7 @@ export default function App(props) { } return ( - + {content} @@ -33,6 +33,7 @@ export default function App(props) { App.displayName = 'CMFApp'; App.propTypes = { store: PropTypes.object.isRequired, + registry: PropTypes.object, children: PropTypes.node, withSettings: PropTypes.bool, loading: PropTypes.func, diff --git a/packages/cmf/src/Dispatcher.js b/packages/cmf/src/Dispatcher.js index 7af118f81d3..e6c63ef8022 100644 --- a/packages/cmf/src/Dispatcher.js +++ b/packages/cmf/src/Dispatcher.js @@ -9,6 +9,7 @@ import React from 'react'; import cmfConnect from './cmfConnect'; import action from './action'; import actionCreator from './actionCreator'; +import { RegistryContext } from './RegistryProvider'; /** * This component purpose is to decorate any component and map an user event @@ -20,27 +21,9 @@ function myfunc(event, props, context) { */ -export class Dispatcher extends React.Component { - static displayName = 'Dispatcher'; - - static propTypes = { - children: PropTypes.node.isRequired, - stopPropagation: PropTypes.bool, - preventDefault: PropTypes.bool, - dispatchActionCreator: PropTypes.func, - }; - - static contextTypes = { - registry: PropTypes.object.isRequired, - }; - - /** - * @param {object} props only one child under children - */ - constructor(props) { - super(props); - this.onEvent = this.onEvent.bind(this); - } +export function Dispatcher(props) { + const registry = React.useContext(RegistryContext); + // console.log('@@@ registry', registry); /** * on any even just try to find a onTHEEVENT props. @@ -49,43 +32,45 @@ export class Dispatcher extends React.Component { * @param {object} event the react event dispatched event * @param {string} eventName the name of the event */ - onEvent(event, eventName) { - if (this.props.stopPropagation) { + function onEvent(event, eventName) { + if (props.stopPropagation) { event.stopPropagation(); } - if (this.props.preventDefault) { + if (props.preventDefault) { event.preventDefault(); } - if (this.props[eventName]) { - this.props.dispatchActionCreator(this.props[eventName], event, this.props); + if (props[eventName]) { + props.dispatchActionCreator(props[eventName], event, props); } } - checkIfActionInfoExist() { - action.getOnProps(this.props).forEach(name => { - if (typeof this.props[name] === 'string') { - actionCreator.get(this.context, this.props[name]); + function checkIfActionInfoExist() { + action.getOnProps(props).forEach(name => { + if (typeof props[name] === 'string') { + actionCreator.get({ registry }, props[name]); } }); } - /** - * @return {object} ReactElement - */ - render() { - this.checkIfActionInfoExist(); - const onProps = action.getOnProps(this.props); - const childrenWithProps = React.Children.map(this.props.children, child => { - const props = {}; - onProps.forEach(name => { - props[name] = event => this.onEvent(event, name); - }); - return React.cloneElement(child, props); + checkIfActionInfoExist(); + const onProps = action.getOnProps(props); + const childrenWithProps = React.Children.map(props.children, child => { + const newProps = {}; + onProps.forEach(name => { + newProps[name] = event => onEvent(event, name); }); - return React.Children.only(childrenWithProps[0]); - } + return React.cloneElement(child, newProps); + }); + return React.Children.only(childrenWithProps[0]); } +Dispatcher.propTypes = { + children: PropTypes.node.isRequired, + stopPropagation: PropTypes.bool, + preventDefault: PropTypes.bool, + dispatchActionCreator: PropTypes.func, +}; +Dispatcher.displayName = 'Dispatcher'; Dispatcher.defaultProps = { stopPropagation: false, preventDefault: false, diff --git a/packages/cmf/src/Inject.component.js b/packages/cmf/src/Inject.component.js index 685d7e1b36c..a5f8b85a3ad 100644 --- a/packages/cmf/src/Inject.component.js +++ b/packages/cmf/src/Inject.component.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import componentAPI from './component'; +import { useCMFContext } from './useContext'; /** * The Inject component let you use the registry to render named component @@ -28,7 +29,8 @@ NotFoundComponent.propTypes = { error: PropTypes.string.isRequired, }; -function Inject({ component, ...props }, context) { +function Inject({ component, ...props }) { + const context = useCMFContext(); try { const Component = componentAPI.get(component, context); return ; @@ -36,9 +38,6 @@ function Inject({ component, ...props }, context) { return ; } } -Inject.contextTypes = { - registry: PropTypes.object.isRequired, -}; Inject.propTypes = { component: PropTypes.string.isRequired, }; diff --git a/packages/cmf/src/RegistryProvider.js b/packages/cmf/src/RegistryProvider.js index 5d10e96a81e..f44ccc44271 100644 --- a/packages/cmf/src/RegistryProvider.js +++ b/packages/cmf/src/RegistryProvider.js @@ -4,39 +4,10 @@ * @module react-cmf/lib/RegistryProvider * @see module:react-cmf/lib/App */ -import PropTypes from 'prop-types'; -import React, { Children } from 'react'; +import React from 'react'; import Registry from './registry'; -/** - * The provider is a JSX wrapper to inject the registry as a context var - * You should never need to use this, it's an internal component - */ -export default class RegistryProvider extends React.Component { - constructor(props) { - super(props); - this.registry = Registry.getRegistry(); - } - - /** - * @return {object} child with registry as only key - */ - getChildContext() { - return { registry: this.registry }; - } - - /** - * react rendering - * @return {object} ReactElement - */ - render() { - return Children.only(this.props.children); - } -} +export const RegistryContext = React.createContext(Registry.getRegistry()); +export const RegistryProvider = RegistryContext.Provider; -RegistryProvider.propTypes = { - children: PropTypes.element.isRequired, -}; -RegistryProvider.childContextTypes = { - registry: PropTypes.object, -}; +export default RegistryContext.Provider; diff --git a/packages/cmf/src/actionCreator.js b/packages/cmf/src/actionCreator.js index f41c17f0c43..c63c5891b4f 100644 --- a/packages/cmf/src/actionCreator.js +++ b/packages/cmf/src/actionCreator.js @@ -10,6 +10,7 @@ import CONST from './constant'; function get(context, id) { const creator = context.registry[`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:${id}`]; if (!creator) { + // console.log('@@ registry', id, Object.keys(context.registry)); throw new Error(`actionCreator not found in the registry: ${id}`); } return creator; diff --git a/packages/cmf/src/bootstrap.js b/packages/cmf/src/bootstrap.js index 4d08edef4e2..84bf408eac8 100644 --- a/packages/cmf/src/bootstrap.js +++ b/packages/cmf/src/bootstrap.js @@ -149,7 +149,12 @@ export default async function bootstrap(appOptions = {}) { saga.run(); render( - + , element, diff --git a/packages/cmf/src/cmfConnect.js b/packages/cmf/src/cmfConnect.js index 47787b76017..3609c8a0b16 100644 --- a/packages/cmf/src/cmfConnect.js +++ b/packages/cmf/src/cmfConnect.js @@ -4,18 +4,11 @@ * @example import { cmfConnect } from '@talend/react-cmf'; -class MyComponent extends React.Component { - static displayName = 'MyComponent'; - constructor(props) { - super(props); - this.onClick = this.onClick.bind(this); - } - onClick(event) { - return this.props.dispatchActionCreator('myaction', event, { props: this.props }); - } - render() { - return ; - } +function MyComponent(props) { + const onClick = (event) => { + props.dispatchActionCreator('myaction', event, { props: props }); + }; + return ; } function mapStateToProps(state) { @@ -32,7 +25,7 @@ import PropTypes from 'prop-types'; import React, { createElement } from 'react'; import hoistStatics from 'hoist-non-react-statics'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; +import { connect, useStore } from 'react-redux'; import { v4 as uuidv4 } from 'uuid'; import actions from './actions'; import actionCreator from './actionCreator'; @@ -43,6 +36,7 @@ import onEvent from './onEvent'; import { initState, getStateAccessors, getStateProps } from './componentState'; import { mapStateToViewProps } from './settings'; import omit from './omit'; +import { RegistryContext } from './RegistryProvider'; export function getComponentName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; @@ -215,6 +209,7 @@ export default function cmfConnect({ } } let displayNameWarning = true; + return function wrapWithCMF(WrappedComponent) { if (!WrappedComponent.displayName && displayNameWarning) { displayNameWarning = false; @@ -238,125 +233,123 @@ export default function cmfConnect({ }, }; } - class CMFContainer extends React.Component { - static displayName = `CMF(${getComponentName(WrappedComponent)})`; - - static propTypes = { - ...cmfConnect.propTypes, - }; - - static contextTypes = { - store: PropTypes.object, - registry: PropTypes.object, - router: PropTypes.object, - }; - static WrappedComponent = WrappedComponent; + function CMFContainer(props, ref) { + const [instanceId] = React.useState(uuidv4()); + const registry = React.useContext(RegistryContext); + const store = useStore(); - static getState = getState; - - static setStateAction = function setStateAction(state, id = 'default', type) { - if (typeof state !== 'function') { - return getSetStateAction(state, id, type); - } - return (_, getReduxState) => - getSetStateAction(state(getState(getReduxState(), id)), id, type); - }; - - constructor(props, context) { - super(props, context); - this.dispatchActionCreator = this.dispatchActionCreator.bind(this); - this.getOnEventProps = this.getOnEventProps.bind(this); - this.id = uuidv4(); + function dispatchActionCreator(actionCreatorId, event, data, extraContext) { + const extendedContext = { registry, store, ...extraContext }; + props.dispatchActionCreator(actionCreatorId, event, data, extendedContext); } - componentDidMount() { - initState(this.props); - if (this.props.saga) { - this.dispatchActionCreator( + React.useEffect(() => { + initState(props); + if (props.saga) { + dispatchActionCreator( 'cmf.saga.start', - { type: 'DID_MOUNT', componentId: this.id }, + { type: 'DID_MOUNT', componentId: instanceId }, { - ...this.props, // DEPRECATED - componentId: getComponentId(componentId, this.props), + ...props, // DEPRECATED + componentId: getComponentId(componentId, props), }, ); } - if (this.props.didMountActionCreator) { - this.dispatchActionCreator(this.props.didMountActionCreator, null, this.props); + if (props.didMountActionCreator) { + dispatchActionCreator(props.didMountActionCreator, null, props); } + return () => { + if (props.willUnmountActionCreator) { + dispatchActionCreator(props.willUnmountActionCreator, null, props); + } + // if the props.keepComponentState is present we have to stick to it + if ( + props.keepComponentState === false || + (props.keepComponentState === undefined && !keepComponentState) + ) { + props.deleteState(props.initialState); + } + if (props.saga) { + dispatchActionCreator( + 'cmf.saga.stop', + { type: 'WILL_UNMOUNT', componentId: instanceId }, + props, + ); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + function getOnEventProps() { + return Object.keys(props).reduce( + (acc, key) => { + // TODO check how to replace the this + onEvent.addOnEventSupport(onEvent.DISPATCH, { props }, acc, key); + onEvent.addOnEventSupport(onEvent.ACTION_CREATOR, { props }, acc, key); + onEvent.addOnEventSupport(onEvent.SETSTATE, { props }, acc, key); + return acc; + }, + { toOmit: [], dispatchActionCreator }, + ); } - componentWillUnmount() { - if (this.props.willUnmountActionCreator) { - this.dispatchActionCreator(this.props.willUnmountActionCreator, null, this.props); - } - // if the props.keepComponentState is present we have to stick to it - if ( - this.props.keepComponentState === false || - (this.props.keepComponentState === undefined && !keepComponentState) - ) { - this.props.deleteState(this.props.initialState); - } - if (this.props.saga) { - this.dispatchActionCreator( - 'cmf.saga.stop', - { type: 'WILL_UNMOUNT', componentId: this.id }, - this.props, - ); - } + if (props.renderIf === false) { + return null; } + const { toOmit, spreadCMFState, ...handlers } = getOnEventProps(); - getOnEventProps() { - return Object.keys(this.props).reduce( - (props, key) => { - onEvent.addOnEventSupport(onEvent.DISPATCH, this, props, key); - onEvent.addOnEventSupport(onEvent.ACTION_CREATOR, this, props, key); - onEvent.addOnEventSupport(onEvent.SETSTATE, this, props, key); - return props; - }, - { toOmit: [] }, - ); + // remove all internal props already used by the container + delete handlers.dispatchActionCreator; + toOmit.push(...CONST.CMF_PROPS, ...propsToOmit); + if (props.omitRouterProps) { + toOmit.push('omitRouterProps', ...CONST.INJECTED_ROUTER_PROPS); + } + let spreadedState = {}; + if ((spreadCMFState || props.spreadCMFState) && props.state) { + spreadedState = props.state.toJS(); } - dispatchActionCreator(actionCreatorId, event, data, context) { - const extendedContext = { ...this.context, ...context }; - this.props.dispatchActionCreator(actionCreatorId, event, data, extendedContext); + const newProps = { + ...omit(props, toOmit), + ...handlers, + ...spreadedState, + }; + if (newProps.dispatchActionCreator && toOmit.indexOf('dispatchActionCreator') === -1) { + // override to inject CMFContainer context + newProps.dispatchActionCreator = dispatchActionCreator; + } + if (!newProps.state && defaultState && toOmit.indexOf('state') === -1) { + newProps.state = defaultState; } + if (rest.forwardRef) { + return ; + } + return ; + } + let CMFWithRef = hoistStatics(CMFContainer, WrappedComponent); + CMFContainer.displayName = `CMF(${getComponentName(WrappedComponent)})`; - render() { - if (this.props.renderIf === false) { - return null; - } - const { toOmit, spreadCMFState, ...handlers } = this.getOnEventProps(); - // remove all internal props already used by the container - toOmit.push(...CONST.CMF_PROPS, ...propsToOmit); - if (this.props.omitRouterProps) { - toOmit.push('omitRouterProps', ...CONST.INJECTED_ROUTER_PROPS); - } - let spreadedState = {}; - if ((spreadCMFState || this.props.spreadCMFState) && this.props.state) { - spreadedState = this.props.state.toJS(); - } - const props = { - ...omit(this.props, toOmit), - ...handlers, - ...spreadedState, - }; - if ( - props.dispatchActionCreator && - props.dispatchActionCreator && - toOmit.indexOf('dispatchActionCreator') === -1 - ) { - // override to inject CMFContainer context - props.dispatchActionCreator = this.dispatchActionCreator; - } - if (!props.state && defaultState && toOmit.indexOf('state') === -1) { - props.state = defaultState; - } - return createElement(WrappedComponent, props); + CMFContainer.propTypes = { + ...cmfConnect.propTypes, + }; + CMFContainer.WrappedComponent = WrappedComponent; + CMFContainer.getState = getState; + + CMFContainer.setStateAction = function setStateAction(state, id = 'default', type) { + if (typeof state !== 'function') { + return getSetStateAction(state, id, type); } + return (_, getReduxState) => + getSetStateAction(state(getState(getReduxState(), id)), id, type); + }; + if (rest.forwardRef) { + CMFWithRef = React.forwardRef(CMFWithRef); + CMFWithRef.displayName = `ForwardRef(${CMFContainer.displayName})`; + CMFWithRef.WrappedComponent = CMFContainer.WrappedComponent; + CMFWithRef.getState = CMFContainer.getState; + CMFWithRef.setStateAction = CMFContainer.setStateAction; } + const Connected = connect( (state, ownProps) => getStateToProps({ @@ -385,7 +378,7 @@ export default function cmfConnect({ ownProps, }), { ...rest }, - )(hoistStatics(CMFContainer, WrappedComponent)); + )(CMFWithRef); Connected.CMFContainer = CMFContainer; return Connected; }; diff --git a/packages/cmf/src/components/ErrorBoundary/ErrorBoundary.component.test.js b/packages/cmf/src/components/ErrorBoundary/ErrorBoundary.component.test.js index 2b356f41204..77149a96400 100644 --- a/packages/cmf/src/components/ErrorBoundary/ErrorBoundary.component.test.js +++ b/packages/cmf/src/components/ErrorBoundary/ErrorBoundary.component.test.js @@ -4,7 +4,6 @@ import { mount } from 'enzyme'; import ErrorBoundary from './ErrorBoundary.component'; // missing in jsdom: https://github.com/jsdom/jsdom/issues/1721 -global.window.URL.createObjectURL = jest.fn(); function TestChildren(props) { if (props.breaking) { @@ -17,6 +16,13 @@ TestChildren.propTypes = { }; describe('Component ErrorBoundary', () => { + beforeEach(() => { + global.window.URL.createObjectURL = jest.fn(); + global.console = { + log: jest.fn(), + error: jest.fn(), + }; + }); it('should render children', () => { const wrapper = mount( @@ -34,5 +40,6 @@ describe('Component ErrorBoundary', () => { ); expect(wrapper.text()).not.toEqual('hello world'); expect(wrapper.find('ErrorPanel').length).toBe(1); + expect(global.console.error).toHaveBeenCalled(); }); }); diff --git a/packages/cmf/src/index.js b/packages/cmf/src/index.js index 7dfffcfc0ce..b7021ea3939 100644 --- a/packages/cmf/src/index.js +++ b/packages/cmf/src/index.js @@ -23,6 +23,7 @@ import localStorage from './localStorage'; import onError from './onError'; import reduxStorage from './reduxstorage'; import * as mock from './mock'; +import { useCMFContext } from './useContext'; // DEPRECATED APIs import action from './action'; @@ -66,6 +67,7 @@ export { Saga, CmfRegisteredSaga, store, + useCMFContext, }; /** diff --git a/packages/cmf/src/mock/index.js b/packages/cmf/src/mock/index.js index 0749a3007f0..b9cc6507b9e 100644 --- a/packages/cmf/src/mock/index.js +++ b/packages/cmf/src/mock/index.js @@ -13,9 +13,4 @@ import store from './store'; import Provider from './provider'; -export default store; - -export { - store, - Provider, -}; +export { Provider, store }; diff --git a/packages/cmf/src/mock/provider.js b/packages/cmf/src/mock/provider.js index 268b37ca186..624f4c3038b 100644 --- a/packages/cmf/src/mock/provider.js +++ b/packages/cmf/src/mock/provider.js @@ -1,7 +1,30 @@ import PropTypes from 'prop-types'; import React from 'react'; +import { Provider } from 'react-redux'; +import { RegistryProvider } from '../RegistryProvider'; import mock from './store'; +class ErrorBoundary extends React.Component { + static propTypes = { + children: PropTypes.any, + onError: PropTypes.func, + }; + + componentDidCatch(error, errorInfo) { + if (this.props.onError) { + this.props.onError(error, errorInfo); + } + this.setState({ hasError: true }); + } + + render() { + if (this.state && this.state.hasError) { + return
Error
; + } + return this.props.children; + } +} + const store = mock.store(); /** * This component help you to mock the provider. @@ -25,26 +48,26 @@ describe('AppMenu', () => { }); }); */ -class MockProvider extends React.Component { - getChildContext() { - let st = this.props.store; - if (!st) { - st = store; - } - if (this.props.state) { - st.state = this.props.state; - st.getState = () => this.props.state; - } - const context = { - store: st, - registry: this.props.registry || {}, - }; - return context; +function MockProvider(props) { + let st = props.store; + if (!st) { + st = store; } - - render() { - return
{this.props.children}
; + if (props.state) { + st.state = props.state; + st.getState = () => props.state; } + const context = { + store: st, + registry: props.registry || {}, + }; + return ( +
+ + {props.children} + +
+ ); } MockProvider.propTypes = { @@ -54,9 +77,10 @@ MockProvider.propTypes = { registry: PropTypes.object, }; -MockProvider.childContextTypes = { - store: PropTypes.object, - registry: PropTypes.object, -}; +MockProvider.getEnzymeOption = context => ({ + wrappingComponent: MockProvider, + wrappingComponentProps: context, +}); +MockProvider.ErrorBoundary = ErrorBoundary; export default MockProvider; diff --git a/packages/cmf/src/onEvent.js b/packages/cmf/src/onEvent.js index dcfdcb260c1..9540e1c6c6b 100644 --- a/packages/cmf/src/onEvent.js +++ b/packages/cmf/src/onEvent.js @@ -9,14 +9,14 @@ function serializeEvent(event) { return event; } -function getOnEventActionCreatorHandler(instance, config, currentHandler) { +function getOnEventActionCreatorHandler(instance, props, config, currentHandler) { let actionCreator = config; if (typeof config === 'object') { actionCreator = config.id; } return function onEventActionCreator(...args) { - instance.dispatchActionCreator(actionCreator, serializeEvent(args[0]), { - props: instance.props, + props.dispatchActionCreator(actionCreator, serializeEvent(args[0]), { + props, ...args[1], ...(config.data || {}), }); @@ -26,7 +26,7 @@ function getOnEventActionCreatorHandler(instance, config, currentHandler) { }; } -function getOnEventDispatchHandler(instance, config, currentHandler) { +function getOnEventDispatchHandler(instance, props, config, currentHandler) { return function onEventDispatch(...args) { const payload = { event: serializeEvent(args[0]), @@ -40,7 +40,7 @@ function getOnEventDispatchHandler(instance, config, currentHandler) { }; } -function getOnEventSetStateHandler(instance, config, currentHandler) { +function getOnEventSetStateHandler(instance, props, config, currentHandler) { return function onEventSetState(...args) { if (typeof currentHandler === 'function') { currentHandler(...args); @@ -59,7 +59,9 @@ function getOnEventSetStateHandler(instance, config, currentHandler) { } } else if (value === 'toggle') { // because toggle need to read the state we dispatch it with a function - instance.props.setState(props => instance.props.setState({ [key]: !props.state.get(key) })); + instance.props.setState(_props => + instance.props.setState({ [key]: !_props.state.get(key) }), + ); } else { // eslint-disable-next-line no-param-reassign acc[key] = value; @@ -102,6 +104,7 @@ function addOnEventSupport(handlerType, instance, props, key) { // eslint-disable-next-line no-param-reassign props[handlerKey] = GET_HANDLER[handlerType]( instance, + props, instance.props[key], originalEventHandler, ); diff --git a/packages/cmf/src/sagas/collection.js b/packages/cmf/src/sagas/collection.js index 0abbe57de32..8b132944eea 100644 --- a/packages/cmf/src/sagas/collection.js +++ b/packages/cmf/src/sagas/collection.js @@ -1,5 +1,4 @@ -import { call, select } from 'redux-saga/effects'; -import { delay } from 'redux-saga'; +import { delay, select } from 'redux-saga/effects'; import selectors from '../selectors'; /** @@ -14,6 +13,6 @@ export function* waitFor(id, interval = 10) { if (collection !== undefined) { break; } - yield call(delay, interval); + yield delay(interval); } } diff --git a/packages/cmf/src/useContext.js b/packages/cmf/src/useContext.js new file mode 100644 index 00000000000..e79841cef9f --- /dev/null +++ b/packages/cmf/src/useContext.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { useStore } from 'react-redux'; +import { RegistryContext } from './RegistryProvider'; + +export function useCMFContext() { + const store = useStore(); + const registry = React.useContext(RegistryContext); + return { + store, + registry, + }; +} diff --git a/packages/containers/.storybook/config.js b/packages/containers/.storybook/config.js index 037aab8b81f..1a02e1f0f45 100644 --- a/packages/containers/.storybook/config.js +++ b/packages/containers/.storybook/config.js @@ -1,3 +1,4 @@ +import '@talend/bootstrap-theme/src/theme/theme.scss'; import React from 'react'; import { storiesOf, configure, addDecorator, addParameters } from '@storybook/react'; import { action } from '@storybook/addon-actions'; diff --git a/packages/containers/package.json b/packages/containers/package.json index 9010f2c8e41..ec208a992b0 100644 --- a/packages/containers/package.json +++ b/packages/containers/package.json @@ -44,7 +44,7 @@ "memoize-one": "^5.2.1", "react-bootstrap": "0.32.4", "react-immutable-proptypes": "^2.2.0", - "redux-saga": "^0.15.6", + "redux-saga": "^1.1.3", "reselect": "^2.5.4", "uuid": "^3.4.0" }, diff --git a/packages/containers/src/AboutDialog/AboutDialog.sagas.test.js b/packages/containers/src/AboutDialog/AboutDialog.sagas.test.js index 516d4c4b287..1a037c96af2 100644 --- a/packages/containers/src/AboutDialog/AboutDialog.sagas.test.js +++ b/packages/containers/src/AboutDialog/AboutDialog.sagas.test.js @@ -57,22 +57,22 @@ describe('AboutDialog sagas', () => { // Toggle loading flag let effect = gen.next().value; let expected = Connected.setStateAction({ loading: true }); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); // HTTP call effect = gen.next().value; - expect(effect.CALL.fn).toEqual(cmf.sagas.http.get); - expect(effect.CALL.args).toEqual([url]); + expect(effect.payload.fn).toEqual(cmf.sagas.http.get); + expect(effect.payload.args).toEqual([url]); // Update CMF collections effect = gen.next(httpResponse).value; expected = cmf.actions.collections.addOrReplace(Constants.COLLECTION_ID, converted); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); // Toggle fetching flag effect = gen.next().value; expected = Connected.setStateAction({ loading: false }); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); const { done } = gen.next(); @@ -85,7 +85,7 @@ describe('AboutDialog sagas', () => { const effect = gen.next().value; const expected = Connected.setStateAction({ show: false }); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); expect(gen.next().done).toBe(true); }); @@ -96,9 +96,9 @@ describe('AboutDialog sagas', () => { const effect = gen.next().value; const expected = Connected.setStateAction({ show: true }); - expect(effect.ALL[0].PUT.action).toEqual(expected); + expect(effect.payload[0].payload.action).toEqual(expected); - expect(effect.ALL[1].CALL.args).toEqual([{ payload: { url: 'hey' } }]); + expect(effect.payload[1].payload.args).toEqual([{ payload: { url: 'hey' } }]); expect(gen.next().done).toBe(true); }); diff --git a/packages/containers/src/Action/Action.test.js b/packages/containers/src/Action/Action.test.js index 9099ead3372..bc1a1b4e0df 100644 --- a/packages/containers/src/Action/Action.test.js +++ b/packages/containers/src/Action/Action.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { mock } from '@talend/react-cmf'; import Action, { mapStateToProps, mergeProps } from './Action.connect'; @@ -7,7 +7,10 @@ import Action, { mapStateToProps, mergeProps } from './Action.connect'; describe('Action', () => { it('should render from name props keeping extra props', () => { const context = mock.store.context(); - const wrapper = shallow(, { context }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); expect(wrapper.getElement()).toMatchSnapshot(); }); }); diff --git a/packages/containers/src/Action/__snapshots__/Action.test.js.snap b/packages/containers/src/Action/__snapshots__/Action.test.js.snap index 3e032b0eb62..c9a671592dc 100644 --- a/packages/containers/src/Action/__snapshots__/Action.test.js.snap +++ b/packages/containers/src/Action/__snapshots__/Action.test.js.snap @@ -1,26 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Action should render from name props keeping extra props 1`] = ` - `; diff --git a/packages/containers/src/ActionBar/ActionBar.test.js b/packages/containers/src/ActionBar/ActionBar.test.js index f818ea504f4..8b01120c50e 100644 --- a/packages/containers/src/ActionBar/ActionBar.test.js +++ b/packages/containers/src/ActionBar/ActionBar.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { mock } from '@talend/react-cmf'; import Container from './ActionBar.connect'; @@ -74,15 +74,20 @@ const actionIds = { }, ], }; - +const context = mock.store.context(); describe('Container ActionBar', () => { it('should pass the props', () => { + context.registry = {}; const props = { actions }; - const wrapper = shallow(, { context: mock.store.context() }); - expect(wrapper.props()).toMatchSnapshot(); + const wrapper = mount(, mock.Provider.getEnzymeOption(context)); + expect(wrapper.find(Container.CMFContainer).props()).toMatchSnapshot(); }); it('should compute props using CMF with array of string', () => { - const wrapper = shallow(, { context: mock.store.context() }); - expect(wrapper.props()).toMatchSnapshot(); + context.registry = {}; + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); + expect(wrapper.find(Container.CMFContainer).props()).toMatchSnapshot(); }); }); diff --git a/packages/containers/src/ActionBar/__snapshots__/ActionBar.test.js.snap b/packages/containers/src/ActionBar/__snapshots__/ActionBar.test.js.snap index 66fbe98b5c6..4814dc9f8fa 100644 --- a/packages/containers/src/ActionBar/__snapshots__/ActionBar.test.js.snap +++ b/packages/containers/src/ActionBar/__snapshots__/ActionBar.test.js.snap @@ -34,10 +34,50 @@ Object { "getComponent": [Function], "initState": [Function], "renderers": Object { - "Action": [Function], - "ActionDropdown": [Function], - "ActionSplitDropdown": [Function], - "Actions": [Function], + "Action": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "DISPLAY_MODE_DROPDOWN": "dropdown", + "DISPLAY_MODE_FILE": "file", + "DISPLAY_MODE_ICON_TOGGLE": "iconToggle", + "DISPLAY_MODE_SPLIT_DROPDOWN": "splitDropdown", + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Action))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, + "ActionDropdown": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Container(ActionDropdown)))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, + "ActionSplitDropdown": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Container(ActionSplitDropdown)))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, + "Actions": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Actions))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, }, "setState": [Function], "state": undefined, @@ -103,10 +143,50 @@ Object { "getComponent": [Function], "initState": [Function], "renderers": Object { - "Action": [Function], - "ActionDropdown": [Function], - "ActionSplitDropdown": [Function], - "Actions": [Function], + "Action": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "DISPLAY_MODE_DROPDOWN": "dropdown", + "DISPLAY_MODE_FILE": "file", + "DISPLAY_MODE_ICON_TOGGLE": "iconToggle", + "DISPLAY_MODE_SPLIT_DROPDOWN": "splitDropdown", + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Action))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, + "ActionDropdown": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Container(ActionDropdown)))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, + "ActionSplitDropdown": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Container(ActionSplitDropdown)))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, + "Actions": Object { + "$$typeof": Symbol(react.memo), + "CMFContainer": [Function], + "WrappedComponent": [Function], + "compare": null, + "displayName": "Connect(CMF(Actions))", + "getState": [Function], + "setStateAction": [Function], + "type": [Function], + }, }, "setState": [Function], "state": undefined, diff --git a/packages/containers/src/Actions/Actions.test.js b/packages/containers/src/Actions/Actions.test.js index 4bba17e0d1c..5580d5c5e9b 100644 --- a/packages/containers/src/Actions/Actions.test.js +++ b/packages/containers/src/Actions/Actions.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { mock } from '@talend/react-cmf'; import Actions from './Actions.connect'; @@ -7,7 +7,12 @@ import Actions from './Actions.connect'; describe('Actions', () => { it('should render', () => { const context = mock.store.context(); - const wrapper = shallow(, { context }); - expect(wrapper.getElement()).toMatchSnapshot(); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(context), + ); + expect(wrapper.find(Actions.CMFContainer).props().actions[0]).toEqual({ + actionId: 'menu:demo', + }); }); }); diff --git a/packages/containers/src/Actions/__snapshots__/Actions.test.js.snap b/packages/containers/src/Actions/__snapshots__/Actions.test.js.snap deleted file mode 100644 index 53e20db84a9..00000000000 --- a/packages/containers/src/Actions/__snapshots__/Actions.test.js.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Actions should render 1`] = ` - -`; diff --git a/packages/containers/src/AppLoader/AppLoader.saga.js b/packages/containers/src/AppLoader/AppLoader.saga.js index 449cb0d4482..3063f98bf64 100644 --- a/packages/containers/src/AppLoader/AppLoader.saga.js +++ b/packages/containers/src/AppLoader/AppLoader.saga.js @@ -1,6 +1,5 @@ import api from '@talend/react-cmf'; -import { call, select, all, take } from 'redux-saga/effects'; -import { delay } from 'redux-saga'; +import { delay, call, select, all, take } from 'redux-saga/effects'; import invariant from 'invariant'; export const ACTION_CREATORS = 'actionCreators'; @@ -20,7 +19,7 @@ export function* waitFor(collectionName, interval = 10) { if (collection !== undefined) { break; } - yield call(delay, interval); + yield delay(interval); } } /** @@ -52,6 +51,7 @@ export function* handleStep(step) { * @param {array} props.steps an array of steps to handle */ export function* appLoaderSaga({ steps }) { + // eslint-disable-next-line no-restricted-syntax for (const step of steps) { yield call(handleStep, step); } diff --git a/packages/containers/src/ComponentForm/ComponentForm.saga.test.js b/packages/containers/src/ComponentForm/ComponentForm.saga.test.js index 4a64e96ef96..7af55f47d89 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.saga.test.js +++ b/packages/containers/src/ComponentForm/ComponentForm.saga.test.js @@ -67,8 +67,9 @@ describe('ComponentForm saga', () => { // when const selectJsonSchema = gen.next().value; - expect(selectJsonSchema.SELECT).toBeDefined(); - const selector = selectJsonSchema.SELECT.selector; + expect(selectJsonSchema.payload).toBeDefined(); + expect(selectJsonSchema.type).toBe('SELECT'); + const selector = selectJsonSchema.payload.selector; const jsonSchemaSelection = selector({ cmf: { components: fromJS({ @@ -161,7 +162,7 @@ describe('ComponentForm saga', () => { gen.next(); // select // then - expect(gen.next().value.PUT.action.cmf.componentState.componentState).toEqual({ + expect(gen.next().value.payload.action.cmf.componentState.componentState).toEqual({ initialState: jsonSchema, ...jsonSchema, }); @@ -228,8 +229,9 @@ describe('ComponentForm saga', () => { // when gen.next(); // fetch step const errorStep = gen.next({ response }).value; - expect(errorStep.PUT).toBeDefined(); - const setStateAction = errorStep.PUT.action(null, getReduxStore); + expect(errorStep.payload).toBeDefined(); + expect(errorStep.type).toBe('PUT'); + const setStateAction = errorStep.payload.action(null, getReduxStore); // then expect(setStateAction).toEqual({ @@ -265,7 +267,7 @@ describe('ComponentForm saga', () => { const nextStep = gen.next({ response, data }).value; // then - expect(nextStep.PUT.action).toEqual({ + expect(nextStep.payload.action).toEqual({ cmf: { componentState: { componentName: 'ComponentForm', @@ -300,7 +302,7 @@ describe('ComponentForm saga', () => { const nextStep = gen.next({ response, data }).value; // then - expect(nextStep.PUT.action).toEqual({ + expect(nextStep.payload.action).toEqual({ cmf: { componentState: { componentName: 'ComponentForm', @@ -329,7 +331,7 @@ describe('ComponentForm saga', () => { const nextStep = gen.next({ response, data }).value; // then - expect(nextStep.PUT.action).toEqual({ + expect(nextStep.payload.action).toEqual({ cmf: { componentState: { componentName: 'ComponentForm', diff --git a/packages/containers/src/ConfirmDialog/ConfirmDialog.container.js b/packages/containers/src/ConfirmDialog/ConfirmDialog.container.js index 74aa5a22cec..1b69ff8cee4 100644 --- a/packages/containers/src/ConfirmDialog/ConfirmDialog.container.js +++ b/packages/containers/src/ConfirmDialog/ConfirmDialog.container.js @@ -1,9 +1,8 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Map } from 'immutable'; import omit from 'lodash/omit'; import Component from '@talend/react-components/lib/ConfirmDialog'; -import { cmfConnect } from '@talend/react-cmf'; +import { cmfConnect, useCMFContext } from '@talend/react-cmf'; import { getActionsProps } from '../actionAPI'; @@ -11,42 +10,35 @@ export const DEFAULT_STATE = new Map({ show: false, }); -// This uses old react context, so no way to switch to stateless function instead of class // eslint-disable-next-line react/prefer-stateless-function -class ConfirmDialog extends React.Component { - static displayName = 'CMFContainer(ConfirmDialog)'; - - static propTypes = { - ...cmfConnect.propTypes, - }; - - static contextTypes = { - store: PropTypes.object, - registry: PropTypes.object, - }; - - render() { - const state = (this.props.state || DEFAULT_STATE).toJS(); - if (!state.validateAction || !state.cancelAction) { - return null; - } - // this should be enough - /* const props = Object.assign( - {}, - state, - omit(this.props, cmfConnect.INJECTED_PROPS), - ); */ - // but as we don't have access to dispatch in the created context of mapStateToProps - // we're having an issue on the setup of the onClick on button - // for now we'll recompute them here where the context has dispatch - // so the connect is only here to force the refresh for now - - state.validateAction = getActionsProps(this.context, state.validateAction, state.model); - state.cancelAction = getActionsProps(this.context, state.cancelAction, state.model); - const props = { ...omit(this.props, cmfConnect.INJECTED_PROPS), ...state }; - - return ; +function ConfirmDialog(props) { + const context = useCMFContext(); + const state = (props.state || DEFAULT_STATE).toJS(); + if (!state.validateAction || !state.cancelAction) { + return null; } + // this should be enough + /* const props = Object.assign( + {}, + state, + omit(props, cmfConnect.INJECTED_PROPS), + ); */ + // but as we don't have access to dispatch in the created context of mapStateToProps + // we're having an issue on the setup of the onClick on button + // for now we'll recompute them here where the context has dispatch + // so the connect is only here to force the refresh for now + + state.validateAction = getActionsProps(context, state.validateAction, state.model); + state.cancelAction = getActionsProps(context, state.cancelAction, state.model); + const newProps = { ...omit(props, cmfConnect.INJECTED_PROPS), ...state }; + + return ; } +ConfirmDialog.displayName = 'CMFContainer(ConfirmDialog)'; + +ConfirmDialog.propTypes = { + ...cmfConnect.propTypes, +}; + export default ConfirmDialog; diff --git a/packages/containers/src/ConfirmDialog/ConfirmDialog.test.js b/packages/containers/src/ConfirmDialog/ConfirmDialog.test.js index cd8cadf904e..bd5e7545666 100644 --- a/packages/containers/src/ConfirmDialog/ConfirmDialog.test.js +++ b/packages/containers/src/ConfirmDialog/ConfirmDialog.test.js @@ -1,7 +1,7 @@ import React from 'react'; -import renderer from 'react-test-renderer'; import { fromJS, Map } from 'immutable'; import { mock } from '@talend/react-cmf'; +import { mount } from 'enzyme'; import Container from './ConfirmDialog.container'; import Connected, { mapStateToProps } from './ConfirmDialog.connect'; @@ -20,8 +20,11 @@ describe('Container ConfirmDialog', () => { show: true, children: 'Confirm this !', }); - const instance = new Container({ state }); - expect(instance.render()).toBe(null); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); + expect(wrapper.instance()).toBe(null); }); it('should render', () => { const state = new Map({ @@ -33,15 +36,11 @@ describe('Container ConfirmDialog', () => { cancelAction: 'menu:demo', model: { foo: 'bar' }, }); - const { Provider } = mock; - const wrapper = renderer - .create( - - , - , - ) - .toJSON(); - expect(wrapper).toMatchSnapshot(); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); + expect(wrapper.html()).toMatchSnapshot(); }); }); diff --git a/packages/containers/src/ConfirmDialog/__snapshots__/ConfirmDialog.test.js.snap b/packages/containers/src/ConfirmDialog/__snapshots__/ConfirmDialog.test.js.snap index 876918af109..41d0a462392 100644 --- a/packages/containers/src/ConfirmDialog/__snapshots__/ConfirmDialog.test.js.snap +++ b/packages/containers/src/ConfirmDialog/__snapshots__/ConfirmDialog.test.js.snap @@ -1,50 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Container ConfirmDialog should render 1`] = ` -
-
- Confirm this ! -
- , + Confirm this !
`; diff --git a/packages/containers/src/DeleteResource/sagas.test.js b/packages/containers/src/DeleteResource/sagas.test.js index 689e44428da..0e988b34d9d 100644 --- a/packages/containers/src/DeleteResource/sagas.test.js +++ b/packages/containers/src/DeleteResource/sagas.test.js @@ -23,7 +23,7 @@ describe('internals', () => { it('should put a redirect action', () => { const gen = internals.redirect('/foo'); const effect = gen.next().value; - expect(effect.PUT.action.cmf.routerReplace).toBe('/foo'); + expect(effect.payload.action.cmf.routerReplace).toBe('/foo'); }); it('should throw if no redirectUrl provided', () => { const gen = internals.redirect(); @@ -36,8 +36,9 @@ describe('internals', () => { let effect = gen.next().value; expect(effect).toEqual(take(CONSTANTS.DIALOG_BOX_DELETE_RESOURCE_OK)); effect = gen.next().value; - expect(effect.SELECT.args[0]).not.toBeDefined(); // resourceLocator - expect(effect.SELECT.args[1]).not.toBeDefined(); // safeId + expect(effect.type).toBe('SELECT'); + expect(effect.payload.args[0]).not.toBeDefined(); // resourceLocator + expect(effect.payload.args[1]).not.toBeDefined(); // safeId effect = gen.next().value; expect(effect).not.toBeDefined(); // no http delete called }); @@ -59,11 +60,11 @@ describe('internals', () => { let effect = gen.next().value; expect(effect).toEqual(take(CONSTANTS.DIALOG_BOX_DELETE_RESOURCE_OK)); effect = gen.next(action).value; - expect(effect.SELECT.args[0]).toBe('datasets'); // resourceLocator - expect(effect.SELECT.args[1]).toBe('123'); // safeId + expect(effect.payload.args[0]).toBe('datasets'); // resourceLocator + expect(effect.payload.args[1]).toBe('123'); // safeId effect = gen.next(resource).value; - expect(effect.CALL).toBeDefined(); - const httpAction = effect.CALL; + expect(effect.payload).toBeDefined(); + const httpAction = effect.payload; expect(httpAction.fn).toBe(cmf.sagas.http.delete); expect(httpAction.args[0]).toBe('/api/datasets/123'); effect = gen.next({ response: { ok: true } }).value; @@ -81,8 +82,8 @@ describe('internals', () => { ); effect = gen.next().value; - expect(effect.CALL.fn).toBe(internals.redirect); - expect(effect.CALL.args[0]).toBe('/resources'); + expect(effect.payload.fn).toBe(internals.redirect); + expect(effect.payload.args[0]).toBe('/resources'); }); it('should use resourceUri as backend api to delete resource if provided', () => { const action = { @@ -102,8 +103,8 @@ describe('internals', () => { gen.next(); gen.next(action); const effect = gen.next(resource).value; - expect(effect.CALL).toBeDefined(); - const httpAction = effect.CALL; + expect(effect.payload).toBeDefined(); + const httpAction = effect.payload; expect(httpAction.fn).toBe(cmf.sagas.http.delete); expect(httpAction.args[0]).toBe('/run-profiles/advanced/profileId'); }); @@ -131,8 +132,8 @@ describe('internals', () => { gen.next(); gen.next(action); const effect = gen.next(resource).value; - expect(effect.CALL).toBeDefined(); - const httpAction = effect.CALL; + expect(effect.payload).toBeDefined(); + const httpAction = effect.payload; expect(httpAction.fn).toBe(cmf.sagas.http.delete); expect(httpAction.args[0]).toBe('/services/run-profiles/runProfileId'); }, @@ -151,8 +152,8 @@ describe('internals', () => { const gen = internals.deleteResourceValidate(); gen.next(); const effect = gen.next(action).value; - expect(effect.SELECT.args[0]).toBe('myCollection'); - expect(effect.SELECT.args[1]).toBe('runProfileId'); + expect(effect.payload.args[0]).toBe('myCollection'); + expect(effect.payload.args[1]).toBe('runProfileId'); }); it('should use resourceType as collection to remove resource in state, if no collectionId provided', () => { const action = { @@ -167,8 +168,8 @@ describe('internals', () => { const gen = internals.deleteResourceValidate(); gen.next(); const effect = gen.next(action).value; - expect(effect.SELECT.args[0]).toBe('myResource'); - expect(effect.SELECT.args[1]).toBe('runProfileId'); + expect(effect.payload.args[0]).toBe('myResource'); + expect(effect.payload.args[1]).toBe('runProfileId'); }); it('should dispatch DIALOG_BOX_DELETE_RESOURCE_ERROR event when delete request fails', () => { const action = { @@ -208,11 +209,12 @@ describe('internals', () => { it('should call redirect ', () => { const gen = internals.deleteResourceCancel(); let effect = gen.next().value; - expect(effect.TAKE.pattern).toBe(CONSTANTS.DIALOG_BOX_DELETE_RESOURCE_CANCEL); + expect(effect.type).toBe('TAKE'); + expect(effect.payload.pattern).toBe(CONSTANTS.DIALOG_BOX_DELETE_RESOURCE_CANCEL); const action = { data: { model: { onCancelRedirectUrl: '/cancel' } } }; effect = gen.next(action).value; - expect(effect.CALL.fn).toBe(internals.redirect); - expect(effect.CALL.args[0]).toBe('/cancel'); + expect(effect.payload.fn).toBe(internals.redirect); + expect(effect.payload.args[0]).toBe('/cancel'); }); }); }); @@ -227,9 +229,13 @@ describe('sagas', () => { // eslint-disable-next-line new-cap const gen = sagas['DeleteResource#handle'](); const effect = gen.next().value; - expect(effect.RACE).toMatchObject({ - deleteConfirmationCancel: { CALL: { fn: internals.deleteResourceCancel } }, - deleteConfirmationValidate: { CALL: { fn: internals.deleteResourceValidate } }, + expect(effect.type).toBe('RACE'); + expect(effect.payload).toMatchObject({ + deleteConfirmationCancel: { type: 'CALL', payload: { fn: internals.deleteResourceCancel } }, + deleteConfirmationValidate: { + type: 'CALL', + payload: { fn: internals.deleteResourceValidate }, + }, }); }); it('should throw a specific error if sth goes bad', () => { diff --git a/packages/containers/src/GuidedTour/GuidedTour.sagas.test.js b/packages/containers/src/GuidedTour/GuidedTour.sagas.test.js index be2b147d1b8..68d4485faff 100644 --- a/packages/containers/src/GuidedTour/GuidedTour.sagas.test.js +++ b/packages/containers/src/GuidedTour/GuidedTour.sagas.test.js @@ -7,8 +7,7 @@ describe('Guided Tour sagas', () => { const effect = gen.next().value; const expected = Connected.setStateAction({ show: true }); - expect(effect.PUT.action).toEqual(expected); - + expect(effect.payload.action).toEqual(expected); expect(gen.next().done).toBe(true); }); @@ -17,7 +16,7 @@ describe('Guided Tour sagas', () => { const effect = gen.next().value; const expected = Connected.setStateAction({ show: false }); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); expect(gen.next().done).toBe(true); }); diff --git a/packages/containers/src/HeaderBar/HeaderBar.sagas.test.js b/packages/containers/src/HeaderBar/HeaderBar.sagas.test.js index a4786264b2a..a19216c9f01 100644 --- a/packages/containers/src/HeaderBar/HeaderBar.sagas.test.js +++ b/packages/containers/src/HeaderBar/HeaderBar.sagas.test.js @@ -19,22 +19,22 @@ describe('HeaderBar sagas', () => { // Toggle fetching flag (enable) let effect = gen.next().value; let expected = Connected.setStateAction({ productsFetchState: Constants.FETCHING_PRODUCTS }); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); // HTTP call effect = gen.next().value; - expect(effect.CALL.fn).toEqual(cmf.sagas.http.get); - expect(effect.CALL.args).toEqual([url]); + expect(effect.payload.fn).toEqual(cmf.sagas.http.get); + expect(effect.payload.args).toEqual([url]); // Toggle fetching flag (enable) effect = gen.next(httpResponse).value; expected = Connected.setStateAction({ productsFetchState: Constants.FETCH_PRODUCTS_SUCCESS }); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); // Update CMF collections effect = gen.next().value; expected = cmf.actions.collections.addOrReplace(Constants.COLLECTION_ID, data); - expect(effect.PUT.action).toEqual(expected); + expect(effect.payload.action).toEqual(expected); const { done } = gen.next(); @@ -48,18 +48,18 @@ describe('HeaderBar sagas', () => { // Toggle fetching flag (enable) let effect = gen.next().value; - expect(effect.PUT.action).toEqual( + expect(effect.payload.action).toEqual( Connected.setStateAction({ productsFetchState: Constants.FETCHING_PRODUCTS }), ); // HTTP call effect = gen.next().value; - expect(effect.CALL.fn).toEqual(cmf.sagas.http.get); - expect(effect.CALL.args).toEqual([url]); + expect(effect.payload.fn).toEqual(cmf.sagas.http.get); + expect(effect.payload.args).toEqual([url]); // Toggle fetching flag (enable) effect = gen.next(httpResponse).value; - expect(effect.PUT.action).toEqual( + expect(effect.payload.action).toEqual( Connected.setStateAction({ productsFetchState: Constants.FETCH_PRODUCTS_ERROR }), ); diff --git a/packages/containers/src/HomeListView/__snapshots__/HomeListView.test.js.snap b/packages/containers/src/HomeListView/__snapshots__/HomeListView.test.js.snap index 336bfb59cd7..8b9a655b4de 100644 --- a/packages/containers/src/HomeListView/__snapshots__/HomeListView.test.js.snap +++ b/packages/containers/src/HomeListView/__snapshots__/HomeListView.test.js.snap @@ -107,7 +107,7 @@ exports[`Component HomeListView should render with object props 1`] = ` } mode="TwoColumns" one={ - } > - -1) { - this.props.setState({ + props.setState({ selectedItems: selectedItems.splice(dataIndex, 1), }); } else { - this.props.setState({ - selectedItems: selectedItems.push(data[this.props.idKey]), + props.setState({ + selectedItems: selectedItems.push(data[props.idKey]), }); } } - onToggleAllMultiSelection() { - const selectedItems = this.getSelectedItems(); - const items = this.props.items; + function onToggleAllMultiSelection() { + const selectedItems = getSelectedItems(); + const items = props.items; if (selectedItems.size !== items.size) { - this.props.setState({ - selectedItems: items.map(item => item.get(this.props.idKey)), + props.setState({ + selectedItems: items.map(item => item.get(props.idKey)), }); } else { - this.props.setState({ + props.setState({ selectedItems: new ImmutableList([]), }); } } - getSelectedItems() { - return this.props.state.get('selectedItems', new ImmutableList()); - } - - getGenericDispatcher(property) { + function getGenericDispatcher(property) { return (event, data) => { - this.props.dispatchActionCreator(property, event, data, this.context); + props.dispatchActionCreator(property, event, data, context); }; } - isSelected(item) { - const selectedItems = this.getSelectedItems(); - return selectedItems.some(itemKey => itemKey === item[this.props.idKey]); + function isSelected(item) { + const selectedItems = getSelectedItems(); + return selectedItems.some(itemKey => itemKey === item[props.idKey]); } - render() { - const state = this.props.state.toJS(); - const items = getItems(this.context, this.props); - const props = { ...omit(this.props, cmfConnect.INJECTED_PROPS) }; - if (!props.displayMode) { - props.displayMode = state.displayMode; - } - if (!props.list) { - props.list = {}; - } - if (!props.list.id) { - props.list.id = 'list'; + const items = getItems(context, props); + const newProps = { ...omit(props, cmfConnect.INJECTED_PROPS) }; + if (!newProps.displayMode) { + newProps.displayMode = state.displayMode; + } + if (!newProps.list) { + newProps.list = {}; + } + if (!newProps.list.id) { + newProps.list.id = 'list'; + } + newProps.list.items = items; + if (!newProps.list.columns) { + newProps.list.columns = []; + } + newProps.list.sort = { + field: state.sortOn, + isDescending: !state.sortAsc, + onChange: (event, data) => { + props.dispatch({ + type: Constants.LIST_CHANGE_SORT_ORDER, + payload: data, + collectionId: props.collectionId, + event, + }); + }, + }; + if (!newProps.list.itemProps) { + newProps.list.itemProps = {}; + } + + if (newProps.rowHeight) { + newProps.rowHeight = newProps.rowHeight[newProps.displayMode]; + } + if (newProps.list.titleProps && newProps.actions.title) { + if (newProps.actions.title) { + newProps.list.titleProps.onClick = getGenericDispatcher(newProps.actions.title); } - props.list.items = items; - if (!props.list.columns) { - props.list.columns = []; + if (newProps.actions.editSubmit) { + newProps.list.titleProps.onEditSubmit = getGenericDispatcher(newProps.actions.editSubmit); } - props.list.sort = { - field: state.sortOn, - isDescending: !state.sortAsc, - onChange: (event, data) => { - this.props.dispatch({ - type: Constants.LIST_CHANGE_SORT_ORDER, - payload: data, - collectionId: props.collectionId, - event, - }); - }, - }; - if (!props.list.itemProps) { - props.list.itemProps = {}; + if (newProps.actions.editCancel) { + newProps.list.titleProps.onEditCancel = getGenericDispatcher(newProps.actions.editCancel); } + } - if (this.props.rowHeight) { - props.rowHeight = this.props.rowHeight[props.displayMode]; - } - if (props.list.titleProps && this.props.actions.title) { - if (this.props.actions.title) { - props.list.titleProps.onClick = this.getGenericDispatcher(this.props.actions.title); - } - if (this.props.actions.editSubmit) { - props.list.titleProps.onEditSubmit = this.getGenericDispatcher( - this.props.actions.editSubmit, - ); - } - if (this.props.actions.editCancel) { - props.list.titleProps.onEditCancel = this.getGenericDispatcher( - this.props.actions.editCancel, - ); - } - } + const cellDictionary = { ...connectedCellDictionary }; + if (newProps.cellDictionary) { + Object.keys(newProps.cellDictionary).reduce((accumulator, key) => { + const current = newProps.cellDictionary[key]; + // eslint-disable-next-line no-param-reassign + accumulator[key] = { + ...omit(current, ['component']), + cellRenderer: props.getComponent(current.component), + }; + return accumulator; + }, cellDictionary); + } + newProps.list.cellDictionary = cellDictionary; - const cellDictionary = { ...connectedCellDictionary }; - if (props.cellDictionary) { - Object.keys(props.cellDictionary).reduce((accumulator, key) => { - const current = props.cellDictionary[key]; + if (newProps.headerDictionary) { + newProps.list.headerDictionary = Object.keys(newProps.headerDictionary).reduce( + (accumulator, key) => { + const current = newProps.headerDictionary[key]; // eslint-disable-next-line no-param-reassign accumulator[key] = { ...omit(current, ['component']), - cellRenderer: props.getComponent(current.component), + headerRenderer: props.getComponent(current.component), }; return accumulator; - }, cellDictionary); - } - props.list.cellDictionary = cellDictionary; + }, + {}, + ); + } - if (props.headerDictionary) { - props.list.headerDictionary = Object.keys(props.headerDictionary).reduce( - (accumulator, key) => { - const current = props.headerDictionary[key]; - // eslint-disable-next-line no-param-reassign - accumulator[key] = { - ...omit(current, ['component']), - headerRenderer: props.getComponent(current.component), - }; - return accumulator; + // toolbar + if (newProps.toolbar) { + if (newProps.toolbar.display) { + newProps.toolbar.display = { + ...newProps.toolbar.display, + onChange: (event, data) => { + onSelectDisplayMode(event, data); }, - {}, - ); + }; + } + if (newProps.toolbar.sort) { + newProps.toolbar.sort.isDescending = !state.sortAsc; + newProps.toolbar.sort.field = state.sortOn; + newProps.toolbar.sort.onChange = (event, data) => { + props.dispatch({ + type: Constants.LIST_CHANGE_SORT_ORDER, + payload: data, + collectionId: props.collectionId, + event, + }); + }; } - // toolbar - if (props.toolbar) { - if (props.toolbar.display) { - props.toolbar.display = { - ...props.toolbar.display, - onChange: (event, data) => { - this.onSelectDisplayMode(event, data); - }, - }; - } - if (props.toolbar.sort) { - props.toolbar.sort.isDescending = !state.sortAsc; - props.toolbar.sort.field = state.sortOn; - props.toolbar.sort.onChange = (event, data) => { - this.props.dispatch({ - type: Constants.LIST_CHANGE_SORT_ORDER, - payload: data, - collectionId: props.collectionId, - event, - }); - }; - } - - if (props.toolbar.filter) { - props.toolbar.filter.onToggle = (event, data) => { - this.props.dispatch({ - type: Constants.LIST_TOGGLE_FILTER, - payload: { ...data, filterDocked: state.filterDocked, searchQuery: state.searchQuery }, - collectionId: props.collectionId, - event, - }); - }; - props.toolbar.filter.onFilter = (event, data) => { - this.props.dispatch({ - type: Constants.LIST_FILTER_CHANGE, - payload: data, - collectionId: props.collectionId, - event, - }); - }; - props.toolbar.filter.docked = state.filterDocked; - props.toolbar.filter.value = state.searchQuery; - } + if (newProps.toolbar.filter) { + newProps.toolbar.filter.onToggle = (event, data) => { + props.dispatch({ + type: Constants.LIST_TOGGLE_FILTER, + payload: { ...data, filterDocked: state.filterDocked, searchQuery: state.searchQuery }, + collectionId: props.collectionId, + event, + }); + }; + newProps.toolbar.filter.onFilter = (event, data) => { + props.dispatch({ + type: Constants.LIST_FILTER_CHANGE, + payload: data, + collectionId: props.collectionId, + event, + }); + }; + newProps.toolbar.filter.docked = state.filterDocked; + newProps.toolbar.filter.value = state.searchQuery; + } - props.toolbar.actionBar = { actions: {}, multiSelectActions: {} }; + newProps.toolbar.actionBar = { actions: {}, multiSelectActions: {} }; - // settings up multi selection - if (props.multiSelectActions && props.idKey) { - props.list.itemProps.onToggle = this.onToggleMultiSelection; - props.list.itemProps.onToggleAll = this.onToggleAllMultiSelection; - props.list.itemProps.isSelected = this.isSelected; - props.toolbar.actionBar.selected = this.getSelectedItems().size; - } + // settings up multi selection + if (newProps.multiSelectActions && props.idKey) { + newProps.list.itemProps.onToggle = onToggleMultiSelection; + newProps.list.itemProps.onToggleAll = onToggleAllMultiSelection; + newProps.list.itemProps.isSelected = isSelected; + newProps.toolbar.actionBar.selected = getSelectedItems().size; + } - const actions = this.props.actions; - const multiSelectActions = this.props.multiSelectActions; - if (multiSelectActions) { - if (multiSelectActions.left) { - props.toolbar.actionBar.multiSelectActions.left = multiSelectActions.left.map(action => ({ + const actions = newProps.actions; + const multiSelectActions = newProps.multiSelectActions; + if (multiSelectActions) { + if (multiSelectActions.left) { + newProps.toolbar.actionBar.multiSelectActions.left = multiSelectActions.left.map( + action => ({ actionId: action, - })); - } - if (multiSelectActions.right) { - props.toolbar.actionBar.multiSelectActions.right = multiSelectActions.right.map( - action => ({ - actionId: action, - }), - ); - } + }), + ); } - if (actions) { - if (actions.left) { - props.toolbar.actionBar.actions.left = actions.left.map(action => ({ actionId: action })); - } - if (actions.right) { - props.toolbar.actionBar.actions.right = actions.right.map(action => ({ + if (multiSelectActions.right) { + newProps.toolbar.actionBar.multiSelectActions.right = multiSelectActions.right.map( + action => ({ actionId: action, - })); - } + }), + ); + } + } + if (actions) { + if (actions.left) { + newProps.toolbar.actionBar.actions.left = actions.left.map(action => ({ + actionId: action, + })); } + if (actions.right) { + newProps.toolbar.actionBar.actions.right = actions.right.map(action => ({ + actionId: action, + })); + } + } - if (props.toolbar.pagination) { - const pagination = props.toolbar.pagination; - Object.assign(props.toolbar.pagination, { - ...pick(state, ['totalResults', 'itemsPerPage', 'startIndex']), - }); - if (!pagination.onChange) { - pagination.onChange = (startIndex, itemsPerPage) => { - this.onChangePage(startIndex, itemsPerPage); - }; - } else if (typeof pagination.onChange === 'string') { - const onChangeActionCreator = pagination.onChange; - pagination.onChange = (startIndex, itemsPerPage) => { - this.props.dispatchActionCreator( - onChangeActionCreator, - null, - { startIndex, itemsPerPage }, - this.context, - ); - }; - } + if (newProps.toolbar.pagination) { + const pagination = newProps.toolbar.pagination; + Object.assign(newProps.toolbar.pagination, { + ...pick(state, ['totalResults', 'itemsPerPage', 'startIndex']), + }); + if (!pagination.onChange) { + pagination.onChange = (startIndex, itemsPerPage) => { + onChangePage(startIndex, itemsPerPage); + }; + } else if (typeof pagination.onChange === 'string') { + const onChangeActionCreator = pagination.onChange; + pagination.onChange = (startIndex, itemsPerPage) => { + props.dispatchActionCreator( + onChangeActionCreator, + null, + { startIndex, itemsPerPage }, + context, + ); + }; } } - return ; } + return ; } +List.displayName = 'Container(List)'; +List.propTypes = { + actions: PropTypes.shape({ + title: PropTypes.string, + left: PropTypes.arrayOf(PropTypes.string), + right: PropTypes.arrayOf(PropTypes.string), + }), + multiSelectActions: PropTypes.shape({ + title: PropTypes.string, + left: PropTypes.arrayOf(PropTypes.string), + right: PropTypes.arrayOf(PropTypes.string), + }), + idKey: PropTypes.string, + list: PropTypes.shape({ + columns: PropTypes.array, + titleProps: PropTypes.object, + }), + toolbar: PropTypes.shape({ + sort: PropTypes.object, + filter: PropTypes.object, + pagination: PropTypes.shape({ + onChange: PropTypes.func, + }), + }), + cellDictionary: PropTypes.object, + displayMode: PropTypes.string, + items: ImmutablePropTypes.list.isRequired, + state: cmfConnect.propTypes.state, + ...cmfConnect.propTypes, +}; + +List.defaultProps = { + state: DEFAULT_STATE, +}; + export default List; diff --git a/packages/containers/src/List/List.sagas.test.js b/packages/containers/src/List/List.sagas.test.js index 21437d150be..4aa9a5fb8cf 100644 --- a/packages/containers/src/List/List.sagas.test.js +++ b/packages/containers/src/List/List.sagas.test.js @@ -1,8 +1,8 @@ import { put } from 'redux-saga/effects'; import { fromJS } from 'immutable'; +import { mock } from '@talend/react-cmf'; import { onToggleFilter, onFilterChange, onChangeSortChange } from './List.sagas'; import Connected from './List.connect'; -import mock, { store } from '../../../cmf/lib/mock'; const localConfig = { collectionId: 'default', @@ -19,19 +19,25 @@ const localConfig = { }, ]), list: { - columns: [{ key: 'id', name: 'ID' }, { key: 'value', name: 'Value' }], + columns: [ + { key: 'id', name: 'ID' }, + { key: 'value', name: 'Value' }, + ], }, }; -const state = store.state(); +const state = mock.store.state(); state.cmf.collections = fromJS({ default: { - columns: [{ key: 'id', name: 'ID' }, { key: 'value', name: 'Value' }], + columns: [ + { key: 'id', name: 'ID' }, + { key: 'value', name: 'Value' }, + ], items: localConfig.items, }, }); -const context = mock.context(); +const context = mock.store.context(); const event = { type: 'click' }; const data = { diff --git a/packages/containers/src/List/List.test.js b/packages/containers/src/List/List.test.js index 71aa4922ed0..57a70f0cf1c 100644 --- a/packages/containers/src/List/List.test.js +++ b/packages/containers/src/List/List.test.js @@ -1,8 +1,8 @@ -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import React from 'react'; import { Map, fromJS, List as ImmutableList } from 'immutable'; import cloneDeep from 'lodash/cloneDeep'; - +import { mock } from '@talend/react-cmf'; import Container, { DEFAULT_STATE } from './List.container'; import Connected, { mapStateToProps } from './List.connect'; @@ -24,7 +24,10 @@ const toolbar = { placeholder: 'find an object', }, sort: { - options: [{ id: 'id', name: 'Id' }, { id: 'name', name: 'Name' }], + options: [ + { id: 'id', name: 'Id' }, + { id: 'name', name: 'Name' }, + ], field: 'id', isDescending: false, }, @@ -85,8 +88,11 @@ const items = fromJS([ describe('Container List', () => { it('should put default props', () => { - const wrapper = shallow(); - const props = wrapper.props(); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context), + ); + const props = wrapper.find('List').props(); expect(props.displayMode).toBe('table'); expect(props.list.items.length).toBe(3); expect(props.list.items[0].id).toBe(1); @@ -107,14 +113,15 @@ describe('Container List', () => { it('should define the cellDictionary props', () => { const getComponent = jest.fn(() => 'my custom component'); - const wrapper = shallow( + const wrapper = mount( , + mock.Provider.getEnzymeOption(mock.store.context), ); - const props = wrapper.props(); + const props = wrapper.find('List').props(); expect(props.list.cellDictionary).toEqual({ custom: { cellRenderer: 'my custom component' }, @@ -129,14 +136,15 @@ describe('Container List', () => { it('should define the headerDictionary props', () => { const getComponent = jest.fn(() => 'my custom component'); - const wrapper = shallow( + const wrapper = mount( , + mock.Provider.getEnzymeOption(mock.store.context), ); - const props = wrapper.props(); + const props = wrapper.find('List').props(); expect(props.list.headerDictionary).toEqual({ custom: { headerRenderer: 'my custom component' }, @@ -150,7 +158,10 @@ describe('Container List', () => { multiSelectionSetting.multiSelectActions = { left: ['object:remove'], }; - const wrapper = shallow(); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); const props = wrapper.props(); expect(typeof props.list.itemProps.onToggle).toBe('function'); expect(typeof props.list.itemProps.onToggleAll).toBe('function'); @@ -158,13 +169,20 @@ describe('Container List', () => { }); it('should render without toolbar', () => { - const wrapper = shallow(, { lifecycleExperimental: true }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + mock.Provider.getEnzymeOption(mock.store.context()), + ); const props = wrapper.props(); expect(props.toolbar).toBe(undefined); }); it('should support displayMode as props', () => { - const wrapper = shallow(); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); const props = wrapper.props(); expect(props.displayMode).toBe('large'); }); @@ -177,16 +195,13 @@ describe('Container List', () => { 'actionCreator:object:open': actionCreator, }, }; - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - context, - }, + mock.Provider.getEnzymeOption(context), ); const props = wrapper.props(); const onClick = props.list.titleProps.onClick; @@ -210,16 +225,13 @@ describe('Container List', () => { 'actionCreator:object:edit:submit': actionCreator, }, }; - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - context, - }, + mock.Provider.getEnzymeOption(context), ); const props = wrapper.props(); const onEditSubmit = props.list.titleProps.onEditSubmit; @@ -243,16 +255,13 @@ describe('Container List', () => { 'actionCreator:object:edit:cancel': actionCreator, }, }; - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - context, - }, + mock.Provider.getEnzymeOption(context), ); const props = wrapper.props(); const onEditCancel = props.list.titleProps.onEditCancel; @@ -280,16 +289,13 @@ describe('Container List', () => { ...cloneDeep(settings), actions: {}, }; - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - context, - }, + mock.Provider.getEnzymeOption(context), ); const props = wrapper.props(); expect(props.list.titleProps.onClick).toBeUndefined(); @@ -305,17 +311,14 @@ describe('Container List', () => { 'actionCreator:pagination:change': actionCreator, }, }; - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - context, - }, + mock.Provider.getEnzymeOption(context), ); const props = wrapper.props(); const event = null; @@ -327,7 +330,12 @@ describe('Container List', () => { props.toolbar.pagination.onChange(data.startIndex, data.itemsPerPage); // then - expect(dispatchActionCreator).toBeCalledWith('pagination:change', event, data, context); + expect(dispatchActionCreator).toBeCalledWith( + 'pagination:change', + event, + data, + expect.objectContaining(context), + ); }); it('should set the proper rowHeight', () => { @@ -335,11 +343,11 @@ describe('Container List', () => { table: 3, large: 2, }; - const wrapper = shallow( + const wrapper = mount( , - { lifecycleExperimental: true }, + mock.Provider.getEnzymeOption(mock.store.context()), ); - const props = wrapper.props(); + const props = wrapper.find('List').props(); expect(props.displayMode).toBe('table'); expect(props.rowHeight).toBe(3); }); @@ -348,11 +356,9 @@ describe('Container List', () => { // given const dispatch = jest.fn(); const setState = jest.fn(); - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - }, + mock.Provider.getEnzymeOption(mock.store.context()), ); const props = wrapper.props(); const event = { type: 'click' }; @@ -369,11 +375,9 @@ describe('Container List', () => { // given const dispatch = jest.fn(); const setState = jest.fn(); - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - }, + mock.Provider.getEnzymeOption(mock.store.context()), ); const props = wrapper.props(); const event = { type: 'click' }; @@ -390,11 +394,9 @@ describe('Container List', () => { // given const dispatch = jest.fn(); const setState = jest.fn(); - const wrapper = shallow( + const wrapper = mount( , - { - lifecycleExperimental: true, - }, + mock.Provider.getEnzymeOption(mock.store.context()), ); const props = wrapper.props(); const event = { type: 'click' }; @@ -418,11 +420,12 @@ describe('Container List', () => { multiSelectionSetting.setState = jest.fn(); const state = fromJS({ selectedItems: [] }); multiSelectionSetting.state = state; - const wrapper = shallow(, { - lifecycleExperimental: true, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); // when - wrapper.instance().onToggleMultiSelection({}, { id: 1 }); + wrapper.find('List').props().list.itemProps.onToggle({}, { id: 1 }); // then expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ selectedItems: new ImmutableList([1]), @@ -439,11 +442,12 @@ describe('Container List', () => { multiSelectionSetting.setState = jest.fn(); const state = fromJS({ selectedItems: [1] }); multiSelectionSetting.state = state; - const wrapper = shallow(, { - lifecycleExperimental: true, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); // when - wrapper.instance().onToggleMultiSelection({}, { id: 1 }); + wrapper.find('List').props().list.itemProps.onToggle({}, { id: 1 }); // then expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ selectedItems: new ImmutableList([]), @@ -459,11 +463,12 @@ describe('Container List', () => { multiSelectionSetting.setState = jest.fn(); const state = fromJS({ selectedItems: [] }); multiSelectionSetting.state = state; - const wrapper = shallow(, { - lifecycleExperimental: true, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); // when - wrapper.instance().onToggleAllMultiSelection(); + wrapper.find('List').props().list.itemProps.onToggleAll(); // then expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ @@ -481,11 +486,12 @@ describe('Container List', () => { multiSelectionSetting.setState = jest.fn(); const state = fromJS({ selectedItems: [1, 2, 3] }); multiSelectionSetting.state = state; - const wrapper = shallow(, { - lifecycleExperimental: true, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); // when - wrapper.instance().onToggleAllMultiSelection(); + wrapper.find('List').props().list.itemProps.onToggleAll(); // then expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ selectedItems: new ImmutableList([]), @@ -504,9 +510,10 @@ describe('Container List', () => { multiSelectionSetting.state = state; // when - const wrapper = shallow(, { - lifecycleExperimental: true, - }); + const wrapper = mount( + , + mock.Provider.getEnzymeOption(mock.store.context()), + ); // then expect(wrapper.props().toolbar.actionBar.selected).toBe(3); }); diff --git a/packages/containers/src/List/__snapshots__/List.test.js.snap b/packages/containers/src/List/__snapshots__/List.test.js.snap index 3538fdf94d8..29d44450168 100644 --- a/packages/containers/src/List/__snapshots__/List.test.js.snap +++ b/packages/containers/src/List/__snapshots__/List.test.js.snap @@ -212,10 +212,12 @@ Object { "onChange": [Function], }, "titleProps": Object { + "actionsKey": "actions", "key": "label", "onClick": [Function], "onEditCancel": [Function], "onEditSubmit": [Function], + "persistentActionsKey": "persistentActions", }, }, "toolbar": Object { @@ -228,6 +230,7 @@ Object { "large", "table", ], + "mode": "table", "onChange": [Function], }, "filter": Object { diff --git a/packages/containers/src/Notification/Notification.test.js b/packages/containers/src/Notification/Notification.test.js index b81f2738f67..4265434cd36 100644 --- a/packages/containers/src/Notification/Notification.test.js +++ b/packages/containers/src/Notification/Notification.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; import { mock } from '@talend/react-cmf'; import Immutable, { fromJS } from 'immutable'; import Container from './Notification.container'; @@ -13,15 +13,9 @@ jest.mock('@talend/react-components/lib/Notification', () => props => ( describe('Container Notification', () => { it('should render', () => { - const { Provider } = mock; - const wrapper = renderer - .create( - - - , - ) - .toJSON(); - expect(wrapper).toMatchSnapshot(); + const context = mock.store.context(); + const wrapper = mount(, mock.Provider.getEnzymeOption(context)); + expect(wrapper.html()).toMatchSnapshot(); }); }); @@ -64,9 +58,7 @@ describe('Connected Notification', () => { const stateProps = { state: fromJS({ notifications: [ok] }), }; - expect(deleteNotification(ko)(stateProps)).toEqual( - stateProps.state - ); + expect(deleteNotification(ko)(stateProps)).toEqual(stateProps.state); }); }); diff --git a/packages/containers/src/Notification/__snapshots__/Notification.test.js.snap b/packages/containers/src/Notification/__snapshots__/Notification.test.js.snap index c17e27ab43a..2a047a66015 100644 --- a/packages/containers/src/Notification/__snapshots__/Notification.test.js.snap +++ b/packages/containers/src/Notification/__snapshots__/Notification.test.js.snap @@ -1,12 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Container Notification should render 1`] = ` -
-
`; diff --git a/packages/containers/src/SelectObject/__snapshots__/SelectObject.test.js.snap b/packages/containers/src/SelectObject/__snapshots__/SelectObject.test.js.snap index 859747cd42d..1ac3ef622ba 100644 --- a/packages/containers/src/SelectObject/__snapshots__/SelectObject.test.js.snap +++ b/packages/containers/src/SelectObject/__snapshots__/SelectObject.test.js.snap @@ -4,7 +4,7 @@ exports[`Component SelectObject should render 1`] = `
- - { }; }); -describe('#register contianers', () => { +describe('#register containers', () => { it('should register all component', () => { cmf.component.registerMany = jest.fn(); cmf.component.register = jest.fn(); diff --git a/packages/datagrid/src/containers/DataGrid/DataGrid.connect.test.js b/packages/datagrid/src/containers/DataGrid/DataGrid.connect.test.js index af59b97559c..d2ba4982aac 100644 --- a/packages/datagrid/src/containers/DataGrid/DataGrid.connect.test.js +++ b/packages/datagrid/src/containers/DataGrid/DataGrid.connect.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { mock } from '@talend/react-cmf'; import ConnectedDataGrid from './DataGrid.connect'; @@ -7,7 +7,9 @@ import ConnectedDataGrid from './DataGrid.connect'; describe('#ConnectedDataGrid', () => { it('should render a connected DataGrid', () => { const context = mock.store.context(); - const wrapper = shallow(, { context }); - expect(wrapper.getElement().type.displayName).toBe('CMF(Container(DataGrid))'); + const wrapper = mount(, mock.Provider.getEnzymeOption(context)); + expect(wrapper.find(ConnectedDataGrid.CMFContainer).type().displayName).toBe( + 'CMF(Container(DataGrid))', + ); }); }); diff --git a/packages/flow-designer/package.json b/packages/flow-designer/package.json index 639f0b7440c..f85351bbd7a 100644 --- a/packages/flow-designer/package.json +++ b/packages/flow-designer/package.json @@ -26,9 +26,9 @@ "@types/invariant": "^2.2.35", "@types/lodash": "^4.14.178", "@types/react": "^16.14.21", - "@types/react-redux": "^5.0.24", + "@types/react-redux": "^7.1.20", "@types/react-test-renderer": "^16.9.5", - "@types/redux-mock-store": "^0.0.15", + "@types/redux-mock-store": "^1.0.3", "@types/redux-thunk": "^2.1.0", "@typescript-eslint/parser": "^4.33.0", "enzyme": "^3.11.0", @@ -39,9 +39,9 @@ "react": "^16.14.0", "react-dom": "^16.14.0", "react-i18next": "^11.15.3", - "react-redux": "^5.1.2", + "react-redux": "^7.2.4", "react-test-renderer": "^17.0.2", - "redux": "^3.7.2", + "redux": "^4.1.0", "redux-mock-store": "^1.5.4", "redux-thunk": "^2.4.1", "reselect": "^4.1.5" diff --git a/packages/sagas/package.json b/packages/sagas/package.json index dcae04fdeda..9e66b964730 100644 --- a/packages/sagas/package.json +++ b/packages/sagas/package.json @@ -34,7 +34,7 @@ "dependencies": { "@talend/react-cmf": "^6.39.1", "immutable": "^3.8.2", - "redux-saga": "^0.15.6" + "redux-saga": "^1.1.3" }, "peerDependencies": { "prop-types": "^15.5.10", diff --git a/packages/sagas/src/pending/pending.js b/packages/sagas/src/pending/pending.js index 8f18c370516..1530c6b40b6 100644 --- a/packages/sagas/src/pending/pending.js +++ b/packages/sagas/src/pending/pending.js @@ -1,5 +1,4 @@ -import { delay } from 'redux-saga'; -import { call, put, select, take } from 'redux-saga/effects'; +import { delay, call, put, select, take } from 'redux-saga/effects'; import cmf from '@talend/react-cmf'; import { Map } from 'immutable'; @@ -40,7 +39,7 @@ export default function* pendingMaybeNeeded(asyncCallerId, actionId) { let pending = false; try { - yield call(delay, PENDING_DELAY_TO_SHOW); + yield delay(PENDING_DELAY_TO_SHOW); pending = true; yield call(ensurePendersCollectionExists); let pendersCollection = yield select(findPenders); diff --git a/packages/sagas/src/pending/pending.test.js b/packages/sagas/src/pending/pending.test.js index e157ae9f233..4cf9171587b 100644 --- a/packages/sagas/src/pending/pending.test.js +++ b/packages/sagas/src/pending/pending.test.js @@ -1,5 +1,4 @@ -import { delay } from 'redux-saga'; -import { select, put, call, take } from 'redux-saga/effects'; +import { delay, select, put, call, take } from 'redux-saga/effects'; import cmf from '@talend/react-cmf'; import { Map } from 'immutable'; import pendingMaybeNeeded, { @@ -34,7 +33,7 @@ describe('test pending status', () => { const gen = pendingMaybeNeeded('', 'streams:create'); let pendersCollection = new Map(); - expect(gen.next().value).toEqual(call(delay, PENDING_DELAY_TO_SHOW)); + expect(gen.next().value).toEqual(delay(PENDING_DELAY_TO_SHOW)); expect(gen.next().value).toEqual(call(ensurePendersCollectionExists)); expect(gen.next().value).toEqual(select(findPenders)); diff --git a/packages/stepper/package.json b/packages/stepper/package.json index f7d5a0ecdbd..19af02f7b7a 100644 --- a/packages/stepper/package.json +++ b/packages/stepper/package.json @@ -58,7 +58,7 @@ "react": "^16.14.0", "react-dom": "^16.14.0", "react-i18next": "^11.15.3", - "react-redux": "^5.1.2", + "react-redux": "^7.2.2", "react-transition-group": "^2.9.0", "style-loader": "^0.23.1" }, @@ -67,7 +67,7 @@ "prop-types": "^15.5.10", "react": "^16.8.6", "react-i18next": "^11.8.13", - "react-redux": "^5.0.7", + "react-redux": "^7.2.2", "react-transition-group": "^2.3.1" }, "publishConfig": { diff --git a/packages/storybook-cmf/package.json b/packages/storybook-cmf/package.json index f09549d1a77..fd562ad1edb 100644 --- a/packages/storybook-cmf/package.json +++ b/packages/storybook-cmf/package.json @@ -30,8 +30,8 @@ "enzyme": "^3.11.0", "react": "^16.14.0", "react-dom": "^16.14.0", - "react-redux": "^5.1.2", - "redux-saga": "^0.15.6" + "react-redux": "^7.2.2", + "redux-saga": "^1.1.3" }, "dependencies": { "@storybook/addons": "^6.4.9", @@ -40,7 +40,7 @@ "peerDependencies": { "@talend/react-cmf": "^6.0.0", "react": "^16.8.6", - "react-redux": "^5.0.7", - "redux-saga": "^0.15.4" + "react-redux": "^7.2.2", + "redux-saga": "^1.1.3" } } diff --git a/packages/storybook-cmf/src/CMFStory/CMFStory.component.js b/packages/storybook-cmf/src/CMFStory/CMFStory.component.js index fa9eb560d0e..6671378c823 100644 --- a/packages/storybook-cmf/src/CMFStory/CMFStory.component.js +++ b/packages/storybook-cmf/src/CMFStory/CMFStory.component.js @@ -3,7 +3,7 @@ import React from 'react'; import { all, fork } from 'redux-saga/effects'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; -import api, { store, RegistryProvider, mock } from '@talend/react-cmf'; +import api, { store, registry, RegistryProvider, mock } from '@talend/react-cmf'; function* initSagaMiddleWare() { yield all([fork(api.sagas.component.handle)]); @@ -34,6 +34,11 @@ class CMFStory extends React.Component { api.registerInternals(); props.sagaMiddleware.run(initSagaMiddleWare); } + if (props.registry) { + this.registry = { ...registry.getRegistry, ...props.registry }; + } else { + this.registry = registry.getRegistry() || {}; + } } getChildContext() { @@ -54,7 +59,7 @@ class CMFStory extends React.Component { render() { return ( - {this.props.children} + {this.props.children} ); } @@ -62,6 +67,7 @@ class CMFStory extends React.Component { CMFStory.propTypes = { state: PropTypes.object, + registry: PropTypes.object, children: PropTypes.node, reducer: PropTypes.func, enhancer: PropTypes.func, @@ -71,9 +77,7 @@ CMFStory.propTypes = { CMFStory.defaultProps = { middleware: [], }; -CMFStory.contextTypes = { - registry: PropTypes.object, -}; + CMFStory.childContextTypes = { router: PropTypes.object, }; diff --git a/packages/storybook-cmf/src/CMFStory/__snapshots__/CMFStory.test.js.snap b/packages/storybook-cmf/src/CMFStory/__snapshots__/CMFStory.test.js.snap index 4773b0741ff..1d52acb6d41 100644 --- a/packages/storybook-cmf/src/CMFStory/__snapshots__/CMFStory.test.js.snap +++ b/packages/storybook-cmf/src/CMFStory/__snapshots__/CMFStory.test.js.snap @@ -4,18 +4,20 @@ exports[`CMFStory should render its name 1`] = ` - +
My Story
-
+
`; diff --git a/versions/dependencies.json b/versions/dependencies.json index a61498d4150..00c997acd74 100644 --- a/versions/dependencies.json +++ b/versions/dependencies.json @@ -60,7 +60,7 @@ "react-hook-form": "^6.9.2", "react-i18next": "^10.11.4", "react-popper": "^1.3.7", - "react-redux": "^5.0.7", + "react-redux": "^7.2.2", "react-router": "^3.2.0", "react-router-redux": "^4.0.8", "react-router-dom": "^5.2.0", @@ -69,13 +69,13 @@ "react-use": "^17.3.1", "reactour": "^1.13.4", "recharts": "^2.1.6", - "redux": "^3.7.2", - "redux-batched-actions": "^0.2.0", + "redux": "^4.0.5", + "redux-batched-actions": "^0.5.0", "redux-batched-subscribe": "^0.1.6", "redux-logger": "^3.0.6", "redux-mock-store": "^1.5.4", - "redux-saga": "^0.15.4", - "redux-thunk": "^2.2.0", + "redux-saga": "^1.1.3", + "redux-thunk": "^2.3.0", "redux-undo": "beta", "reselect": "^2.5.4", "@sentry/browser": "^5.11.1", @@ -108,16 +108,16 @@ "babel-eslint": "^10.0.3", "babel-jest": "^24.7.1", "//comment_babel": "// babel 7", - "@babel/cli": "^7.11.5", - "@babel/core": "^7.11.5", + "@babel/cli": "^7.11.6", + "@babel/core": "^7.11.6", "@babel/polyfill": "^7.8.7", - "@babel/plugin-proposal-class-properties": "^7.11.5", - "@babel/plugin-proposal-object-rest-spread": "^7.11.5", - "@babel/plugin-transform-object-assign": "^7.11.5", - "@babel/plugin-proposal-export-namespace-from": "^7.11.5", - "@babel/plugin-proposal-export-default-from": "^7.11.5", - "@babel/preset-env": "^7.11.5", - "@babel/preset-react": "^7.11.5", + "@babel/plugin-proposal-class-properties": "^7.11.6", + "@babel/plugin-proposal-object-rest-spread": "^7.11.6", + "@babel/plugin-transform-object-assign": "^7.11.6", + "@babel/plugin-proposal-export-namespace-from": "^7.11.6", + "@babel/plugin-proposal-export-default-from": "^7.11.6", + "@babel/preset-env": "^7.11.6", + "@babel/preset-react": "^7.11.6", "enzyme": "^3.11.0", "enzyme-adapter-react-15": "^1.3.1", "enzyme-adapter-react-16": "^1.11.2", diff --git a/yarn.lock b/yarn.lock index c6146851f22..4c937440c9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1264,7 +1264,7 @@ core-js-pure "^3.19.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== @@ -2053,6 +2053,58 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9" integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA== +"@redux-saga/core@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4" + integrity sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg== + dependencies: + "@babel/runtime" "^7.6.3" + "@redux-saga/deferred" "^1.1.2" + "@redux-saga/delay-p" "^1.1.2" + "@redux-saga/is" "^1.1.2" + "@redux-saga/symbols" "^1.1.2" + "@redux-saga/types" "^1.1.0" + redux "^4.0.4" + typescript-tuple "^2.2.1" + +"@redux-saga/deferred@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/deferred/-/deferred-1.1.2.tgz#59937a0eba71fff289f1310233bc518117a71888" + integrity sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ== + +"@redux-saga/delay-p@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/delay-p/-/delay-p-1.1.2.tgz#8f515f4b009b05b02a37a7c3d0ca9ddc157bb355" + integrity sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g== + dependencies: + "@redux-saga/symbols" "^1.1.2" + +"@redux-saga/is@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/is/-/is-1.1.2.tgz#ae6c8421f58fcba80faf7cadb7d65b303b97e58e" + integrity sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w== + dependencies: + "@redux-saga/symbols" "^1.1.2" + "@redux-saga/types" "^1.1.0" + +"@redux-saga/symbols@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/symbols/-/symbols-1.1.2.tgz#216a672a487fc256872b8034835afc22a2d0595d" + integrity sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ== + +"@redux-saga/testing-utils@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@redux-saga/testing-utils/-/testing-utils-1.1.3.tgz#0148b7bfa541cb69ee144e1a2e79a53813c54560" + integrity sha512-MGMcBHgt80CoC8s8i0Mc7svGJPysS9qkJuAINlg+NvudLZcV23myd+H4uaXA4zmiLf16C4M+97b+e6wFoTaGcw== + dependencies: + "@redux-saga/symbols" "^1.1.2" + "@redux-saga/types" "^1.1.0" + +"@redux-saga/types@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" + integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== + "@rooks/use-mutation-observer@4.11.2": version "4.11.2" resolved "https://registry.yarnpkg.com/@rooks/use-mutation-observer/-/use-mutation-observer-4.11.2.tgz#a0466c4338e0a4487ea19253c86bcd427c29f4af" @@ -3911,7 +3963,7 @@ dependencies: "@types/unist" "*" -"@types/hoist-non-react-statics@*": +"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -4119,13 +4171,15 @@ dependencies: "@types/react" "^16" -"@types/react-redux@^5.0.24": - version "5.0.24" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-5.0.24.tgz#8567dbd87bdfbb8a2b17bcdbb064d678f86c91e2" - integrity sha512-EJfdjLtxceQmrVZrcwSZR6V97e6qyG/gaJ2MQrsVzGYiXO2cN+oHBSizB1/SnReTkorpEgQulhVdYcFee8abqQ== +"@types/react-redux@^7.1.20": + version "7.1.20" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.20.tgz#42f0e61ababb621e12c66c96dda94c58423bd7df" + integrity sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw== dependencies: + "@types/hoist-non-react-statics" "^3.3.0" "@types/react" "*" - redux "^3.6.0" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" "@types/react-syntax-highlighter@11.0.5": version "11.0.5" @@ -4159,12 +4213,12 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/redux-mock-store@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-0.0.15.tgz#94298dfd8395648e8c1f18705be2a5d4967ff3df" - integrity sha512-w7bLLE8Luu/MX7BNaoWODeI3YRIjYTr9J3cnzmiNfiwjfay2PWoGoBpUifYA8Y5CXdoacKAx8WyRtw5Xaiku1g== +"@types/redux-mock-store@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.3.tgz#895de4a364bc4836661570aec82f2eef5989d1fb" + integrity sha512-Wqe3tJa6x9MxMN4DJnMfZoBRBRak1XTPklqj4qkVm5VBpZnC8PSADf4kLuFQ9NAdHaowfWoEeUMz7NWc2GMtnA== dependencies: - redux "^3.6.0" + redux "^4.0.5" "@types/redux-thunk@^2.1.0": version "2.1.0" @@ -13132,11 +13186,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash-es@^4.2.1: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== - lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -13272,7 +13321,7 @@ lodash.unset@^4.1.0: resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" integrity sha1-Nw0dPoW3Kn4bDN8tJyEhMG8j5O0= -lodash@4.x, lodash@^4, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.2.1, lodash@^4.7.0: +lodash@4.x, lodash@^4, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -16614,7 +16663,7 @@ react-jsonschema-form@0.51.0: prop-types "^15.5.8" setimmediate "^1.0.5" -react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== @@ -16655,18 +16704,17 @@ react-prop-types@^0.4.0: dependencies: warning "^3.0.0" -react-redux@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57" - integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q== +react-redux@^7.2.2, react-redux@^7.2.4: + version "7.2.6" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa" + integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ== dependencies: - "@babel/runtime" "^7.1.2" - hoist-non-react-statics "^3.3.0" - invariant "^2.2.4" - loose-envify "^1.1.0" - prop-types "^15.6.1" - react-is "^16.6.0" - react-lifecycles-compat "^3.0.0" + "@babel/runtime" "^7.15.4" + "@types/react-redux" "^7.1.20" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^17.0.2" react-refresh@^0.10.0: version "0.10.0" @@ -17122,10 +17170,10 @@ redux-actions@^0.10.1: dependencies: reduce-reducers "^0.1.0" -redux-batched-actions@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/redux-batched-actions/-/redux-batched-actions-0.2.1.tgz#d0c41d495a934004cabafbe0e851746d1795e9dd" - integrity sha512-7lyD7tDdggZTi0v/7WQ3rvvHSk+JxGF4udoGGvw1/0gwjTUCRpD466Og/dm/daegEofmDKMqMWLv03DhgKuNDA== +redux-batched-actions@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/redux-batched-actions/-/redux-batched-actions-0.5.0.tgz#d3f0e359b2a95c7d80bab442df450bfafd57d122" + integrity sha512-6orZWyCnIQXMGY4DUGM0oj0L7oYnwTACsfsru/J7r94RM3P9eS7SORGpr3LCeRCMoIMQcpfKZ7X4NdyFHBS8Eg== redux-batched-subscribe@^0.1.6: version "0.1.6" @@ -17153,10 +17201,12 @@ redux-saga-tester@^1.0.874: dependencies: babel-runtime "^6.11.6" -redux-saga@^0.15.6: - version "0.15.6" - resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.15.6.tgz#8638dc522de6c6c0a496fe8b2b5466287ac2dc4d" - integrity sha1-hjjcUi3mxsCklv6LK1RmKHrC3E0= +redux-saga@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112" + integrity sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw== + dependencies: + "@redux-saga/core" "^1.1.3" redux-storage-decorator-filter@^1.1.8: version "1.1.8" @@ -17206,15 +17256,12 @@ redux-thunk@*, redux-thunk@^2.4.1: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== -redux@^3.6.0, redux@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" - integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A== +redux@^4.0.0, redux@^4.0.4, redux@^4.0.5, redux@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" + integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== dependencies: - lodash "^4.2.1" - lodash-es "^4.2.1" - loose-envify "^1.1.0" - symbol-observable "^1.0.3" + "@babel/runtime" "^7.9.2" reflect.ownkeys@^0.2.0: version "0.2.0" @@ -19268,11 +19315,6 @@ symbol-observable@1.0.1: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= -symbol-observable@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -19883,6 +19925,25 @@ typeface-source-sans-pro@^1.1.13: resolved "https://registry.yarnpkg.com/typeface-source-sans-pro/-/typeface-source-sans-pro-1.1.13.tgz#a7757542b8c301c26322acb0c8b97cc1a12cf91e" integrity sha512-eCczLh0FYByjVoMxZVDfSSTI8A6qJOBJftgWBVJL63AuemQTTvMU8DEk6ud/TQhkbIwWZ3A7ll/NfS9yI0lIrQ== +typescript-compare@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425" + integrity sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA== + dependencies: + typescript-logic "^0.0.0" + +typescript-logic@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/typescript-logic/-/typescript-logic-0.0.0.tgz#66ebd82a2548f2b444a43667bec120b496890196" + integrity sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q== + +typescript-tuple@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/typescript-tuple/-/typescript-tuple-2.2.1.tgz#7d9813fb4b355f69ac55032e0363e8bb0f04dad2" + integrity sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q== + dependencies: + typescript-compare "^0.0.2" + typescript@^3.0.0: version "3.9.10" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"