diff --git a/package.json b/package.json index d13727d..5171d36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eventrix", - "version": "2.4.1", + "version": "2.4.1-rc.0", "description": "A system for managing the state of the application based on the events broadcast in the application", "main": "dist/index.js", "repository": "https://github.com/rstgroup/eventrix", @@ -8,6 +8,7 @@ "scripts": { "test": "jest", "test:watch": "jest --watch", + "test:clean": "jest --clearCache", "build": "webpack --config webpack-prod.config.js --stats-error-details", "coverage": "jest --coverage", "coverage:report": "cat ./coverage/lcov.info | coveralls", @@ -25,8 +26,9 @@ "license": "MIT", "dependencies": { "lodash": "^4.17.4", - "react": "^16.0.0 || ^17.0.0", - "react-dom": "^16.0.0 || ^17.0.0" + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0", + "use-sync-external-store": "^1.0.0" }, "devDependencies": { "@babel/cli": "^7.7.0", @@ -42,21 +44,23 @@ "@babel/plugin-transform-runtime": "^7.6.2", "@babel/plugin-transform-typescript": "^7.14.6", "@babel/polyfill": "^7.0.0", - "@babel/preset-env": "^7.7.1", - "@babel/preset-react": "^7.0.0", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.13.0", "@babel/runtime": "^7.6.3", "@babel/traverse": "^7.7.0", - "@testing-library/jest-dom": "^5.11.9", - "@testing-library/react": "^11.2.5", - "@types/jest": "^26.0.23", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^12.1.15 || ^13.1.1", + "@types/jest": "^27.4.1", "@types/lodash": "^4.14.170", - "@types/react": "^17.0.11", + "@types/react": "^17.0.11 || ^18.0.0", + "@types/use-sync-external-store": "0.0.3", "@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/parser": "^4.28.0", "babel-eslint": "^10.1.0", + "babel-jest": "^27.5.1", "babel-loader": "^8.0.6", - "clean-webpack-plugin": "^4.0.0-alpha.0", + "clean-webpack-plugin": "^4.0.0", "coveralls": "^3.1.0", "eslint": "^7.32.0", "eslint-config-prettier": "^6.14.0", @@ -64,12 +68,11 @@ "eslint-plugin-jest": "^25.0.1", "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-react": "^7.21.5", - "jest": "^24.9.0", + "jest": "^27.5.1", "loader-utils": "^2.0.0", "pre-commit": "^1.2.2", "prettier": "^2.4.1", - "react-proptypes": "^1.0.0", - "ts-jest": "^27.0.3", + "ts-jest": "^27.1.4", "ts-loader": "^9.2.3", "typescript": "^4.2.4", "webpack": "^5.31.2", @@ -77,6 +80,7 @@ }, "types": "./types/index.d.ts", "jest": { + "testEnvironment": "jsdom", "moduleFileExtensions": [ "ts", "tsx", diff --git a/src/react/context/EventrixProvider.tsx b/src/react/context/EventrixProvider.tsx index 0439340..de469cd 100644 --- a/src/react/context/EventrixProvider.tsx +++ b/src/react/context/EventrixProvider.tsx @@ -4,6 +4,7 @@ import { EventrixI } from '../../interfaces'; export interface EventrixProviderPropsI { eventrix: EventrixI; + children: React.ReactNode; } const EventrixProvider: React.FC = ({ eventrix, children }): JSX.Element => { diff --git a/src/react/decorators/eventrixState.test.tsx b/src/react/decorators/eventrixState.test.tsx index ce68e7f..dbe8904 100644 --- a/src/react/decorators/eventrixState.test.tsx +++ b/src/react/decorators/eventrixState.test.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; import EventrixProvider from '../context/EventrixProvider'; import Eventrix from '../../Eventrix'; import eventrixComponent from './eventrixComponent'; @@ -11,7 +11,12 @@ describe('eventrixState', () => { class ItemComponent extends React.Component { render() { const { fooBar }: any = this.state; - return
{fooBar}
; + return ( +
+ {fooBar === 'test' &&
} + {fooBar} +
+ ); } } @@ -29,6 +34,7 @@ describe('eventrixState', () => { const { fooBar, componentState }: any = this.state; return (
+ {fooBar === 'test' &&
} {fooBar} {componentState}
); @@ -37,7 +43,7 @@ describe('eventrixState', () => { const TestContainer = ({ eventrix, children }: any) => {children}; - it('should change component state when eventrix state changed', () => { + it('should change component state when eventrix state changed', async () => { const eventrixInstance = new Eventrix({ foo: { bar: 'empty', @@ -51,10 +57,11 @@ describe('eventrixState', () => { , ); eventrixInstance.stateManager.setState('foo.bar', 'test'); + await waitFor(() => getByTestId('testData')); expect(getByTestId('stateData').textContent).toEqual('test'); }); - it('should change component state when eventrix state changed and component has own state', () => { + it('should change component state when eventrix state changed and component has own state', async () => { const eventrixInstance = new Eventrix({ foo: { bar: 'empty', @@ -68,6 +75,7 @@ describe('eventrixState', () => { , ); eventrixInstance.stateManager.setState('foo.bar', 'test'); + await waitFor(() => getByTestId('testData')); expect(getByTestId('stateData').textContent).toEqual('test test'); }); }); diff --git a/src/react/hocs/withEventrixState.test.tsx b/src/react/hocs/withEventrixState.test.tsx index cd4289a..3cc3263 100644 --- a/src/react/hocs/withEventrixState.test.tsx +++ b/src/react/hocs/withEventrixState.test.tsx @@ -1,16 +1,12 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { EventrixProvider } from '../context'; import Eventrix from '../../Eventrix'; import withEventrixState from './withEventrixState'; describe('withEventrixState', () => { const ComponentTestFoo = ({ foo = '' }: { foo: string }) => { - return ( -
-
{foo}
-
- ); + return
{!!foo &&
{foo}
}
; }; interface FooStateI { @@ -19,11 +15,7 @@ describe('withEventrixState', () => { const ComponentTestFooWS = withEventrixState(ComponentTestFoo, ['foo']); const ComponentTestBarFoo = ({ barFoo = '' }: { barFoo: string }) => { - return ( -
-
{barFoo}
-
- ); + return
{!!barFoo &&
{barFoo}
}
; }; interface BarFooStateI { @@ -34,11 +26,7 @@ describe('withEventrixState', () => { })); const ComponentTestBarBarFoo = ({ barBarFoo = '' }: { barBarFoo: string }) => { - return ( -
-
{barBarFoo}
-
- ); + return
{!!barBarFoo &&
{barBarFoo}
}
; }; interface BarBarFooStateI { barBarFoo: string; @@ -49,7 +37,7 @@ describe('withEventrixState', () => { const TestContainer = ({ eventrix, children }: any) => {children}; - it('should change state when eventrix state has changed', () => { + it('should change state when eventrix state has changed', async () => { const initialState = { foo: '', bar: { @@ -73,18 +61,21 @@ describe('withEventrixState', () => { expect(getByTestId('testBarBarFoo').textContent).toEqual(initialState.bar.bar.foo); eventrixInstance.stateManager.setState('foo', 'newFoo'); + await waitFor(() => getByTestId('testFooValue')); expect(getByTestId('testFoo').textContent).toEqual('newFoo'); expect(getByTestId('testBarFoo').textContent).toEqual(initialState.bar.foo); expect(getByTestId('testBarBarFoo').textContent).toEqual(initialState.bar.bar.foo); eventrixInstance.stateManager.setState('bar.foo', 'newBarFoo'); + await waitFor(() => getByTestId('testBarFooValue')); expect(getByTestId('testFoo').textContent).toEqual('newFoo'); expect(getByTestId('testBarFoo').textContent).toEqual('newBarFoo'); expect(getByTestId('testBarBarFoo').textContent).toEqual(initialState.bar.bar.foo); eventrixInstance.stateManager.setState('bar.bar.foo', 'newBarBarFoo'); + await waitFor(() => getByTestId('testBarBarFooValue')); expect(getByTestId('testFoo').textContent).toEqual('newFoo'); expect(getByTestId('testBarFoo').textContent).toEqual('newBarFoo'); diff --git a/src/react/hooks/useEventState.test.tsx b/src/react/hooks/useEventState.test.tsx index cd595b0..917b631 100644 --- a/src/react/hooks/useEventState.test.tsx +++ b/src/react/hooks/useEventState.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { EventrixProvider } from '../context'; import Eventrix from '../../Eventrix'; import useEventState from './useEventState'; @@ -7,11 +7,11 @@ import useEventState from './useEventState'; describe('useEventState', () => { const ItemComponent = () => { const [eventData] = useEventState('testEvent'); - return
{eventData}
; + return
{!!eventData &&
{eventData}
}
; }; const TestContainer = ({ eventrix, children }: any) => {children}; - it('should save event data in state', () => { + it('should save event data in state', async () => { const eventrixInstance = new Eventrix({}); const { getByTestId } = render( @@ -20,6 +20,7 @@ describe('useEventState', () => { , ); eventrixInstance.emit('testEvent', 'testData'); + await waitFor(() => getByTestId('eventDataValue')); expect(getByTestId('eventData').textContent).toEqual('testData'); }); }); diff --git a/src/react/hooks/useEventState.tsx b/src/react/hooks/useEventState.tsx index cc7dae7..22917aa 100644 --- a/src/react/hooks/useEventState.tsx +++ b/src/react/hooks/useEventState.tsx @@ -6,7 +6,7 @@ function useEventState(eventName: string): [EventStateI | undefined const { eventrix } = useContext(EventrixContext); const [eventState, setEventState] = useState(); - const listener = useCallback((data) => setEventState(data), [setEventState]); + const listener = useCallback((data: EventStateI) => setEventState(data), [setEventState]); useEffect(() => { eventrix.listen(eventName, listener); diff --git a/src/react/hooks/useEventrixState.test.tsx b/src/react/hooks/useEventrixState.test.tsx index 9acb803..22ba304 100644 --- a/src/react/hooks/useEventrixState.test.tsx +++ b/src/react/hooks/useEventrixState.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; import { EventrixProvider } from '../context'; import Eventrix from '../../Eventrix'; import useEvent from './useEvent'; @@ -9,25 +10,19 @@ describe('useEventrixState', () => { const OtherStateComponent = () => { const [test, setTest] = useEventrixState('test'); useEvent('changeTest', (newState) => setTest(newState)); - return ( -
-
{test}
-
- ); + return
{!!test &&
{test}
}
; }; const FooInFooComponent = () => { const [foo = {}] = useEventrixState('foo'); return ( -
-
{foo.description}
-
+
{!!foo.description &&
{foo.description}
}
); }; const FooBarComponent = () => { const [fooBar] = useEventrixState('foo.bar'); - return
{fooBar}
; + return
{!!fooBar &&
{fooBar}
}
; }; const FooComponent = () => { @@ -35,7 +30,7 @@ describe('useEventrixState', () => { useEvent('changeFoo', (newState) => setFoo(newState)); return (
-
{foo.title}
+
{!!foo.title &&
{foo.title}
}
@@ -45,14 +40,14 @@ describe('useEventrixState', () => { const TestContainer = ({ eventrix, children }: any) => {children}; - it('should change state when eventrix state has changed', () => { + it('should change state when eventrix state has changed', async () => { const initialState = { foo: { - title: 'initialTitle', - description: 'initialDescription', - bar: 'initialBar', + title: '', + description: '', + bar: '', }, - test: 'initialTest', + test: '', }; const eventrixInstance = new Eventrix(initialState); const secondFooData = { @@ -73,6 +68,7 @@ describe('useEventrixState', () => { expect(getByTestId('testData').textContent).toEqual(initialState.test); eventrixInstance.emit('changeTest', secondTestData); + await waitFor(() => getByTestId('testDataValue')); expect(getByTestId('fooTitle').textContent).toEqual(initialState.foo.title); expect(getByTestId('fooDescription').textContent).toEqual(initialState.foo.description); @@ -80,6 +76,9 @@ describe('useEventrixState', () => { expect(getByTestId('testData').textContent).toEqual(secondTestData); eventrixInstance.emit('changeFoo', secondFooData); + await waitFor(() => getByTestId('fooTitleValue')); + await waitFor(() => getByTestId('fooDescriptionValue')); + await waitFor(() => getByTestId('fooBarDataValue')); expect(getByTestId('fooTitle').textContent).toEqual(secondFooData.title); expect(getByTestId('fooDescription').textContent).toEqual(secondFooData.description); diff --git a/src/react/hooks/useEventrixState.tsx b/src/react/hooks/useEventrixState.tsx index 5b64544..986e5c2 100644 --- a/src/react/hooks/useEventrixState.tsx +++ b/src/react/hooks/useEventrixState.tsx @@ -10,7 +10,7 @@ function useEventrixState(stateName: string): [StateI, SetStateI const onSetEventrixState = useCallback(() => setState(eventrix.getState(stateName)), [setState, stateName]); const setEventrixState = useCallback( - (value) => { + (value: StateI) => { eventrix.emit('setState', { stateName, value }); }, [eventrix.emit, stateName], diff --git a/src/react/hooks/useFetchState.test.tsx b/src/react/hooks/useFetchState.test.tsx index c8bd2d6..62245f8 100644 --- a/src/react/hooks/useFetchState.test.tsx +++ b/src/react/hooks/useFetchState.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; import { EventrixProvider } from '../context'; import Eventrix from '../../Eventrix'; import useFetchState from './useFetchState'; @@ -61,7 +61,7 @@ describe('useFetchState', () => { expect(getByTestId('loading').textContent).toEqual('Loading'); expect(getByTestId('status').textContent).toEqual(FetchStateStatus.Loading); }); - it('should fetch users and show fetch success message', () => { + it('should fetch users and show fetch success message', async () => { const mockedUsersList: UserI[] = [ { name: 'Jan', surname: 'Kowalski' }, { name: 'Jan', surname: 'Nowak' }, @@ -75,13 +75,13 @@ describe('useFetchState', () => { , ); fireEvent.click(getByTestId('fetchDataButton')); - return Promise.resolve().then(() => { - expect(mockedFetchMetchod).toHaveBeenCalledWith({ search: 'Jan' }); - expect(getByTestId('success').textContent).toEqual('Success loaded list'); - expect(getByTestId('status').textContent).toEqual(FetchStateStatus.Success); - }); + + await waitFor(() => getByTestId('success')); + expect(mockedFetchMetchod).toHaveBeenCalledWith({ search: 'Jan' }); + expect(getByTestId('success').textContent).toEqual('Success loaded list'); + expect(getByTestId('status').textContent).toEqual(FetchStateStatus.Success); }); - it('should fetch users and show fetch error message', () => { + it('should fetch users and show fetch error message', async () => { const fetchErrorMessage = 'fetch error'; const mockedFetchMetchod = jest.fn(() => Promise.reject({ message: fetchErrorMessage })); const usersFetchReceiver = fetchStateReceiver('users', mockedFetchMetchod); @@ -92,12 +92,9 @@ describe('useFetchState', () => { , ); fireEvent.click(getByTestId('fetchDataButton')); - return Promise.reject() - .catch(() => {}) - .then(() => { - expect(mockedFetchMetchod).toHaveBeenCalledWith({ search: 'Jan' }); - expect(getByTestId('error').textContent).toEqual(fetchErrorMessage); - expect(getByTestId('status').textContent).toEqual(FetchStateStatus.Error); - }); + await waitFor(() => getByTestId('error')); + expect(mockedFetchMetchod).toHaveBeenCalledWith({ search: 'Jan' }); + expect(getByTestId('error').textContent).toEqual(fetchErrorMessage); + expect(getByTestId('status').textContent).toEqual(FetchStateStatus.Error); }); }); diff --git a/src/react/hooks/useFetchToState.test.tsx b/src/react/hooks/useFetchToState.test.tsx index 14e9703..a289087 100644 --- a/src/react/hooks/useFetchToState.test.tsx +++ b/src/react/hooks/useFetchToState.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { EventrixProvider } from '../context'; import Eventrix from '../../Eventrix'; import useEmit from './useEmit'; @@ -33,17 +33,15 @@ describe('useFetchToState', () => { }; const TestContainer = ({ eventrix, children }: any) => {children}; - it('should fetch data and set state', (done) => { + it('should fetch data and set state', async () => { const eventrixInstance = new Eventrix({}); const { getByTestId } = render( , ); - eventrixInstance.listen('fetchData.success', () => { - expect(getByTestId('eventData').textContent).toEqual('testData'); - done(); - }); eventrixInstance.emit('remoteFetch'); + await waitFor(() => getByTestId('eventData')); + expect(getByTestId('eventData').textContent).toEqual('testData'); }); }); diff --git a/src/react/hooks/useFetchToState.tsx b/src/react/hooks/useFetchToState.tsx index 1394764..647b401 100644 --- a/src/react/hooks/useFetchToState.tsx +++ b/src/react/hooks/useFetchToState.tsx @@ -5,7 +5,7 @@ import { FetchMethodI, EmitFetchI } from '../../interfaces'; function useFetchToState(eventName: string, statePath: string, fetchMethod: FetchMethodI): [EmitFetchI] { const { eventrix } = useContext(EventrixContext); - const emitFetch = useCallback((data) => eventrix.emit(eventName, data), [eventrix.emit, eventName]); + const emitFetch = useCallback((data: EventDataI) => eventrix.emit(eventName, data), [eventrix.emit, eventName]); useEffect(() => { const fetchReceiver = fetchToStateReceiver(eventName, statePath, fetchMethod); diff --git a/src/redux/connect.tsx b/src/redux/connect.tsx index 97f0606..a9d1682 100644 --- a/src/redux/connect.tsx +++ b/src/redux/connect.tsx @@ -1,55 +1,24 @@ -import React, { Component } from 'react'; -import { EventrixContext } from '../react'; +import React, { useCallback, useContext, useState } from 'react'; +import { EventrixContext, useEvent } from '../react'; import { DISPATCH_EVENT_NAME } from './events'; -import { mapStateToPropsType, mapDispatchToPropsType, mapDispatchToPropsResponseType, ActionI } from '../interfaces'; - -interface StateI { - store: any; -} - -function connect

(mapStateToProps: mapStateToPropsType, mapDispatchToProps: mapDispatchToPropsType, Context = EventrixContext) { - return (BaseComponent: any) => - // eslint-disable-next-line react/display-name - class extends Component { - static contextType = Context; - - constructor(props: P, context: any) { - super(props, context); - this.dispatch = this.dispatch.bind(this); - this.updateState = this.updateState.bind(this); - this.state = { - store: context.eventrix.getState(), - }; - } - - componentDidMount(): void { - this.context.eventrix.listen('setState:*', this.updateState); - } - - componentWillUnmount(): void { - this.context.eventrix.unlisten('setState:*', this.updateState); - } - - getStateToProps(): ReducedStateI { - const state = this.context.eventrix.getState(); - return mapStateToProps(state); - } - - getDispatchToProps(): mapDispatchToPropsResponseType { - return mapDispatchToProps(this.dispatch); - } - - dispatch(action: ActionI): void { - this.context.eventrix.emit(DISPATCH_EVENT_NAME, action); - } - - updateState(): void { - this.setState({ store: this.context.eventrix.getState() }); - } - - render() { - return ; - } +import { mapStateToPropsType, mapDispatchToPropsType, ActionI } from '../interfaces'; + +function connect

(mapStateToProps: mapStateToPropsType, mapDispatchToProps: mapDispatchToPropsType) { + return (BaseComponent: any) => { + const connectWrapper = (props: P) => { + const [state, setState] = useState(true); + const { eventrix } = useContext(EventrixContext); + const storeState = eventrix.getState(); + const updateState = useCallback(() => { + setState(!state); + }, [state, setState]); + useEvent('setState:*', updateState); + const dispatch = useCallback((action: ActionI) => eventrix.emit(DISPATCH_EVENT_NAME, action), [eventrix]); + + return ; }; + return connectWrapper; + }; } + export default connect;