From 07e889e023d60f76908bf4da0c76fd6716aa37cc Mon Sep 17 00:00:00 2001 From: Eric Mackrodt Date: Mon, 1 Jul 2019 23:26:46 +1000 Subject: [PATCH] Add Hooks api (#6) * Create hooks api * Use sideEffect for hook subscription. * Update hooks code. * Performance improvements to the hook * Hooks tests --- packages/core/src/index.ts | 2 + packages/core/src/state-container.ts | 8 +- packages/core/src/utils.ts | 22 ++ packages/example/package.json | 12 +- .../src/calculator-state-definition.ts | 3 +- packages/example/src/index.tsx | 2 + packages/example/src/long-list.tsx | 19 ++ packages/example/src/state.ts | 4 +- packages/example/src/welcome-component.tsx | 42 +--- packages/react/package.json | 12 +- packages/react/src/__tests__/hooks.test.tsx | 152 +++++++++++++ packages/react/src/__tests__/react.test.tsx | 21 +- packages/react/src/connect.tsx | 5 +- packages/react/src/context.ts | 5 - packages/react/src/hooks.ts | 91 ++++++++ packages/react/src/provider.tsx | 8 +- packages/react/src/react.ts | 21 +- packages/react/src/types.ts | 15 ++ packages/react/tsconfig-web.json | 2 +- yarn.lock | 199 ++++++++++++++---- 20 files changed, 533 insertions(+), 112 deletions(-) create mode 100644 packages/example/src/long-list.tsx create mode 100644 packages/react/src/__tests__/hooks.test.tsx delete mode 100644 packages/react/src/context.ts create mode 100644 packages/react/src/hooks.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5a01b62..668328d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -5,8 +5,10 @@ export { Transformers, Subscription, Staat, + LegacyStaat, StateContainerType, IScope, + TransformersTree, } from './types'; export * from './scope'; export const internals = { diff --git a/packages/core/src/state-container.ts b/packages/core/src/state-container.ts index 379c767..3c74dc8 100644 --- a/packages/core/src/state-container.ts +++ b/packages/core/src/state-container.ts @@ -1,4 +1,5 @@ import { Subscription } from './types'; +import { processLargeArrayAsync } from './utils'; export class StateContainer { private state: T; @@ -10,15 +11,12 @@ export class StateContainer { } private fireSubscriptions = () => { - const results = this.subscriptions.map(s => s()); - return Promise.all(results); + processLargeArrayAsync(this.subscriptions, s => s()); }; public setState(state: T) { this.state = state; - Promise.resolve().then(() => - this.fireSubscriptions().then(() => this.state), - ); + this.fireSubscriptions(); return this.state; } diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 42c9bf4..5c62f28 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -68,3 +68,25 @@ export function getScope, TScope>( ): TScope { return getProperty(state, path, 0); } + +// https://stackoverflow.com/questions/10344498/best-way-to-iterate-over-an-array-without-blocking-the-ui/10344560#10344560 +export function processLargeArrayAsync( + array: T[], + fn: (c: T) => void, + chunk = 100, +) { + chunk = chunk || 100; + let index = 0; + const doChunk = () => { + let cnt = chunk; + while (cnt-- && index < array.length) { + fn(array[index]); + ++index; + } + if (index < array.length) { + // set Timeout for async iteration + setTimeout(doChunk, 1); + } + }; + doChunk(); +} diff --git a/packages/example/package.json b/packages/example/package.json index 3995fb5..56f6704 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -12,13 +12,13 @@ "dependencies": { "staat": "1.2.0", "staat-timetravel": "1.2.0", - "staat-react": "1.2.0", - "react": "^16.7.0", - "react-dom": "^16.7.0" + "staat-react": "1.3.0", + "react": "^16.8.6", + "react-dom": "^16.8.6" }, "devDependencies": { - "@types/react": "^16.7.17", - "@types/react-dom": "^16.0.11", + "@types/react": "^16.8.20", + "@types/react-dom": "^16.8.4", "clean-webpack-plugin": "^1.0.0", "html-webpack-plugin": "^3.2.0", "ts-loader": "5.3.2", @@ -26,4 +26,4 @@ "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.14" } -} +} \ No newline at end of file diff --git a/packages/example/src/calculator-state-definition.ts b/packages/example/src/calculator-state-definition.ts index 224039c..05c082f 100644 --- a/packages/example/src/calculator-state-definition.ts +++ b/packages/example/src/calculator-state-definition.ts @@ -26,11 +26,10 @@ const subtract = calculatorScope.reducer( }, ); -const t = timeTravel( +export default timeTravel( { add, subtract, }, calculatorScope, ); -export default t; diff --git a/packages/example/src/index.tsx b/packages/example/src/index.tsx index 20b4a36..eaa6752 100644 --- a/packages/example/src/index.tsx +++ b/packages/example/src/index.tsx @@ -3,11 +3,13 @@ import * as ReactDOM from 'react-dom'; import { Provider } from './state'; import Calculator from './calculator'; import Welcome from './welcome-component'; +import { LongList } from './long-list'; ReactDOM.render( + , document.getElementById('entry'), ); diff --git a/packages/example/src/long-list.tsx b/packages/example/src/long-list.tsx new file mode 100644 index 0000000..5bb49c4 --- /dev/null +++ b/packages/example/src/long-list.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { useStaat } from './state'; + +export const LongListItem: React.FunctionComponent = () => { + const name = useStaat(({ welcome }) => welcome.name); + return
Value: {name}
; +}; + +export const LongList: React.FunctionComponent = () => { + return ( +
+ {Array(5000) + .fill(undefined) + .map((_, index) => ( + + ))} +
+ ); +}; diff --git a/packages/example/src/state.ts b/packages/example/src/state.ts index 222a00f..d73c760 100644 --- a/packages/example/src/state.ts +++ b/packages/example/src/state.ts @@ -10,4 +10,6 @@ const initialState = { export const appState = staat(initialState); -export const { connect, Provider } = reactStaat(appState); +export const { connect, Provider, useReducers, useStaat } = reactStaat( + appState, +); diff --git a/packages/example/src/welcome-component.tsx b/packages/example/src/welcome-component.tsx index 114acbd..08cedf5 100644 --- a/packages/example/src/welcome-component.tsx +++ b/packages/example/src/welcome-component.tsx @@ -1,43 +1,21 @@ import * as React from 'react'; -import { connect, appState } from './state'; -import * as welcome from './welcome-state-definition'; +import { useReducers, useStaat } from './state'; +import * as welcomeDefinition from './welcome-state-definition'; -const Welcome: React.StatelessComponent = props => { +const Welcome: React.FunctionComponent = () => { + const name = useStaat(({ welcome }) => welcome.name); + const { setName } = useReducers({ + setName: welcomeDefinition.setName, + }); return (
-

Welcome, {props.name}

- props.setName(evt.target.value)} /> +

Welcome, {name}

+ setName(evt.target.value)} /> {/* */}
); }; -type StateProps = { - name?: string; -}; - -type TrasformerProps = { - setName: (name: string) => void; - // undo: typeof welcomeState.undo; - // redo: typeof welcomeState.redo; -}; - -type OwnProps = {}; - -type WelcomeProps = StateProps & TrasformerProps & OwnProps; -export default connect( - state => { - return { - name: state.welcome.name, - }; - }, - () => { - return { - setName: (name: string) => appState.reduce(welcome.setName, name), - // undo: welcome.undo.bind(welcomeState), - // redo: welcomeState.redo.bind(welcomeState) - }; - }, -)(Welcome); +export default Welcome; diff --git a/packages/react/package.json b/packages/react/package.json index 3d9918d..3b8b6de 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "staat-react", - "version": "1.2.0", + "version": "1.3.0", "description": "", "main": "build/index.js", "typings": "build/index.d.ts", @@ -13,19 +13,21 @@ "license": "ISC", "repository": "https://github.com/ericmackrodt/staat", "devDependencies": { - "@types/react": "^16.7.17", + "@types/react": "^16.8.20", "@babel/core": "7.3.3", + "@testing-library/react": "^8.0.4", + "@testing-library/react-hooks": "^1.1.0", "babel-loader": "8.0.5", "cross-env": "^5.2.0", "jest-dom": "^3.0.0", "metro-react-native-babel-preset": "0.51.1", - "react-testing-library": "^5.4.4", + "react-test-renderer": ">=16.8.3", "ts-loader": "5.3.2", "webpack": "^4.28.1", "webpack-cli": "^3.1.2" }, "peerDependencies": { - "react": ">=16.5.0", + "react": ">=16.8.3", "staat": "1.2.0" } -} +} \ No newline at end of file diff --git a/packages/react/src/__tests__/hooks.test.tsx b/packages/react/src/__tests__/hooks.test.tsx new file mode 100644 index 0000000..872198e --- /dev/null +++ b/packages/react/src/__tests__/hooks.test.tsx @@ -0,0 +1,152 @@ +import { reactStaat } from '../react'; +import staat from 'staat'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { wait } from '@testing-library/react'; + +type TestState = { + count: number; + other: string; +}; + +const state: TestState = { + count: 0, + other: 'tst', +}; + +it('builds react-staat object', () => { + const staatState = staat({ ...state }); + const sut = reactStaat(staatState); + expect(typeof sut.Provider).toBe('function'); + expect(typeof sut.connect).toBe('function'); + expect(typeof sut.useStaat).toBe('function'); + expect(typeof sut.useReducers).toBe('function'); +}); + +describe('Hooks', () => { + describe('useStaat', () => { + it('updates the value', async () => { + const staatState = staat({ ...state }); + const { useStaat } = reactStaat(staatState); + + const { result } = renderHook(() => useStaat(({ count }) => count)); + + act(() => { + staatState.reduce(s => ({ ...s, count: 1000 })); + }); + + await wait(() => expect(result.current).toBe(1000)); + }); + }); + + it('unsubscribes from staat', async () => { + const staatState = staat({ ...state }); + const { useStaat } = reactStaat(staatState); + staatState.unsubscribe = jest.fn(); + const { unmount } = renderHook(() => useStaat(({ count }) => count)); + unmount(); + expect(staatState.unsubscribe).toHaveBeenCalled(); + }); + + it('updates if number of members is different', async () => { + const staatState = staat({ ...state }); + const { useStaat } = reactStaat(staatState); + + const { result } = renderHook(() => useStaat(({ count }) => count)); + + act(() => { + staatState.reduce(sts => { + return { ...sts, count: 200, another: '1' }; + }); + }); + + await wait(() => expect(result.current).toBe(200)); + }); + + it('does not update the component if state is same object', async () => { + const original = { ...state }; + const staatState = staat(original); + const { useStaat } = reactStaat(staatState); + + const { result } = renderHook(() => useStaat(sts => sts)); + + act(() => { + staatState.reduce(sts => { + sts.count = 200; + return sts; + }); + }); + await wait(() => expect(result.current).toBe(original), { timeout: 100 }); + expect(original.count).toBe(200); + }); + + it('does not update the component if state members are equal', async () => { + const original = { ...state }; + const staatState = staat(original); + const { useStaat } = reactStaat(staatState); + + const { result } = renderHook(() => useStaat(sts => sts)); + + act(() => { + staatState.reduce(sts => { + return { ...sts }; + }); + }); + await wait(() => expect(result.current).toBe(original), { timeout: 100 }); + expect(result.current).toBe(original); + }); + + it('updates when same number of properties but different name', async () => { + const original: { a: string; b?: number; c?: number } = { a: '1', b: 2 }; + const staatState = staat(original); + const { useStaat } = reactStaat(staatState); + + const { result } = renderHook(() => useStaat(sts => sts)); + + act(() => { + staatState.reduce(() => { + return { a: '1', c: 2 }; + }); + }); + await wait(() => expect(result.current).toEqual({ a: '1', c: 2 }), { + timeout: 100, + }); + }); + + describe('useReducers', () => { + it('changes the state without arguments', () => { + const staatState = staat({ ...state }); + const { useReducers } = reactStaat(staatState); + const newValue = 10; + + const { result } = renderHook(() => + useReducers({ + testReducer: (sts: TestState) => ({ ...sts, count: newValue }), + }), + ); + act(() => { + result.current.testReducer(); + }); + + expect(staatState.currentState.count).toBe(10); + }); + + it('changes the state with arguments', () => { + const staatState = staat({ ...state }); + const { useReducers } = reactStaat(staatState); + + const { result } = renderHook(() => + useReducers({ + testReducer: (sts: TestState, value: number) => ({ + ...sts, + count: value, + }), + }), + ); + act(() => { + result.current.testReducer(300); + }); + + expect(staatState.currentState.count).toBe(300); + }); + }); +}); diff --git a/packages/react/src/__tests__/react.test.tsx b/packages/react/src/__tests__/react.test.tsx index 9831f25..7082eaf 100644 --- a/packages/react/src/__tests__/react.test.tsx +++ b/packages/react/src/__tests__/react.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { reactStaat } from '../react'; -import staat from 'staat'; +import staat, { LegacyStaat } from 'staat'; import { ReactStaat } from '../types'; -import { render, fireEvent } from 'react-testing-library'; +import { render, fireEvent, cleanup } from '@testing-library/react'; /* tslint:disable no-submodule-imports */ import 'jest-dom/extend-expect'; @@ -44,9 +44,10 @@ const TestComponent: React.StatelessComponent = ({ describe('React', () => { let sut: ReactStaat; let ConnectedComponent: React.ComponentType; + let staatState: LegacyStaat; beforeEach(() => { - const staatState = staat(state, transformers); + staatState = staat(transformers, state); sut = reactStaat(staatState); ConnectedComponent = sut.connect( @@ -61,6 +62,8 @@ describe('React', () => { )(TestComponent); }); + afterEach(cleanup); + it('builds react-staat object', () => { expect(typeof sut.Provider).toBe('function'); expect(typeof sut.connect).toBe('function'); @@ -91,4 +94,16 @@ describe('React', () => { ); expect(getByText(/^Count:/).textContent).toBe('Count: 10'); }); + + it('unsubscribes when unmounting', async () => { + staatState.unsubscribe = jest.fn(); + const tree = ( + + + + ); + const { unmount } = render(tree); + unmount(); + expect(staatState.unsubscribe).toHaveBeenCalled(); + }); }); diff --git a/packages/react/src/connect.tsx b/packages/react/src/connect.tsx index 1245d01..7cc5c17 100644 --- a/packages/react/src/connect.tsx +++ b/packages/react/src/connect.tsx @@ -1,7 +1,6 @@ -import * as React from 'react'; -import { Consumer } from './context'; +import React, { Context } from 'react'; -export default function makeConnect() { +export default function makeConnect({ Consumer }: Context) { return function connect( mapStateToProps: (states: TState, ownProps: TOwnProps) => TStateProps, mapTransformersToProps?: () => TTransformerProps, diff --git a/packages/react/src/context.ts b/packages/react/src/context.ts deleted file mode 100644 index b8f6b12..0000000 --- a/packages/react/src/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -const { Provider, Consumer } = React.createContext(null); - -export { Provider, Consumer }; diff --git a/packages/react/src/hooks.ts b/packages/react/src/hooks.ts new file mode 100644 index 0000000..3dbf587 --- /dev/null +++ b/packages/react/src/hooks.ts @@ -0,0 +1,91 @@ +import { useState, useEffect } from 'react'; +import { Staat } from 'staat'; +import { Reducers } from './types'; + +function shallowEqual(left: T, right: T) { + const leftKeys = Object.keys(left || {}); + const rightKeys = Object.keys(right || {}); + + if (typeof left !== 'object') { + return left === right; + } + + if (left === right) { + return true; + } + + if ( + leftKeys.length !== rightKeys.length || + !leftKeys.every(l => rightKeys.includes(l)) + ) { + return false; + } + + return leftKeys.every( + key => + (left as Record)[key] === + (right as Record)[key], + ); +} + +export function makeUseStaat(staat: Staat) { + return function(selector: (state: TState) => TSubset): TSubset { + const [currentState, setState] = useState( + selector(staat.currentState), + ); + + useEffect(() => { + let didUnsubscribe = false; + + function stateChanged() { + setTimeout(() => { + if (didUnsubscribe) { + return; + } + + const newState = selector(staat.currentState); + + setState((oldState: TSubset) => { + if (!shallowEqual(oldState, newState)) { + return newState; + } + + return oldState; + }); + }); + return Promise.resolve(); + } + + staat.subscribe(stateChanged); + + return () => { + didUnsubscribe = true; + staat.unsubscribe(stateChanged); + }; + }, []); + + return currentState; + }; +} + +export function makeUseReducers( + staat: Staat, +): < + TReducers extends Record TState> +>( + reducers: TReducers, +) => Reducers { + return function(reducers) { + const result = Object.keys(reducers).reduce( + (acc, key) => { + acc[key] = (...args: any[]) => { + const reducer = reducers[key]; + return staat.reduce(reducer, ...args); + }; + return acc; + }, + {} as Record, + ); + return result as Reducers; + }; +} diff --git a/packages/react/src/provider.tsx b/packages/react/src/provider.tsx index b720a53..bd3d991 100644 --- a/packages/react/src/provider.tsx +++ b/packages/react/src/provider.tsx @@ -1,9 +1,9 @@ -import * as React from 'react'; +import React from 'react'; import { Staat } from 'staat'; -import { Provider } from './context'; -export default function makeProvider( - staat: Staat, +export default function makeProvider( + staat: Staat, + { Provider }: React.Context, ): React.ComponentType { return class StaatProvider extends React.Component { private _mounted: boolean; diff --git a/packages/react/src/react.ts b/packages/react/src/react.ts index 40b60ba..e40e28d 100644 --- a/packages/react/src/react.ts +++ b/packages/react/src/react.ts @@ -1,13 +1,22 @@ -import { Staat } from 'staat'; -import { ReactStaat } from './types'; +import React from 'react'; +import { Staat, LegacyStaat } from 'staat'; import makeConnect from './connect'; +import { makeUseStaat, makeUseReducers } from './hooks'; import makeProvider from './provider'; +import { ReactStaat } from './types'; -export function reactStaat( - staat: Staat, +export function reactStaat( + staat: LegacyStaat, +): ReactStaat; +export function reactStaat(staat: Staat): ReactStaat; +export function reactStaat( + staat: Staat | LegacyStaat, ): ReactStaat { + const context = React.createContext(null); return { - Provider: makeProvider(staat), - connect: makeConnect(), + Provider: makeProvider(staat, context), + connect: makeConnect(context), + useStaat: makeUseStaat(staat), + useReducers: makeUseReducers(staat), }; } diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 339bc0a..1b0d418 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -2,8 +2,23 @@ export type ProviderProps = { states: T; }; +export type Reducers = { + [TKey in keyof TReducers]: TReducers[TKey] extends ( + state: TState, + ...args: infer TArgs + ) => unknown + ? (...args: TArgs) => TState + : TReducers[TKey] +}; + export type ReactStaat = { Provider: React.ComponentType; + useStaat(selector: (state: TState) => TSubset): TSubset; + useReducers< + TReducers extends Record TState> + >( + reducers: TReducers, + ): Reducers; connect( mapStateToProps: (state: TState, ownProps: TOwnProps) => TStateProps, mapTransformersToProps?: () => TTransformerProps, diff --git a/packages/react/tsconfig-web.json b/packages/react/tsconfig-web.json index ed21bcf..94ae03e 100644 --- a/packages/react/tsconfig-web.json +++ b/packages/react/tsconfig-web.json @@ -4,4 +4,4 @@ "baseUrl": ".", "declarationDir": "./build", } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8ab9b37..67d94f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -519,11 +519,12 @@ "@babel/helper-regex" "^7.0.0" regexpu-core "^4.1.3" -"@babel/runtime@^7.1.5": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f" +"@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5": + version "7.4.5" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" + integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ== dependencies: - regenerator-runtime "^0.12.0" + regenerator-runtime "^0.13.2" "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2": version "7.2.2" @@ -558,6 +559,15 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@jest/types@^24.8.0": + version "24.8.0" + resolved "https://registry.npmjs.org/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" + integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^12.0.9" + "@lerna/add@^3.8.5": version "3.8.5" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.8.5.tgz#f7dec6b9ce20bc42d64bbb29381b62a918c8c7aa" @@ -1111,12 +1121,61 @@ "@sheerun/mutationobserver-shim@^0.3.2": version "0.3.2" - resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" + resolved "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" + integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== + +"@testing-library/dom@^5.0.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-5.4.0.tgz#49f41c99473286a4102721242bc47571fb7efef0" + integrity sha512-0OQsquNYfbxgqqoGf9RZ9lglXEYgKlhSe+W9UFQGDAvT554Y9PG6hGe0RHYggAXe/GoNPccSsl65nn+qq0cFKw== + dependencies: + "@babel/runtime" "^7.4.5" + "@sheerun/mutationobserver-shim" "^0.3.2" + aria-query "3.0.0" + pretty-format "^24.8.0" + wait-for-expect "^1.2.0" + +"@testing-library/react-hooks@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-1.1.0.tgz#14b6b5c7c3d0e2cb3e55e9cbb248b44321641c64" + integrity sha512-piE/ceQoNf134FFVXBABDbttBJ8eLPD4eg7zIciVJv92RyvoIsBHCvvG8Vd4IG5pyuWYrkLsZTO8ucZBwa4twA== + dependencies: + "@babel/runtime" "^7.4.2" + "@types/react" "^16.8.22" + "@types/react-test-renderer" "^16.8.2" + +"@testing-library/react@^8.0.4": + version "8.0.4" + resolved "https://registry.npmjs.org/@testing-library/react/-/react-8.0.4.tgz#6ed405ba88b625ec53d7cfa78c038a950bafc1fa" + integrity sha512-omm4D00Z0aMaWfPRRP4X6zIaOVb0Kf1Yc1H5VE4id9D0pQRiBcTtmjbN0kZgT8rQGxHhVAuv1NuwFwMTwKzFqg== + dependencies: + "@babel/runtime" "^7.4.5" + "@testing-library/dom" "^5.0.0" "@types/deep-diff@^0.0.31": version "0.0.31" resolved "https://registry.yarnpkg.com/@types/deep-diff/-/deep-diff-0.0.31.tgz#0632b274d1db23cb80f0c1d4e4254c3392fd8219" +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.1" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + +"@types/istanbul-lib-report@*": + version "1.1.1" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c" + integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + "@types/jest@^23.3.10": version "23.3.10" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.10.tgz#4897974cc317bf99d4fe6af1efa15957fa9c94de" @@ -1129,19 +1188,48 @@ version "15.5.8" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.8.tgz#8ae4e0ea205fe95c3901a5a1df7f66495e3a56ce" -"@types/react-dom@^16.0.11": - version "16.0.11" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.11.tgz#bd10ccb0d9260343f4b9a49d4f7a8330a5c1f081" +"@types/react-dom@^16.8.4": + version "16.8.4" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz#7fb7ba368857c7aa0f4e4511c4710ca2c5a12a88" + integrity sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA== dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.7.17": +"@types/react-test-renderer@^16.8.2": + version "16.8.2" + resolved "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.8.2.tgz#ad544b5571ebfc5f182c320376f1431a2b725c5e" + integrity sha512-cm42QR9S9V3aOxLh7Fh7PUqQ8oSfSdnSni30T7TiTmlKvE6aUlo+LhQAzjnZBPriD9vYmgG8MXI8UDK4Nfb7gg== + dependencies: + "@types/react" "*" + +"@types/react@*": version "16.7.17" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.17.tgz#3242e796a1ffbba4f49eae5915a67f4c079504e9" dependencies: "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@^16.8.20": + version "16.8.20" + resolved "https://registry.npmjs.org/@types/react/-/react-16.8.20.tgz#4f633ecbd0a4d56d0ccc50fff6f9321bbcd7d583" + integrity sha512-ZLmI+ubSJpfUIlQuULDDrdyuFQORBuGOvNnMue8HeA0GVrAJbWtZQhcBvnBPNRBI/GrfSfrKPFhthzC2SLEtLQ== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +"@types/react@^16.8.22": + version "16.8.22" + resolved "https://registry.npmjs.org/@types/react/-/react-16.8.22.tgz#7f18bf5ea0c1cad73c46b6b1c804a3ce0eec6d54" + integrity sha512-C3O1yVqk4sUXqWyx0wlys76eQfhrQhiDhDlHBrjER76lR2S2Agiid/KpOU9oCqj1dISStscz7xXz1Cg8+sCQeA== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +"@types/yargs@^12.0.9": + version "12.0.12" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" + integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== + "@webassemblyjs/ast@1.7.11": version "1.7.11" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace" @@ -1419,6 +1507,14 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-query@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" + integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= + dependencies: + ast-types-flow "0.0.7" + commander "^2.11.0" + arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -1515,6 +1611,11 @@ assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" +ast-types-flow@0.0.7: + version "0.0.7" + resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -2223,6 +2324,11 @@ commander@2.17.x, commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" +commander@^2.11.0: + version "2.20.0" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + commander@^2.12.1: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -2807,15 +2913,6 @@ dom-serializer@0: domelementtype "~1.1.1" entities "~1.1.1" -dom-testing-library@^3.13.1: - version "3.16.3" - resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.16.3.tgz#9dbf88d0a0c12f653248ad8f2a5aa17cc66f85e9" - dependencies: - "@babel/runtime" "^7.1.5" - "@sheerun/mutationobserver-shim" "^0.3.2" - pretty-format "^23.6.0" - wait-for-expect "^1.1.0" - dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -6190,6 +6287,16 @@ pretty-format@^23.6.0: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +pretty-format@^24.8.0: + version "24.8.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" + integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== + dependencies: + "@jest/types" "^24.8.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -6384,14 +6491,20 @@ react-deep-force-update@^1.0.0: resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.2.tgz#3d2ae45c2c9040cbb1772be52f8ea1ade6ca2ee1" integrity sha512-WUSQJ4P/wWcusaH+zZmbECOk7H5N2pOIl0vzheeornkIMhu+qrNdGFm0bDZLCb0hSF0jf/kH1SgkNGfBdTc4wA== -react-dom@^16.7.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0.tgz#a17b2a7ca89ee7390bc1ed5eb81783c7461748b8" +react-dom@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" + integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.12.0" + scheduler "^0.13.6" + +react-is@^16.8.4, react-is@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" + integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== react-proxy@^1.1.7: version "1.1.8" @@ -6401,11 +6514,15 @@ react-proxy@^1.1.7: lodash "^4.6.1" react-deep-force-update "^1.0.0" -react-testing-library@^5.4.4: - version "5.4.4" - resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-5.4.4.tgz#3fa787999492be94b228e4540a7211556bf4fd94" +react-test-renderer@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1" + integrity sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw== dependencies: - dom-testing-library "^3.13.1" + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.13.6" react-transform-hmr@^1.0.4: version "1.0.4" @@ -6415,14 +6532,15 @@ react-transform-hmr@^1.0.4: global "^4.3.0" react-proxy "^1.1.7" -react@^16.7.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.7.0.tgz#b674ec396b0a5715873b350446f7ea0802ab6381" +react@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" + integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.12.0" + scheduler "^0.13.6" read-cmd-shim@^1.0.1: version "1.0.1" @@ -6569,9 +6687,10 @@ regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" -regenerator-runtime@^0.12.0: - version "0.12.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" +regenerator-runtime@^0.13.2: + version "0.13.2" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" + integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== regenerator-transform@^0.13.3: version "0.13.3" @@ -6818,9 +6937,10 @@ sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -scheduler@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b" +scheduler@^0.13.6: + version "0.13.6" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -7812,9 +7932,10 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" -wait-for-expect@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.0.tgz#6607375c3f79d32add35cd2c87ce13f351a3d453" +wait-for-expect@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-1.2.0.tgz#fdab6a26e87d2039101db88bff3d8158e5c3e13f" + integrity sha512-EJhKpA+5UHixduMBEGhTFuLuVgQBKWxkFbefOdj2bbk2/OpA5Opsc4aUTGmF+qJ+v3kTGxDRNYwKaT4j6g5n8Q== walker@~1.0.5: version "1.0.7"