diff --git a/__tests__/reductiveContext_test.re b/__tests__/reductiveContext_test.re deleted file mode 100644 index 6756bbb..0000000 --- a/__tests__/reductiveContext_test.re +++ /dev/null @@ -1,309 +0,0 @@ -[@bs.config {jsx: 3}]; -open Jest; -open Expect; -open ReasonHooksTestingLibrary; - -module TestStoreContext = { - type testState = { - counter: int, - content: string, - }; - - type testAction = - | Increment - | Decrement - | AppendA - | AppendB - | Reset; - - let testReducer = (state, action) => - switch (action) { - | Increment => {...state, counter: state.counter + 1} - | Decrement => {...state, counter: state.counter - 1} - | AppendA => {...state, content: state.content ++ "A"} - | AppendB => {...state, content: state.content ++ "B"} - | Reset => {counter: 0, content: ""} - }; - - let appStore = - Reductive.Store.create( - ~reducer=testReducer, - ~preloadedState={counter: 0, content: ""}, - (), - ); - - include ReductiveContext.Make({ - type action = testAction; - type state = testState; - }); -}; - -module App = { - [@react.component] - let make = (~children) => { - - children - ; - }; -}; - -type selectorMockTest = {. "selector": TestStoreContext.testState => int}; -type spyReturnMockValue = string; - -let getResult = container => - container->Testing.Result.result->Testing.Result.current; - -describe("reductiveContext", () => { - open Testing; - open ReactTestingLibrary; - - let options = Options.t(~wrapper=App.make, ()); - beforeEach(() => - Reductive.Store.dispatch(TestStoreContext.appStore, Reset) - ); - - describe("useSelector", () => { - test("sets initial state correctly", () => { - let selector = (state: TestStoreContext.testState) => state.counter; - - let container = - renderHook( - () => TestStoreContext.useSelector(selector), - ~options, - (), - ); - - expect(getResult(container)) |> toEqual(0); - }); - - test("selects the state and re-renders component on store updates", () => { - let selector = (state: TestStoreContext.testState) => state.counter; - - let container = - renderHook( - () => TestStoreContext.useSelector(selector), - ~options, - (), - ); - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - (); - }); - - expect(getResult(container)) |> toEqual(1); - }); - - test("always gives back the latest state", () => { - let selector = (state: TestStoreContext.testState) => state.counter; - let renderedStates: ref(array(int)) = ref([||]); - - module Comp = { - [@react.component] - let make = () => { - let counter = TestStoreContext.useSelector(selector); - renderedStates := Belt.Array.concat(renderedStates^, [|counter|]); -
; - }; - }; - - let element = ; - render(element) |> ignore; - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - Reductive.Store.dispatch(TestStoreContext.appStore, Decrement); - (); - }); - - expect(renderedStates^) |> toEqual([|0, 1, 0|]); - }); - - test("prevents re-render if selected state is referentially equal", () => { - let renderedStates: ref(array(int)) = ref([||]); - let selector = (state: TestStoreContext.testState) => state.counter; - - module Comp = { - [@react.component] - let make = () => { - let counter = TestStoreContext.useSelector(selector); - renderedStates := Belt.Array.concat(renderedStates^, [|counter|]); -
; - }; - }; - - let element = ; - render(element) |> ignore; - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, AppendA); - Reductive.Store.dispatch(TestStoreContext.appStore, AppendB); - (); - }); - - expect(renderedStates^) |> toEqual([|0|]); - }); - - test("correctly updates selected state if selector depends on props", () => { - let renderedStates: ref(array(int)) = ref([||]); - - module Comp = { - [@react.component] - let make = (~prop) => { - let selector = - React.useCallback1( - (s: TestStoreContext.testState) => s.counter + prop, - [|prop|], - ); - - let counter = TestStoreContext.useSelector(selector); - - renderedStates := Belt.Array.concat(renderedStates^, [|counter|]); -
; - }; - }; - - let updateProp = ref(() => ()); - module Parent = { - [@react.component] - let make = () => { - let (prop, dispatch) = React.useReducer((s, _) => s + 1, 0); - updateProp := dispatch; - ; - }; - }; - let element = ; - - render(element) |> ignore; - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); // state.counter - 1, prop - 0, total - 1 - updateProp^(); // state.counter - 1, prop - 1, total - 2 - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); // state.counter - 2, prop - 1, total - 3 - updateProp^(); // state.counter - 2, prop - 2, total - 4 - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); // state.counter - 3, prop - 2, total - 5 - (); - }); - - // changing selector function that depends on props leads to double re-render and duplicated state values - let distinctRenderState = - (renderedStates^) - ->Belt.Array.keepWithIndex((value, index) => - (renderedStates^) - ->Belt.Array.getIndexBy(v => v === value) - ->Belt.Option.getWithDefault(-1) - === index - ); - expect(distinctRenderState) |> toEqual([|0, 1, 2, 3, 4, 5|]); - }); - - test("removes subscription on unmount", () => { - let setupSpy: selectorMockTest => spyReturnMockValue = [%bs.raw - {| - function (obj) { - return jest.spyOn(obj, "selector"); - } - |} - ]; - - let selector = (state: TestStoreContext.testState) => state.counter; - let selectorSpyObject = ref({"selector": selector}); - - let selectStateFromSpy = state => { - let spySelector = (selectorSpyObject^)##selector; - spySelector(state); - }; - - module Comp = { - [@react.component] - let make = () => { - let _ = TestStoreContext.useSelector(selectStateFromSpy); -
; - }; - }; - - let element = ; - render(element) |> unmount() |> ignore; - - // start spying after unmount - let spy = setupSpy(selectorSpyObject^); - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - Reductive.Store.dispatch(TestStoreContext.appStore, Decrement); - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - (); - }); - - let expectSelectorToHaveBeenCalled: spyReturnMockValue => unit = [%bs.raw - {| - function (spy) { - console.log(spy) - return expect(spy).toHaveBeenCalledTimes(0) - } - |} - ]; - - expectSelectorToHaveBeenCalled(spy); - expect(true) |> toEqual(true); - }); - }); - - describe("useStore", () => { - test("gets the store", () => { - let container = - renderHook(() => TestStoreContext.useStore(), ~options, ()); - - expect(getResult(container)) |> toEqual(TestStoreContext.appStore); - }); - - test("re-renders component on store updates", () => { - let container = - renderHook(() => TestStoreContext.useStore(), ~options, ()); - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - (); - }); - - let state = container->getResult->Reductive.Store.getState; - expect(state.counter) |> toEqual(1); - }); - }); - - describe("useDispatch", () => - test("has stable reference", () => { - let dispatchRerenders: ref(int) = ref(0); - let forceRender = ref(() => ()); - - module Comp = { - [@react.component] - let make = () => { - let (_, setState) = React.useState(() => 0); - forceRender := (() => setState(prev => prev + 1)); - - let dispatch = TestStoreContext.useDispatch(); - React.useEffect1( - () => { - dispatchRerenders := dispatchRerenders^ + 1; - None; - }, - [|dispatch|], - ); -
; - }; - }; - - let element = ; - render(element) |> ignore; - - act(() => { - forceRender^(); - forceRender^(); - forceRender^(); - (); - }); - expect(dispatchRerenders^) |> toEqual(1); - }) - ); -}); diff --git a/__tests__/reductiveContext_test.res b/__tests__/reductiveContext_test.res new file mode 100644 index 0000000..0a89644 --- /dev/null +++ b/__tests__/reductiveContext_test.res @@ -0,0 +1,300 @@ +open Jest +open Expect + +// Bindings to react-hooks testing library +module HooksTestingLibrary = { + module Testing = { + module Result = { + @deriving({abstract: light}) + type current<'value> = {current: 'value} + + @deriving({abstract: light}) + type t<'value> = {result: current<'value>} + } + + module Options = { + @deriving({abstract: light}) + type t<'props> = { + @optional + initialProps: 'props, + @optional + wrapper: React.component<{ + "children": React.element, + }>, + } + } + + @module("@testing-library/react-hooks") + external renderHook: ( + @uncurry ('props => 'hook), + ~options: Options.t<'props>=?, + unit, + ) => Result.t<'hook> = "renderHook" + } +} + +open HooksTestingLibrary + +module TestStoreContext = { + type testState = { + counter: int, + content: string, + } + + type testAction = + | Increment + | Decrement + | AppendA + | AppendB + | Reset + + let testReducer = (state, action) => + switch action { + | Increment => {...state, counter: state.counter + 1} + | Decrement => {...state, counter: state.counter - 1} + | AppendA => {...state, content: state.content ++ "A"} + | AppendB => {...state, content: state.content ++ "B"} + | Reset => {counter: 0, content: ""} + } + + let appStore = Reductive.Store.create( + ~reducer=testReducer, + ~preloadedState={counter: 0, content: ""}, + (), + ) + + include ReductiveContext.Make({ + type action = testAction + type state = testState + }) +} + +module App = { + @react.component + let make = (~children) => + + children + +} + +type selectorMockTest = {"selector": TestStoreContext.testState => int} +type spyReturnMockValue = string + +let getResult = container => container->Testing.Result.result->Testing.Result.current + +describe("reductiveContext", () => { + open Testing + open! ReactTestingLibrary + + let options = Options.t(~wrapper=App.make, ()) + beforeEach(() => Reductive.Store.dispatch(TestStoreContext.appStore, Reset)) + + describe("useSelector", () => { + test("sets initial state correctly", () => { + let selector = (state: TestStoreContext.testState) => state.counter + + let container = renderHook(() => TestStoreContext.useSelector(selector), ~options, ()) + + expect(getResult(container)) |> toEqual(0) + }) + + test("selects the state and re-renders component on store updates", () => { + let selector = (state: TestStoreContext.testState) => state.counter + + let container = renderHook(() => TestStoreContext.useSelector(selector), ~options, ()) + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) + () + }) + + expect(getResult(container)) |> toEqual(1) + }) + + test("always gives back the latest state", () => { + let selector = (state: TestStoreContext.testState) => state.counter + let renderedStates: ref> = ref([]) + + module Comp = { + @react.component + let make = () => { + let counter = TestStoreContext.useSelector(selector) + renderedStates := Belt.Array.concat(renderedStates.contents, [counter]) +
+ } + } + + let element = + render(element) |> ignore + + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Increment)) + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Decrement)) + + expect(renderedStates.contents) |> toEqual([0, 1, 0]) + }) + + test("prevents re-render if selected state is referentially equal", () => { + let renderedStates: ref> = ref([]) + let selector = (state: TestStoreContext.testState) => state.counter + + module Comp = { + @react.component + let make = () => { + let counter = TestStoreContext.useSelector(selector) + renderedStates := Belt.Array.concat(renderedStates.contents, [counter]) +
+ } + } + + let element = + render(element) |> ignore + + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, AppendA)) + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, AppendB)) + + expect(renderedStates.contents) |> toEqual([0]) + }) + + test("correctly updates selected state if selector depends on props", () => { + let renderedStates: ref> = ref([]) + + module Comp = { + @react.component + let make = (~prop) => { + let selector = React.useCallback1( + (s: TestStoreContext.testState) => s.counter + prop, + [prop], + ) + + let counter = TestStoreContext.useSelector(selector) + + renderedStates := Belt.Array.concat(renderedStates.contents, [counter]) +
+ } + } + + let updateProp = ref(() => ()) + module Parent = { + @react.component + let make = () => { + let (prop, dispatch) = React.useReducer((s, _) => s + 1, 0) + updateProp := dispatch + + } + } + let element = + + render(element) |> ignore + + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Increment)) // state.counter - 1, prop - 0, total - 1 + act(() => updateProp.contents()) // state.counter - 1, prop - 1, total - 2 + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Increment)) // state.counter - 2, prop - 1, total - 3 + act(() => updateProp.contents()) // state.counter - 2, prop - 2, total - 4 + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Increment)) // state.counter - 3, prop - 2, total - 5 + + // changing selector function that depends on props leads to double re-render and duplicated state values + let distinctRenderState = + renderedStates.contents->Belt.Array.keepWithIndex((value, index) => + renderedStates.contents + ->Belt.Array.getIndexBy(v => v === value) + ->Belt.Option.getWithDefault(-1) === index + ) + expect(distinctRenderState) |> toEqual([0, 1, 2, 3, 4, 5]) + }) + + test("removes subscription on unmount", () => { + let setupSpy: selectorMockTest => spyReturnMockValue = %raw(` + function (obj) { + return jest.spyOn(obj, "selector"); + } + `) + + let selector = (state: TestStoreContext.testState) => state.counter + let selectorSpyObject = ref({"selector": selector}) + + let selectStateFromSpy = state => { + let spySelector = selectorSpyObject.contents["selector"] + spySelector(state) + } + + module Comp = { + @react.component + let make = () => { + let _ = TestStoreContext.useSelector(selectStateFromSpy) +
+ } + } + + let element = + render(element) |> unmount() |> ignore + + // start spying after unmount + let spy = setupSpy(selectorSpyObject.contents) + + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Increment)) + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Decrement)) + act(() => Reductive.Store.dispatch(TestStoreContext.appStore, Increment)) + + let expectSelectorToHaveBeenCalled: spyReturnMockValue => unit = %raw(` + function (spy) { + console.log(spy) + return expect(spy).toHaveBeenCalledTimes(0) + } + `) + + expectSelectorToHaveBeenCalled(spy) + expect(true) |> toEqual(true) + }) + }) + + describe("useStore", () => { + test("gets the store", () => { + let container = renderHook(() => TestStoreContext.useStore(), ~options, ()) + + expect(getResult(container)) |> toEqual(TestStoreContext.appStore) + }) + + test("re-renders component on store updates", () => { + let container = renderHook(() => TestStoreContext.useStore(), ~options, ()) + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) + () + }) + + let state = container->getResult->Reductive.Store.getState + expect(state.counter) |> toEqual(1) + }) + }) + + describe("useDispatch", () => + test("has stable reference", () => { + let dispatchRerenders: ref = ref(0) + let forceRender = ref(() => ()) + + module Comp = { + @react.component + let make = () => { + let (_, setState) = React.useState(() => 0) + forceRender := (() => setState(prev => prev + 1)) + + let dispatch = TestStoreContext.useDispatch() + React.useEffect1(() => { + dispatchRerenders := dispatchRerenders.contents + 1 + None + }, [dispatch]) +
+ } + } + + let element = + render(element) |> ignore + + act(() => forceRender.contents()) + act(() => forceRender.contents()) + act(() => forceRender.contents()) + + expect(dispatchRerenders.contents) |> toEqual(1) + }) + ) +}) diff --git a/bsconfig.json b/bsconfig.json index ead74a2..da911d4 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -1,20 +1,14 @@ { "name": "reductive", "refmt": 3, - "bsc-flags": [ - "-bs-super-errors" - ], + "bsc-flags": ["-bs-super-errors"], "reason": { "react-jsx": 3 }, - "bs-dependencies": [ - "reason-react" - ], + "bs-dependencies": ["@rescript/react"], "bs-dev-dependencies": [ - "immutable-re", "@glennsl/bs-jest", - "bs-react-testing-library", - "reason-hooks-testing-library" + "@rescriptbr/react-testing-library" ], "sources": [ "src", diff --git a/examples/basic/basicEntry.re b/examples/basic/basicEntry.re deleted file mode 100644 index 45149ef..0000000 --- a/examples/basic/basicEntry.re +++ /dev/null @@ -1,48 +0,0 @@ -let unsubscribe = Reductive.Store.subscribe(SimpleStore.store, () => Js.log("store has updated")); - -let dispatch = Reductive.Store.dispatch(SimpleStore.store); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Decrement); - -dispatch(Increment); - -Reductive.Store.subscribe( - SimpleStore.store, - () => Js.log(Reductive.Store.getState(SimpleStore.store)) -); - -/* when replacing reducers, the action and state types must match*/ -Reductive.Store.replaceReducer(SimpleStore.store, SimpleStore.doubleCounter); - -dispatch(Increment); - -unsubscribe(); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -Reductive.Store.subscribe( - ComplexStore.store, - () => Js.log(Reductive.Store.getState(ComplexStore.store)) -); - -Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(A)); - -Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(B)); - -Reductive.Store.dispatch(ComplexStore.store, ComplexStore.CounterAction(Increment)); diff --git a/examples/basic/basicEntry.res b/examples/basic/basicEntry.res new file mode 100644 index 0000000..30694b0 --- /dev/null +++ b/examples/basic/basicEntry.res @@ -0,0 +1,46 @@ +let unsubscribe = Reductive.Store.subscribe(SimpleStore.store, () => Js.log("store has updated")) + +let dispatch = Reductive.Store.dispatch(SimpleStore.store) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Decrement) + +dispatch(Increment) + +let _: unit => unit = Reductive.Store.subscribe(SimpleStore.store, () => + Js.log(Reductive.Store.getState(SimpleStore.store)) +) + +/* when replacing reducers, the action and state types must match */ +Reductive.Store.replaceReducer(SimpleStore.store, SimpleStore.doubleCounter) + +dispatch(Increment) + +unsubscribe() + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +let _: unit => unit = Reductive.Store.subscribe(ComplexStore.store, () => + Js.log(Reductive.Store.getState(ComplexStore.store)) +) + +Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(A)) + +Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(B)) + +Reductive.Store.dispatch(ComplexStore.store, ComplexStore.CounterAction(Increment)) diff --git a/examples/basic/complexStore.re b/examples/basic/complexStore.res similarity index 64% rename from examples/basic/complexStore.re rename to examples/basic/complexStore.res index 024547f..fa43d47 100644 --- a/examples/basic/complexStore.re +++ b/examples/basic/complexStore.res @@ -1,34 +1,33 @@ -open SimpleStore; +open SimpleStore type stringAction = | A - | B; + | B let stringReduce = (state, action) => switch action { | A => state ++ "a" | B => state ++ "b" - }; + } type appActions = | StringAction(stringAction) - | CounterAction(action); + | CounterAction(action) type appState = { counter: int, - notACounter: string -}; + notACounter: string, +} let appReducer = (state, action) => switch action { | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} | CounterAction(action) => {...state, counter: counter(state.counter, action)} - }; + } -let store = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={counter: 0, notACounter: ""}, - ~enhancer=Middleware.logger, - () - ); +let store = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={counter: 0, notACounter: ""}, + ~enhancer=Middleware.logger, + (), +) diff --git a/examples/basic/simpleStore.re b/examples/basic/simpleStore.res similarity index 89% rename from examples/basic/simpleStore.re rename to examples/basic/simpleStore.res index 63b4911..62ee3d2 100644 --- a/examples/basic/simpleStore.re +++ b/examples/basic/simpleStore.res @@ -1,17 +1,17 @@ type action = | Increment - | Decrement; + | Decrement let counter = (state, action) => switch action { | Increment => state + 1 | Decrement => state - 1 - }; + } let doubleCounter = (state, action) => switch action { | Increment => state + 2 | Decrement => state - 2 - }; + } -let store = Reductive.Store.create(~reducer=counter, ~preloadedState=0, ()); +let store = Reductive.Store.create(~reducer=counter, ~preloadedState=0, ()) diff --git a/examples/immutable/appState.re b/examples/immutable/appState.res similarity index 57% rename from examples/immutable/appState.re rename to examples/immutable/appState.res index 2615484..1d0e3cd 100644 --- a/examples/immutable/appState.re +++ b/examples/immutable/appState.res @@ -1,4 +1,4 @@ type appState = { counter: int, - notACounter: string -}; + notACounter: string, +} diff --git a/examples/immutable/immutableEntry.re b/examples/immutable/immutableEntry.re deleted file mode 100644 index 6ae06ed..0000000 --- a/examples/immutable/immutableEntry.re +++ /dev/null @@ -1,7 +0,0 @@ -[@bs.config {jsx: 3}]; -ReactDOMRe.renderToElementWithId( - - - , - "index", -); diff --git a/examples/immutable/immutableEntry.res b/examples/immutable/immutableEntry.res new file mode 100644 index 0000000..83ba12c --- /dev/null +++ b/examples/immutable/immutableEntry.res @@ -0,0 +1,7 @@ +ReactDOM.querySelector("#index")->Belt.Option.forEach( + ReactDOM.render( + + + , + ), +) diff --git a/examples/immutable/immutableRenderer.re b/examples/immutable/immutableRenderer.re deleted file mode 100644 index b883db8..0000000 --- a/examples/immutable/immutableRenderer.re +++ /dev/null @@ -1,50 +0,0 @@ -[@bs.config {jsx: 3}]; - -let stateSelector = state => state; -[@react.component] -let make = () => { - let state = TimeTravelStore.useSelector(stateSelector); - let dispatch = TimeTravelStore.useDispatch(); - - let incrementIfOdd = - (store: Reductive.Store.t(ReduxThunk.thunk(AppState.appState), AppState.appState)) => - switch (Reductive.Store.getState(store)) { - | {counter} when counter mod 2 === 1 => - Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)) - | _ => () - }; - let incrementAsync = (store) => - ignore( - Js.Global.setTimeout( - () => - Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)), - 1000 - ) - ); - -
-
(ReasonReact.string("string: " ++ state.notACounter))
-
(ReasonReact.string("counter: " ++ string_of_int(state.counter)))
- - - - - - - -
; -}; diff --git a/examples/immutable/immutableRenderer.res b/examples/immutable/immutableRenderer.res new file mode 100644 index 0000000..655f337 --- /dev/null +++ b/examples/immutable/immutableRenderer.res @@ -0,0 +1,46 @@ +let stateSelector = state => state +@react.component +let make = () => { + let state = TimeTravelStore.useSelector(stateSelector) + let dispatch = TimeTravelStore.useDispatch() + + let incrementIfOdd = ( + store: Reductive.Store.t, AppState.appState>, + ) => + switch Reductive.Store.getState(store) { + | {counter} if mod(counter, 2) === 1 => + Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)) + | _ => () + } + let incrementAsync = store => + ignore( + Js.Global.setTimeout( + () => Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)), + 1000, + ), + ) + +
+
{React.string("string: " ++ state.notACounter)}
+
{React.string("counter: " ++ string_of_int(state.counter))}
+ + + + + + + +
+} diff --git a/examples/immutable/timeTravelStore.re b/examples/immutable/timeTravelStore.re deleted file mode 100644 index b4d403a..0000000 --- a/examples/immutable/timeTravelStore.re +++ /dev/null @@ -1,94 +0,0 @@ -open SimpleStore; - -type stringAction = - | A - | B; - -let stringReduce = (state, action) => - switch action { - | A => state ++ "a" - | B => state ++ "b" - }; - -type ReduxThunk.thunk(_) += - | StringAction (stringAction) - | CounterAction (action); - -type ReduxThunk.thunk('a) += - | ReplaceState ('a); - -let appReducter = (state: AppState.appState, action) => - switch action { - | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} - | CounterAction(action) => {...state, counter: counter(state.counter, action)} - | ReplaceState(replacedState) => replacedState - | _ => state - }; - -type ReduxThunk.thunk(_) += - | TravelBackward - | TravelForward; - -let past = ref(Immutable.Stack.empty()); - -let future = ref(Immutable.Stack.empty()); - -let goBack = currentState => - switch (Immutable.Stack.first(past^)) { - | Some(lastState) => - future := Immutable.Stack.addFirst(currentState, future^); - if (Immutable.Stack.isNotEmpty(past^)) { - past := Immutable.Stack.removeFirstOrRaise(past^); - }; - lastState; - | None => currentState - }; - -let goForward = currentState => - switch (Immutable.Stack.first(future^)) { - | Some(nextState) => - past := Immutable.Stack.addFirst(currentState, past^); - if (Immutable.Stack.isNotEmpty(future^)) { - future := Immutable.Stack.removeFirstOrRaise(future^); - }; - nextState; - | None => currentState - }; - -let recordHistory = currentState => { - past := Immutable.Stack.addFirst(currentState, past^); - future := Immutable.Stack.empty(); -}; - -let timeTravel = (store, next, action) => { - let currentState = Reductive.Store.getState(store); - switch (action) { - | TravelBackward => next(ReplaceState(goBack(currentState))) - | TravelForward => next(ReplaceState(goForward(currentState))) - | _ => - next(action); - let newState = Reductive.Store.getState(store); - if (currentState !== newState) { - recordHistory(currentState); - }; - }; -}; - -let thunkedLoggedTimeTravelLogger = (store, next) => - Middleware.thunk(store) @@ - Middleware.logger(store) @@ - timeTravel(store) @@ - next; - -let timeTravelStore = - Reductive.Store.create( - ~reducer=appReducter, - ~preloadedState={counter: 0, notACounter: ""}, - ~enhancer=thunkedLoggedTimeTravelLogger, - (), - ); - -include ReductiveContext.Make({ - type action = ReduxThunk.thunk(AppState.appState); - type state = AppState.appState; -}); diff --git a/examples/immutable/timeTravelStore.res b/examples/immutable/timeTravelStore.res new file mode 100644 index 0000000..8acef4e --- /dev/null +++ b/examples/immutable/timeTravelStore.res @@ -0,0 +1,95 @@ +open SimpleStore + +type stringAction = + | A + | B + +let stringReduce = (state, action) => + switch action { + | A => state ++ "a" + | B => state ++ "b" + } + +type ReduxThunk.thunk<_> += + | StringAction(stringAction) + | CounterAction(action) + +type ReduxThunk.thunk<'a> += + | ReplaceState('a) + +let appReducter = (state: AppState.appState, action) => + switch action { + | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} + | CounterAction(action) => {...state, counter: counter(state.counter, action)} + | ReplaceState(replacedState) => replacedState + | _ => state + } + +type ReduxThunk.thunk<_> += + | TravelBackward + | TravelForward + +let past = ref(list{}) + +let future = ref(list{}) + +let goBack = currentState => + switch Belt.List.head(past.contents) { + | Some(lastState) => + future := Belt.List.add(future.contents, currentState) + past := + switch past.contents { + | list{} => list{} + | list{_, ...t} => t + } + lastState + | None => currentState + } + +let goForward = currentState => + switch Belt.List.head(future.contents) { + | Some(nextState) => + past := Belt.List.add(past.contents, currentState) + future := + switch future.contents { + | list{} => list{} + | list{_, ...t} => t + } + + nextState + | None => currentState + } + +let recordHistory = currentState => { + past := Belt.List.add(past.contents, currentState) + future := list{} +} + +let timeTravel = (store, next, action) => { + let currentState = Reductive.Store.getState(store) + switch action { + | TravelBackward => next(ReplaceState(goBack(currentState))) + | TravelForward => next(ReplaceState(goForward(currentState))) + | _ => + next(action) + let newState = Reductive.Store.getState(store) + if currentState !== newState { + recordHistory(currentState) + } + } +} + +let thunkedLoggedTimeTravelLogger = (store, next) => + \"@@"(Middleware.thunk(store), \"@@"(Middleware.logger(store), \"@@"(timeTravel(store), next))) + +let timeTravelStore = Reductive.Store.create( + ~reducer=appReducter, + ~preloadedState={counter: 0, notACounter: ""}, + ~enhancer=thunkedLoggedTimeTravelLogger, + (), +) + +include ReductiveContext.Make({ + type action = ReduxThunk.thunk + type state = AppState.appState +}) diff --git a/examples/middleware.re b/examples/middleware.res similarity index 85% rename from examples/middleware.re rename to examples/middleware.res index fef168b..ef8a460 100644 --- a/examples/middleware.re +++ b/examples/middleware.res @@ -6,18 +6,17 @@ * return value can be used by the middleware that called you (optional) */ -/*** +/* ** * logs the action before dispatching and the new state after. */ let logger = (store, next, action) => { - Js.log(action); - let returnValue = next(action); - Js.log(Reductive.Store.getState(store)); + Js.log(action) + let returnValue = next(action) + Js.log(Reductive.Store.getState(store)) returnValue -}; +} - -/*** +/* ** * middleware that listens for a specific action and calls that function. * Allows for async actions. */ @@ -25,4 +24,4 @@ let thunk = (store, next, action) => switch action { | ReduxThunk.Thunk(func) => func(store) | _ => next(action) - }; + } diff --git a/examples/react/dataRenderer.re b/examples/react/dataRenderer.re deleted file mode 100644 index 6a4b4b7..0000000 --- a/examples/react/dataRenderer.re +++ /dev/null @@ -1,42 +0,0 @@ -let stateSelector = state => state; - -[@react.component] -let make = () => { - let state = ThunkedStore.useSelector(stateSelector); - let dispatch = ThunkedStore.useDispatch(); - - let incrementIfOdd = - (store: Reductive.Store.t(ReduxThunk.thunk(ThunkedStore.appState), ThunkedStore.appState)) => - switch (Reductive.Store.getState(store)) { - | {counter} when counter mod 2 === 1 => - Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)) - | _ => () - }; - let incrementAsync = (store) => - ignore( - Js.Global.setTimeout( - () => Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)), - 1000 - ) - ); - -
-
(ReasonReact.string("string: " ++ state.notACounter))
-
(ReasonReact.string("counter: " ++ string_of_int(state.counter)))
- - - - - -
; -}; diff --git a/examples/react/dataRenderer.res b/examples/react/dataRenderer.res new file mode 100644 index 0000000..6ba09e0 --- /dev/null +++ b/examples/react/dataRenderer.res @@ -0,0 +1,43 @@ +let stateSelector = state => state + +@react.component +let make = () => { + let state = ThunkedStore.useSelector(stateSelector) + let dispatch = ThunkedStore.useDispatch() + + let incrementIfOdd = ( + store: Reductive.Store.t, ThunkedStore.appState>, + ) => + switch Reductive.Store.getState(store) { + | {counter} if mod(counter, 2) === 1 => + Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)) + | _ => () + } + let incrementAsync = store => + ignore( + Js.Global.setTimeout( + () => Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)), + 1000, + ), + ) + +
+
{React.string("string: " ++ state.notACounter)}
+
{React.string("counter: " ++ string_of_int(state.counter))}
+ + + + + +
+} diff --git a/examples/react/reactEntry.re b/examples/react/reactEntry.re deleted file mode 100644 index 2fa98db..0000000 --- a/examples/react/reactEntry.re +++ /dev/null @@ -1,7 +0,0 @@ -[@bs.config {jsx: 3}]; -ReactDOMRe.renderToElementWithId( - - - , - "index", -); diff --git a/examples/react/reactEntry.res b/examples/react/reactEntry.res new file mode 100644 index 0000000..86fa6dc --- /dev/null +++ b/examples/react/reactEntry.res @@ -0,0 +1,5 @@ +ReactDOM.querySelector("#index")->Belt.Option.forEach( + ReactDOM.render( + , + ), +) diff --git a/examples/react/reduxThunk.re b/examples/react/reduxThunk.re deleted file mode 100644 index ddfb302..0000000 --- a/examples/react/reduxThunk.re +++ /dev/null @@ -1,4 +0,0 @@ -type thunk('state) = ..; - -type thunk('state) += - | Thunk ((Reductive.Store.t(thunk('state), 'state) => unit)); diff --git a/examples/react/reduxThunk.res b/examples/react/reduxThunk.res new file mode 100644 index 0000000..ee094fc --- /dev/null +++ b/examples/react/reduxThunk.res @@ -0,0 +1,4 @@ +type thunk<'state> = .. + +type thunk<'state> += + | Thunk(Reductive.Store.t, 'state> => unit) diff --git a/examples/react/thunkedStore.re b/examples/react/thunkedStore.re deleted file mode 100644 index bd8efc1..0000000 --- a/examples/react/thunkedStore.re +++ /dev/null @@ -1,43 +0,0 @@ -[@bs.config {jsx: 3}]; -open SimpleStore; - -type stringAction = - | A - | B; - -let stringReduce = (state, action) => - switch action { - | A => state ++ "a" - | B => state ++ "b" - }; - -type ReduxThunk.thunk(_) += - | StringAction (stringAction) - | CounterAction (action); - -type appState = { - counter: int, - notACounter: string -}; - -let appReducer = (state, action) => - switch action { - | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} - | CounterAction(action) => {...state, counter: counter(state.counter, action)} - | _ => state - }; - -let thunkedLogger = (store, next) => Middleware.thunk(store) @@ Middleware.logger(store) @@ next; - -let appStore = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={counter: 0, notACounter: ""}, - ~enhancer=thunkedLogger, - (), - ); - -include ReductiveContext.Make({ - type state = appState; - type action = ReduxThunk.thunk(appState); -}); diff --git a/examples/react/thunkedStore.res b/examples/react/thunkedStore.res new file mode 100644 index 0000000..eea3f87 --- /dev/null +++ b/examples/react/thunkedStore.res @@ -0,0 +1,42 @@ +open SimpleStore + +type stringAction = + | A + | B + +let stringReduce = (state, action) => + switch action { + | A => state ++ "a" + | B => state ++ "b" + } + +type ReduxThunk.thunk<_> += + | StringAction(stringAction) + | CounterAction(action) + +type appState = { + counter: int, + notACounter: string, +} + +let appReducer = (state, action) => + switch action { + | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} + | CounterAction(action) => {...state, counter: counter(state.counter, action)} + | _ => state + } + +let thunkedLogger = (store, next) => + \"@@"(Middleware.thunk(store), \"@@"(Middleware.logger(store), next)) + +let appStore = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={counter: 0, notACounter: ""}, + ~enhancer=thunkedLogger, + (), +) + +include ReductiveContext.Make({ + type state = appState + type action = ReduxThunk.thunk +}) diff --git a/examples/render-v2/renderEntryV2.re b/examples/render-v2/renderEntryV2.res similarity index 50% rename from examples/render-v2/renderEntryV2.re rename to examples/render-v2/renderEntryV2.res index e9b322c..2c0424f 100644 --- a/examples/render-v2/renderEntryV2.re +++ b/examples/render-v2/renderEntryV2.res @@ -9,39 +9,39 @@ module Counter = { type counterAction = | Increment - | Decrement; + | Decrement let counterReduce = (state, action) => - switch (action) { + switch action { | Increment => state + 1 | Decrement => state - 1 - }; -}; + } +} module Content = { type contentAction = | AppendA - | AppendB; + | AppendB let contentReduce = (state, action) => - switch (action) { + switch action { | AppendA => state ++ "A" | AppendB => state ++ "B" - }; -}; + } +} module App = { type appState = { counter: int, content: string, - }; + } type appAction = | CounterAction(Counter.counterAction) - | StringAction(Content.contentAction); + | StringAction(Content.contentAction) let appReducer = (state, action) => - switch (action) { + switch action { | StringAction(action) => { ...state, content: Content.contentReduce(state.content, action), @@ -50,77 +50,64 @@ module App = { ...state, counter: Counter.counterReduce(state.counter, action), } - }; -}; + } +} module Store = { include ReductiveContext.Make({ - type action = App.appAction; - type state = App.appState; - }); + type action = App.appAction + type state = App.appState + }) - let logger = (store, next) => Middleware.logger(store) @@ next; + let logger = (store, next) => \"@@"(Middleware.logger(store), next) - let store = - Reductive.Store.create( - ~reducer=App.appReducer, - ~preloadedState={counter: 0, content: ""}, - ~enhancer=logger, - (), - ); + let store = Reductive.Store.create( + ~reducer=App.appReducer, + ~preloadedState={counter: 0, content: ""}, + ~enhancer=logger, + (), + ) module Selectors = { - open App; - let state = state => state; - let contentState = state => state.content; - let counterState = state => state.counter; - }; -}; + open App + let state = state => state + let contentState = state => state.content + let counterState = state => state.counter + } +} module ContentComponent = { - [@react.component] + @react.component let make = () => { - let dispatch = Store.useDispatch(); - let state = Store.useSelector(Store.Selectors.contentState); + let dispatch = Store.useDispatch() + let state = Store.useSelector(Store.Selectors.contentState)
-
{ReasonReact.string("Content: " ++ state)}
- - -
; - }; -}; +
{React.string("Content: " ++ state)}
+ + +
+ } +} module CounterComponent = { - [@react.component] + @react.component let make = () => { - let dispatch = Store.useDispatch(); - let state = Store.useSelector(Store.Selectors.counterState); + let dispatch = Store.useDispatch() + let state = Store.useSelector(Store.Selectors.counterState)
{React.string("Counter: " ++ string_of_int(state))}
- - -
; - }; -}; + + +
+ } +} module RenderApp = { - [@react.component] - let make = () => { + @react.component + let make = () =>
- - - - -
; - }; -}; + +
+} -ReactDOMRe.renderToElementWithId(, "index"); +ReactDOM.querySelector("#index")->Belt.Option.forEach(ReactDOM.render()) diff --git a/examples/render/renderEntry.re b/examples/render/renderEntry.re deleted file mode 100644 index 56447e7..0000000 --- a/examples/render/renderEntry.re +++ /dev/null @@ -1,115 +0,0 @@ -[@bs.config {jsx: 3}]; -/* - * Example using multiple components to represent different slices of state. - * Updating the state exposed by one component should not cause the other - * components to also update (visually). Use the React Devtools "highlight - * updates" feature to see this in action. If that proves difficult, then - * try the Chrome devtools Rendering options, enabling "Paint flashing". - */ -type counterAction = - | Increment - | Decrement; - -let counterReduce = (state, action) => - switch (action) { - | Increment => state + 1 - | Decrement => state - 1 - }; - -type stringAction = - | AppendA - | AppendB; - -let stringReduce = (state, action) => - switch (action) { - | AppendA => state ++ "A" - | AppendB => state ++ "B" - }; - -type ReduxThunk.thunk(_) += - | StringAction(stringAction) - | CounterAction(counterAction); - -type appState = { - counter: int, - content: string, -}; - -let appReducer = (state, action) => - switch (action) { - | StringAction(action) => { - ...state, - content: stringReduce(state.content, action), - } - | CounterAction(action) => { - ...state, - counter: counterReduce(state.counter, action), - } - | _ => state - }; - -let thunkedLogger = (store, next) => - Middleware.thunk(store) @@ Middleware.logger(store) @@ next; - -let appStore = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={counter: 0, content: ""}, - ~enhancer=thunkedLogger, - (), - ); - -module AppStore = { - include ReductiveContext.Make({ - type state = appState; - type action = ReduxThunk.thunk(appState); - }); -}; - -let contentSelector = state => state.content; -module StringComponent = { - [@react.component] - let make = () => { - let dispatch = AppStore.useDispatch(); - let state = AppStore.useSelector(contentSelector); - -
-
{ReasonReact.string("Content: " ++ state)}
- - -
; - }; -}; - -let counterSelector = state => state.counter; - -module CounterComponent = { - [@react.component] - let make = () => { - let dispatch = AppStore.useDispatch(); - let state = AppStore.useSelector(counterSelector); - -
-
{ReasonReact.string("Counter: " ++ string_of_int(state))}
- - -
; - }; -}; - -module RenderApp = { - [@react.component] - let make = () => { -
; - }; -}; - -ReactDOMRe.renderToElementWithId(, "index"); diff --git a/examples/render/renderEntry.res b/examples/render/renderEntry.res new file mode 100644 index 0000000..91b76c4 --- /dev/null +++ b/examples/render/renderEntry.res @@ -0,0 +1,108 @@ +/* + * Example using multiple components to represent different slices of state. + * Updating the state exposed by one component should not cause the other + * components to also update (visually). Use the React Devtools "highlight + * updates" feature to see this in action. If that proves difficult, then + * try the Chrome devtools Rendering options, enabling "Paint flashing". + */ +type counterAction = + | Increment + | Decrement + +let counterReduce = (state, action) => + switch action { + | Increment => state + 1 + | Decrement => state - 1 + } + +type stringAction = + | AppendA + | AppendB + +let stringReduce = (state, action) => + switch action { + | AppendA => state ++ "A" + | AppendB => state ++ "B" + } + +type ReduxThunk.thunk<_> += + | StringAction(stringAction) + | CounterAction(counterAction) + +type appState = { + counter: int, + content: string, +} + +let appReducer = (state, action) => + switch action { + | StringAction(action) => { + ...state, + content: stringReduce(state.content, action), + } + | CounterAction(action) => { + ...state, + counter: counterReduce(state.counter, action), + } + | _ => state + } + +let thunkedLogger = (store, next) => + \"@@"(Middleware.thunk(store), \"@@"(Middleware.logger(store), next)) + +let appStore = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={counter: 0, content: ""}, + ~enhancer=thunkedLogger, + (), +) + +module AppStore = { + include ReductiveContext.Make({ + type state = appState + type action = ReduxThunk.thunk + }) +} + +let contentSelector = state => state.content +module StringComponent = { + @react.component + let make = () => { + let dispatch = AppStore.useDispatch() + let state = AppStore.useSelector(contentSelector) + +
+
{React.string("Content: " ++ state)}
+ + +
+ } +} + +let counterSelector = state => state.counter + +module CounterComponent = { + @react.component + let make = () => { + let dispatch = AppStore.useDispatch() + let state = AppStore.useSelector(counterSelector) + +
+
{React.string("Counter: " ++ string_of_int(state))}
+ + +
+ } +} + +module RenderApp = { + @react.component + let make = () => +
+ + + +
+} + +ReactDOM.querySelector("#index")->Belt.Option.forEach(ReactDOM.render()) diff --git a/examples/todomvc/todomvcEntry.re b/examples/todomvc/todomvcEntry.re deleted file mode 100644 index 9fa4b37..0000000 --- a/examples/todomvc/todomvcEntry.re +++ /dev/null @@ -1,338 +0,0 @@ -[@bs.config {jsx: 3}]; -/** - * Example of a todo list, loosely based on the redux example, mixed with - * some code from Jared Forsyth's ReasonReact tutorial, and Cheng Lou's - * todomvc ReasonReact example. - * - * Borrowed CSS from https://github.com/tastejs/todomvc-app-css - */ -type visibility = - | ALL - | ACTIVE - | COMPLETED; - -type appAction = - | AddItem(string) - | Show(visibility) - | UpdateItem(int, string) - | ToggleItem(int) - | ToggleAll(bool) - | ClearItem(int) - | ClearCompleted; - -type todo = { - id: int, - text: string, - completed: bool, -}; - -type appState = { - todos: list(todo), - filter: visibility, -}; - -let lastId = ref(0); -let newItem = text => { - lastId := lastId^ + 1; - {id: lastId^, text, completed: false}; -}; - -let appReducer = (state, action) => - switch (action) { - | AddItem(text) => {...state, todos: [newItem(text), ...state.todos]} - | Show(vis) => {...state, filter: vis} - | UpdateItem(id, text) => { - ...state, - todos: - List.map( - todo => todo.id == id ? {...todo, text} : todo, - state.todos, - ), - } - | ToggleItem(id) => { - ...state, - todos: - List.map( - todo => - todo.id == id ? {...todo, completed: !todo.completed} : todo, - state.todos, - ), - } - | ToggleAll(checked) => { - ...state, - todos: List.map(todo => {...todo, completed: checked}, state.todos), - } - | ClearItem(id) => { - ...state, - todos: List.filter(todo => todo.id != id, state.todos), - } - | ClearCompleted => { - ...state, - todos: List.filter(todo => !todo.completed, state.todos), - } - }; - -let logger = (store, next) => Middleware.logger(store) @@ next; - -let appStore = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={todos: [], filter: ALL}, - ~enhancer=logger, - (), - ); - -module AppStore = { - include ReductiveContext.Make({ - type action = appAction; - type state = appState; - }); -}; - -let valueFromEvent = (evt): string => ReactEvent.Form.target(evt)##value; - -module TodoItem = { - type state = { - editText: string, - editing: bool, - }; - type action = - | Edit - | FinishEdit(string) - | CancelEdit - | Change(string); - - let getInitialState = todo => {editText: todo.text, editing: false}; - - [@react.component] - let make = (~todo, ~dispatch) => { - let editFieldRef = React.useRef(Js.Nullable.null); - - let reducer = (state, action) => { - switch (action) { - | Edit => {editing: true, editText: todo.text} - | FinishEdit(editText) => {editing: false, editText} - | Change(text) => state.editing ? {...state, editText: text} : state - | CancelEdit => {editing: false, editText: todo.text} - }; - }; - let (state, todoItemDispatch) = - React.useReducer(reducer, getInitialState(todo)); - - React.useEffect1( - () => { - Js.log("Inside effect "); - let optionRef = editFieldRef->React.Ref.current->Js.Nullable.toOption; - switch (state.editing, optionRef) { - | (true, Some(field)) => - let node = ReactDOMRe.domElementToObj(field); - Js.log(node); - - ignore(node##focus()); - ignore( - node##setSelectionRange(node##value##length, node##value##length), - ); - | _ => () - }; - - None; - }, - [|state.editing|], - ); - - let submit = () => { - switch (String.trim(state.editText)) { - | "" => dispatch(ClearItem(todo.id)) - | nonEmptyValue => - todoItemDispatch(FinishEdit(nonEmptyValue)); - dispatch(UpdateItem(todo.id, nonEmptyValue)); - }; - }; - - let handleKeyDown = key => { - switch (key) { - | 27 => todoItemDispatch(CancelEdit) - | 13 => submit() - | _ => () - }; - }; - - let className = - [todo.completed ? "completed" : "", state.editing ? "editing" : ""] - |> String.concat(" "); - -
  • -
    - dispatch(ToggleItem(todo.id))} - /> - -
    - submit()} - onChange={event => { - let value = valueFromEvent(event); - todoItemDispatch(Change(value)); - }} - onKeyDown={event => handleKeyDown(ReactEvent.Keyboard.which(event))} - /> -
  • ; - }; -}; - -module TodoList = { - [@react.component] - let make = (~todos, ~dispatch) => { - let todoItems = - List.map( - todo => , - todos, - ); - let todoCount = List.length(todos); - let completedCount = - List.length(List.filter(todo => todo.completed, todos)); - let activeTodoCount = todoCount - completedCount; - -
    - dispatch(ToggleAll(activeTodoCount > 0))} - checked={todoCount > 0 && activeTodoCount === 0} - /> - -
      - {ReasonReact.array(Belt.List.toArray(todoItems))} -
    -
    ; - }; -}; - -module TodoInput = { - [@react.component] - let make = (~onSubmit) => { - let (value, setValue) = React.useState(() => ""); - let handleChange = event => { - let value = valueFromEvent(event); - setValue(_ => value); - }; - let handleKeyDown = event => - if (ReactEvent.Keyboard.key(event) == "Enter") { - onSubmit(value); - setValue(_ => ""); - }; - - ; - }; -}; - -module AddTodo = { - [@react.component] - let make = (~dispatch) => { - dispatch(AddItem(text))} />; - }; -}; - -module VisibleTodoList = { - [@react.component] - let make = (~state: appState, ~dispatch) => { - let todos = - switch (state.filter) { - | ALL => state.todos - | ACTIVE => List.filter(t => !t.completed, state.todos) - | COMPLETED => List.filter(t => t.completed, state.todos) - }; - ; - }; -}; - -module FilterLink = { - [@react.component] - let make = (~filter, ~label, ~state: appState, ~dispatch) => { - let className = filter == state.filter ? "selected" : ""; -
  • - dispatch(Show(filter))}> - {ReasonReact.string(label)} - -
  • ; - }; -}; - -module Footer = { - [@react.component] - let make = (~state: appState, ~dispatch) => { - let completedCount = - List.length(List.filter(todo => todo.completed, state.todos)); - let activeTodoCount = List.length(state.todos) - completedCount; - let activeTodoWord = activeTodoCount === 1 ? "item" : "items"; - let clearButton = - completedCount > 0 - ? - : ReasonReact.null; -
    - - - {ReasonReact.string(string_of_int(activeTodoCount))} - - {ReasonReact.string(" " ++ activeTodoWord ++ " left")} - -
      - - - -
    - clearButton -
    ; - }; -}; - -let stateSelector = state => state; - -module App = { - [@react.component] - let make = () => { - let dispatch = AppStore.useDispatch(); - let state = AppStore.useSelector(stateSelector); - -
    -
    -

    {ReasonReact.string("todos")}

    - -
    - -
    -
    ; - }; -}; - -ReactDOMRe.renderToElementWithId( - , - "TodoApp", -); diff --git a/examples/todomvc/todomvcEntry.res b/examples/todomvc/todomvcEntry.res new file mode 100644 index 0000000..85b33ca --- /dev/null +++ b/examples/todomvc/todomvcEntry.res @@ -0,0 +1,290 @@ +@ocaml.doc(" + * Example of a todo list, loosely based on the redux example, mixed with + * some code from Jared Forsyth's ReasonReact tutorial, and Cheng Lou's + * todomvc ReasonReact example. + * + * Borrowed CSS from https://github.com/tastejs/todomvc-app-css + ") +type visibility = + | ALL + | ACTIVE + | COMPLETED + +type appAction = + | AddItem(string) + | Show(visibility) + | UpdateItem(int, string) + | ToggleItem(int) + | ToggleAll(bool) + | ClearItem(int) + | ClearCompleted + +type todo = { + id: int, + text: string, + completed: bool, +} + +type appState = { + todos: list, + filter: visibility, +} + +let lastId = ref(0) +let newItem = text => { + lastId := lastId.contents + 1 + {id: lastId.contents, text: text, completed: false} +} + +let appReducer = (state, action) => + switch action { + | AddItem(text) => {...state, todos: list{newItem(text), ...state.todos}} + | Show(vis) => {...state, filter: vis} + | UpdateItem(id, text) => { + ...state, + todos: List.map(todo => todo.id == id ? {...todo, text: text} : todo, state.todos), + } + | ToggleItem(id) => { + ...state, + todos: List.map( + todo => todo.id == id ? {...todo, completed: !todo.completed} : todo, + state.todos, + ), + } + | ToggleAll(checked) => { + ...state, + todos: List.map(todo => {...todo, completed: checked}, state.todos), + } + | ClearItem(id) => { + ...state, + todos: List.filter(todo => todo.id != id, state.todos), + } + | ClearCompleted => { + ...state, + todos: List.filter(todo => !todo.completed, state.todos), + } + } + +let logger = (store, next) => \"@@"(Middleware.logger(store), next) + +let appStore = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={todos: list{}, filter: ALL}, + ~enhancer=logger, + (), +) + +module AppStore = { + include ReductiveContext.Make({ + type action = appAction + type state = appState + }) +} + +let valueFromEvent = (evt): string => ReactEvent.Form.target(evt)["value"] + +module TodoItem = { + type state = { + editText: string, + editing: bool, + } + type action = + | Edit + | FinishEdit(string) + | CancelEdit + | Change(string) + + let getInitialState = todo => {editText: todo.text, editing: false} + + @react.component + let make = (~todo, ~dispatch) => { + let editFieldRef = React.useRef(Js.Nullable.null) + + let reducer = (state, action) => + switch action { + | Edit => {editing: true, editText: todo.text} + | FinishEdit(editText) => {editing: false, editText: editText} + | Change(text) => state.editing ? {...state, editText: text} : state + | CancelEdit => {editing: false, editText: todo.text} + } + let (state, todoItemDispatch) = React.useReducer(reducer, getInitialState(todo)) + + React.useEffect1(() => { + Js.log("Inside effect ") + let optionRef = editFieldRef.current->Js.Nullable.toOption + switch (state.editing, optionRef) { + | (true, Some(field)) => + let node = ReactDOM.domElementToObj(field) + Js.log(node) + + ignore(node["focus"]()) + ignore(node["setSelectionRange"](node["value"]["length"], node["value"]["length"])) + | _ => () + } + + None + }, [state.editing]) + + let submit = () => + switch String.trim(state.editText) { + | "" => dispatch(ClearItem(todo.id)) + | nonEmptyValue => + todoItemDispatch(FinishEdit(nonEmptyValue)) + dispatch(UpdateItem(todo.id, nonEmptyValue)) + } + + let handleKeyDown = key => + switch key { + | 27 => todoItemDispatch(CancelEdit) + | 13 => submit() + | _ => () + } + + let className = + list{todo.completed ? "completed" : "", state.editing ? "editing" : ""} |> String.concat(" ") + +
  • +
    + dispatch(ToggleItem(todo.id))} + /> + +
    + submit()} + onChange={event => { + let value = valueFromEvent(event) + todoItemDispatch(Change(value)) + }} + onKeyDown={event => handleKeyDown(ReactEvent.Keyboard.which(event))} + /> +
  • + } +} + +module TodoList = { + @react.component + let make = (~todos, ~dispatch) => { + let todoItems = List.map(todo => , todos) + let todoCount = List.length(todos) + let completedCount = List.length(List.filter(todo => todo.completed, todos)) + let activeTodoCount = todoCount - completedCount + +
    + dispatch(ToggleAll(activeTodoCount > 0))} + checked={todoCount > 0 && activeTodoCount === 0} + /> + +
      {React.array(Belt.List.toArray(todoItems))}
    +
    + } +} + +module TodoInput = { + @react.component + let make = (~onSubmit) => { + let (value, setValue) = React.useState(() => "") + let handleChange = event => { + let value = valueFromEvent(event) + setValue(_ => value) + } + let handleKeyDown = event => + if ReactEvent.Keyboard.key(event) == "Enter" { + onSubmit(value) + setValue(_ => "") + } + + + } +} + +module AddTodo = { + @react.component + let make = (~dispatch) => dispatch(AddItem(text))} /> +} + +module VisibleTodoList = { + @react.component + let make = (~state: appState, ~dispatch) => { + let todos = switch state.filter { + | ALL => state.todos + | ACTIVE => List.filter(t => !t.completed, state.todos) + | COMPLETED => List.filter(t => t.completed, state.todos) + } + + } +} + +module FilterLink = { + @react.component + let make = (~filter, ~label, ~state: appState, ~dispatch) => { + let className = filter == state.filter ? "selected" : "" +
  • dispatch(Show(filter))}> {React.string(label)}
  • + } +} + +module Footer = { + @react.component + let make = (~state: appState, ~dispatch) => { + let completedCount = List.length(List.filter(todo => todo.completed, state.todos)) + let activeTodoCount = List.length(state.todos) - completedCount + let activeTodoWord = activeTodoCount === 1 ? "item" : "items" + let clearButton = + completedCount > 0 + ? + : React.null +
    + + {React.string(string_of_int(activeTodoCount))} + {React.string(" " ++ (activeTodoWord ++ " left"))} + +
      + + + +
    + clearButton +
    + } +} + +let stateSelector = state => state + +module App = { + @react.component + let make = () => { + let dispatch = AppStore.useDispatch() + let state = AppStore.useSelector(stateSelector) + +
    +

    {React.string("todos")}

    + +
    +
    + } +} + +ReactDOM.querySelector("#index")->Belt.Option.forEach( + ReactDOM.render( ), +) diff --git a/package.json b/package.json index c2a8ac6..3d847b6 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "reductive", "version": "2.1.1", - "description": "Redux in Reason", + "description": "Redux in ReScript", "main": "index.js", "scripts": { - "build": "bsb -make-world", - "start": "bsb -make-world -w", - "clean": "bsb -clean-world", + "build": "rescript build", + "start": "rescript build -w", + "clean": "rescript clean", "test": "npm run build && jest --silent", "webpack": "webpack -w" }, @@ -17,26 +17,23 @@ "homepage": "https://github.com/reasonml-community/reductive#readme", "bugs": "https://github.com/reasonml-community/reductive/issues", "keywords": [ - "reason", - "reasonml", - "ocaml", + "rescript", "redux", "state management" ], "author": "rickyvetter", "license": "MIT", "peerDependencies": { - "reason-react": "^0.7.0" + "@rescript/react": "^0.10" }, "devDependencies": { "@glennsl/bs-jest": "^0.4.8", - "bs-platform": "^5.0.6", - "bs-react-testing-library": "^0.5.0", - "immutable-re": "https://github.com/facebookincubator/immutable-re.git", + "@rescript/react": "^0.10", + "@rescriptbr/react-testing-library": "^1.0.1", + "@testing-library/react-hooks": "^7.0.1", "react": "^16.8.6", "react-dom": "^16.8.6", - "reason-hooks-testing-library": "^0.2.0", - "reason-react": "^0.7.0", + "rescript": "^9.1.4", "webpack": "^4.21.0", "webpack-cli": "^3.1.2" }, diff --git a/src/context.bs.js b/src/context.bs.js deleted file mode 100644 index c8e6ef6..0000000 --- a/src/context.bs.js +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by BUCKLESCRIPT VERSION 5.0.6, PLEASE EDIT WITH CARE -'use strict'; - -var React = require("react"); - -function Make(Config) { - var context = React.createContext(Config[/* defaultValue */0]); - var make = context.Provider; - var Provider = /* module */[/* make */make]; - return /* module */[ - /* context */context, - /* Provider */Provider - ]; -} - -exports.Make = Make; -/* react Not a pure module */ diff --git a/src/reductive.bs.js b/src/reductive.bs.js deleted file mode 100644 index 9553bca..0000000 --- a/src/reductive.bs.js +++ /dev/null @@ -1,173 +0,0 @@ -// Generated by BUCKLESCRIPT VERSION 5.0.6, PLEASE EDIT WITH CARE -'use strict'; - -var List = require("bs-platform/lib/js/list.js"); -var Curry = require("bs-platform/lib/js/curry.js"); -var React = require("react"); -var Caml_option = require("bs-platform/lib/js/caml_option.js"); -var Context$Reductive = require("./context.bs.js"); - -function create(reducer, preloadedState, enhancer, param) { - if (enhancer !== undefined) { - return /* record */[ - /* state */preloadedState, - /* reducer */reducer, - /* listeners : [] */0, - /* customDispatcher */enhancer - ]; - } else { - return /* record */[ - /* state */preloadedState, - /* reducer */reducer, - /* listeners : [] */0, - /* customDispatcher */undefined - ]; - } -} - -function unsubscribe(store, listener, param) { - store[/* listeners */2] = List.filter((function (l) { - return listener !== l; - }))(store[/* listeners */2]); - return /* () */0; -} - -function subscribe(store, listener) { - store[/* listeners */2] = /* :: */[ - listener, - store[/* listeners */2] - ]; - return (function (param) { - return unsubscribe(store, listener, param); - }); -} - -function nativeDispatch(store, action) { - store[/* state */0] = Curry._2(store[/* reducer */1], store[/* state */0], action); - return List.iter((function (listener) { - return Curry._1(listener, /* () */0); - }), store[/* listeners */2]); -} - -function dispatch(store, action) { - var match = store[/* customDispatcher */3]; - if (match !== undefined) { - return Curry._3(match, store, (function (param) { - return nativeDispatch(store, param); - }), action); - } else { - return nativeDispatch(store, action); - } -} - -function getState(store) { - return store[/* state */0]; -} - -function replaceReducer(store, reducer) { - store[/* reducer */1] = reducer; - return /* () */0; -} - -var Store = /* module */[ - /* create */create, - /* unsubscribe */unsubscribe, - /* subscribe */subscribe, - /* nativeDispatch */nativeDispatch, - /* dispatch */dispatch, - /* getState */getState, - /* replaceReducer */replaceReducer -]; - -function compose(param) { - return /* () */0; -} - -function combineReducers(param) { - return /* () */0; -} - -function applyMiddleware(param) { - return /* () */0; -} - -function bindActionCreators(actions, dispatch) { - return List.map((function (action, param) { - return Curry._1(dispatch, action); - }), actions); -} - -function Make(funarg) { - var defaultValue = funarg[/* store */0]; - var include = Context$Reductive.Make(/* module */[/* defaultValue */defaultValue]); - var context = include[0]; - var Provider = include[1]; - var Reductive$Make$Provider = function (Props) { - var children = Props.children; - return React.createElement(Provider[/* make */0], { - value: funarg[/* store */0], - children: children - }); - }; - var Provider$1 = /* module */[/* make */Reductive$Make$Provider]; - var useSelector = function (selector) { - var storeFromContext = React.useContext(context); - var match = React.useReducer((function (s, param) { - return s + 1 | 0; - }), 0); - var forceRerender = match[1]; - var latestSelectedState = React.useRef(undefined); - var latestSelector = React.useRef(selector); - React.useLayoutEffect((function () { - latestSelector.current = selector; - var newSelectedState = Curry._1(selector, funarg[/* store */0][/* state */0]); - latestSelectedState.current = Caml_option.some(newSelectedState); - return undefined; - }), /* array */[selector]); - React.useLayoutEffect((function () { - var checkForUpdates = function (param) { - var newSelectedState = Curry._1(selector, funarg[/* store */0][/* state */0]); - var match = latestSelectedState.current; - var hasStateChanged = match !== undefined ? newSelectedState !== Caml_option.valFromOption(match) : true; - if (hasStateChanged) { - latestSelectedState.current = Caml_option.some(newSelectedState); - return Curry._1(forceRerender, /* () */0); - } else { - return 0; - } - }; - return subscribe(storeFromContext, checkForUpdates); - }), /* array */[storeFromContext]); - var currentStoreState = funarg[/* store */0][/* state */0]; - if (latestSelector.current !== selector) { - return Curry._1(selector, currentStoreState); - } else { - var match$1 = latestSelectedState.current; - if (match$1 !== undefined) { - return Caml_option.valFromOption(match$1); - } else { - return Curry._1(selector, currentStoreState); - } - } - }; - var useDispatch = function (param) { - var partial_arg = React.useContext(context); - return (function (param) { - return dispatch(partial_arg, param); - }); - }; - return [ - [context], - Provider$1, - useSelector, - useDispatch - ]; -} - -exports.Store = Store; -exports.Make = Make; -exports.compose = compose; -exports.combineReducers = combineReducers; -exports.applyMiddleware = applyMiddleware; -exports.bindActionCreators = bindActionCreators; -/* react Not a pure module */ diff --git a/src/reductive.re b/src/reductive.re deleted file mode 100644 index a6e33e8..0000000 --- a/src/reductive.re +++ /dev/null @@ -1,164 +0,0 @@ -module Store = { - type t('action, 'state) = { - mutable state: 'state, - mutable reducer: ('state, 'action) => 'state, - mutable listeners: list(unit => unit), - customDispatcher: - option((t('action, 'state), 'action => unit, 'action) => unit), - }; - let create = (~reducer, ~preloadedState, ~enhancer=?, ()) => - switch (preloadedState, enhancer, reducer) { - | (preloadedState, None, reducer) => { - state: preloadedState, - listeners: [], - reducer, - customDispatcher: None, - } - | (preloadedState, Some(enhancer), reducer) => { - state: preloadedState, - listeners: [], - reducer, - customDispatcher: Some(enhancer), - } - }; - let unsubscribe = (store, listener, ()) => - store.listeners = List.filter(l => listener !== l, store.listeners); - let subscribe = (store, listener) => { - store.listeners = [listener, ...store.listeners]; - unsubscribe(store, listener); - }; - let nativeDispatch = (store, action) => { - store.state = store.reducer(store.state, action); - List.iter(listener => listener(), store.listeners); - }; - let dispatch = (store, action) => - switch (store.customDispatcher) { - | Some(customDispatcher) => - customDispatcher(store, nativeDispatch(store), action) - | None => nativeDispatch(store, action) - }; - let getState = store => store.state; - let replaceReducer = (store, reducer) => store.reducer = reducer; -}; - -/* deprecated, use hooks API instead */ -module Lense = { - type state('reductiveState) = { - reductiveState: option('reductiveState), - unsubscribe: option(unit => unit), - }; - type action = - | UpdateState - | AddListener(action => unit); - let createMake = - ( - ~name="Lense", - ~lense: 'state => 'lense, - store: Store.t('action, 'state), - ) => { - let innerComponent = ReasonReact.reducerComponent(name); - let make = - ( - ~component: - ( - ~state: 'lense, - ~dispatch: 'action => unit, - array(ReasonReact.reactElement) - ) => - ReasonReact.component('a, 'b, 'c), - _children: array(ReasonReact.reactElement), - ) - : ReasonReact.component( - state('lense), - ReasonReact.noRetainedProps, - action, - ) => { - ...innerComponent, - initialState: () => { - reductiveState: Some(lense(Store.getState(store))), - unsubscribe: None, - }, - reducer: (action, state) => - switch (action) { - | AddListener(send) => - ReasonReact.Update({ - unsubscribe: - Some(Store.subscribe(store, _ => send(UpdateState))), - reductiveState: Some(lense(Store.getState(store))), - }) - | UpdateState => - lense(Store.getState(store)) - |. Some - |. Belt.Option.eq(state.reductiveState, (a,b) => a === b) - ? ReasonReact.NoUpdate - : ReasonReact.Update({ - ...state, - reductiveState: Some(lense(Store.getState(store))), - }) - }, - didMount: ({send}) => send(AddListener(send)), - willUnmount: ({state}) => - switch (state.unsubscribe) { - | Some(unsubscribe) => unsubscribe() - | None => () - }, - render: ({state}) => - switch (state.reductiveState) { - | None => ReasonReact.null - | Some(state) => - ReasonReact.element( - component(~state, ~dispatch=Store.dispatch(store), [||]), - ) - }, - }; - make; - }; -}; - -/* deprecated, use provider from reductiveContext.re */ -module Provider = { - type state('reductiveState) = Lense.state('reductiveState); - type action = Lense.action; - let createMake = (~name="Provider", store: Store.t('action, 'state)) => - Lense.createMake(~name, ~lense=s => s, store); -}; - -/*** These are all visible apis of Redux that aren't needed in Reason. - * When used, build tools will provide explanation of alternatives. - * (see .rei for those) - */ -let compose = _ => (); - -let combineReducers = _ => (); - -let applyMiddleware = _ => (); - -let bindActionCreators = (actions, dispatch) => - List.map((action, ()) => dispatch(action), actions); - -type store('action, 'state) = Store.t('action, 'state); -type reducer('action, 'state) = ('state, 'action) => 'state; - -type middleware('action, 'state) = - (store('action, 'state), 'action => unit, 'action) => unit; - -type storeCreator('action, 'state) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'state, - ~enhancer: middleware('action, 'state)=?, - unit - ) => - store('action, 'state); - -type storeEnhancer('action, 'state) = - storeCreator('action, 'state) => storeCreator('action, 'state); - -type liftedStoreEnhancer('action, 'state, 'enhancedAction, 'enhancedState) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'enhancedState, - ~enhancer: middleware('enhancedAction, 'enhancedState)=?, - unit - ) => - store('enhancedAction, 'enhancedState); \ No newline at end of file diff --git a/src/reductive.rei b/src/reductive.rei deleted file mode 100644 index a331c35..0000000 --- a/src/reductive.rei +++ /dev/null @@ -1,163 +0,0 @@ -module Store: { - type t('action, 'state); - let create: - ( - ~reducer: ('state, 'action) => 'state, - ~preloadedState: 'state, - ~enhancer: (t('action, 'state), 'action => unit, 'action) => unit=?, - unit - ) => - t('action, 'state); - let unsubscribe: (t('action, 'state), unit => unit, unit) => unit; - let subscribe: (t('action, 'state), unit => unit, unit) => unit; - /* skips all middleware and applies an update directly to the store */ - let nativeDispatch: (t('action, 'state), 'action) => unit; - let dispatch: (t('action, 'state), 'action) => unit; - let getState: t('action, 'state) => 'state; - let replaceReducer: - (t('action, 'state), ('state, 'action) => 'state) => unit; -}; - -module Lense: { - type state('reductiveState); - type action = - | UpdateState - | AddListener(action => unit); - - [@deprecated "Legacy API, prefer the new hooks API with jsx 3"] - let createMake: - ( - ~name: string=?, - ~lense: 'state => 'lense, - Store.t('action, 'state), - ~component: ( - ~state: 'lense, - ~dispatch: 'action => unit, - array(ReasonReact.reactElement) - ) => - ReasonReact.component('a, 'b, 'c), - array(ReasonReact.reactElement) - ) => - ReasonReact.component(state('lense), ReasonReact.noRetainedProps, action); -}; - -module Provider: { - type state('reductiveState) = Lense.state('reductiveState); - type action = Lense.action; - - [@deprecated "Legacy API, prefer the new hooks API with jsx 3"] - let createMake: - ( - ~name: string=?, - Store.t('action, 'state), - ~component: ( - ~state: 'state, - ~dispatch: 'action => unit, - array(ReasonReact.reactElement) - ) => - ReasonReact.component('a, 'b, 'c), - array(ReasonReact.reactElement) - ) => - ReasonReact.component(state('state), ReasonReact.noRetainedProps, action); -}; - -/*** These are all visible apis of Redux that aren't needed in Reason. - * When used, build tools will provide explanation of alternatives. - */ -[@ocaml.deprecated - {| -Use the |> as an infix operator to chain the -result of one function into another: - -`compose(f, g, h)(x)` -in JS goes to -`x |> h |> g |> f` -in Reason. -|} -] -let compose: _ => unit; - -[@ocaml.deprecated - {| -combineReducers uses some introspection to determine -the shape of your state. Instead, consider a declarative pattern like: - -type counterAction = -| Increment -| Decrement; -type stringAction = -| A -| B; -type action = -| StringAction stringAction -| CounterAction counterAction; -type state = {string, counter}; - -let combinedReducer state action => { -| StringAction action => {...state, string: stringReducer state action} -| CounterAction action => {...state, counter: counterReducer state action} -}; - -this pattern gives you full control over the shape of your state. -|} -] -let combineReducers: _ => unit; - -[@ocaml.deprecated - {| -The enhancer attribute in Redux allows you -to provide a custom dispatch method (to perform more -actions before or after the dispatch function). You can simply pass in -a function directly which handles the exact actions you're looking for. - -To chain middlewares you can do something like: - -let thunkedLoggedTimeTravelLogger store next => - Middleware.thunk store @@ - Middleware.logger store @@ - Middleware.timeTravel store @@ - next; -|} -] -let applyMiddleware: _ => unit; - -[@ocaml.deprecated - {| -bindActionCreators is not as useful in Reason, -since action creators are types, not functions. -The code is implemented as: - -let bindActionCreators actions dispatch => -List.map (fun action () => dispatch action) actions; - -Instead - you are free to build the action data type at dispatch time. -|} -] -let bindActionCreators: (list('a), 'a => 'b) => list(unit => 'b); - -type store('action, 'state) = Store.t('action, 'state); -type reducer('action, 'state) = ('state, 'action) => 'state; - -type middleware('action, 'state) = - (store('action, 'state), 'action => unit, 'action) => unit; - -type storeCreator('action, 'state) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'state, - ~enhancer: middleware('action, 'state)=?, - unit - ) => - store('action, 'state); - -type storeEnhancer('action, 'state) = - storeCreator('action, 'state) => storeCreator('action, 'state); - -type liftedStoreEnhancer('action, 'state, 'enhancedAction, 'enhancedState) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'enhancedState, - ~enhancer: middleware('enhancedAction, 'enhancedState)=?, - unit - ) => - store('enhancedAction, 'enhancedState); \ No newline at end of file diff --git a/src/reductive.res b/src/reductive.res new file mode 100644 index 0000000..7a24050 --- /dev/null +++ b/src/reductive.res @@ -0,0 +1,73 @@ +module Store = { + type rec t<'action, 'state> = { + mutable state: 'state, + mutable reducer: ('state, 'action) => 'state, + mutable listeners: list unit>, + customDispatcher: option<(t<'action, 'state>, 'action => unit, 'action) => unit>, + } + let create = (~reducer, ~preloadedState, ~enhancer=?, ()) => + switch (preloadedState, enhancer, reducer) { + | (preloadedState, None, reducer) => { + state: preloadedState, + listeners: list{}, + reducer: reducer, + customDispatcher: None, + } + | (preloadedState, Some(enhancer), reducer) => { + state: preloadedState, + listeners: list{}, + reducer: reducer, + customDispatcher: Some(enhancer), + } + } + let unsubscribe = (store, listener, ()) => + store.listeners = List.filter(l => listener !== l, store.listeners) + let subscribe = (store, listener) => { + store.listeners = list{listener, ...store.listeners} + unsubscribe(store, listener) + } + let nativeDispatch = (store, action) => { + store.state = store.reducer(store.state, action) + List.iter(listener => listener(), store.listeners) + } + let dispatch = (store, action) => + switch store.customDispatcher { + | Some(customDispatcher) => customDispatcher(store, nativeDispatch(store), action) + | None => nativeDispatch(store, action) + } + let getState = store => store.state + let replaceReducer = (store, reducer) => store.reducer = reducer +} + +/* ** These are all visible apis of Redux that aren't needed in Reason. + * When used, build tools will provide explanation of alternatives. + * (see .rei for those) + */ +let compose = _ => () + +let combineReducers = _ => () + +let applyMiddleware = _ => () + +let bindActionCreators = (actions, dispatch) => List.map((action, ()) => dispatch(action), actions) + +type store<'action, 'state> = Store.t<'action, 'state> +type reducer<'action, 'state> = ('state, 'action) => 'state + +type middleware<'action, 'state> = (store<'action, 'state>, 'action => unit, 'action) => unit + +type storeCreator<'action, 'state> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'state, + ~enhancer: middleware<'action, 'state>=?, + unit, +) => store<'action, 'state> + +type storeEnhancer<'action, 'state> = storeCreator<'action, 'state> => storeCreator<'action, 'state> + +type liftedStoreEnhancer<'action, 'state, 'enhancedAction, 'enhancedState> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'enhancedState, + ~enhancer: middleware<'enhancedAction, 'enhancedState>=?, + unit, +) => store<'enhancedAction, 'enhancedState> diff --git a/src/reductive.resi b/src/reductive.resi new file mode 100644 index 0000000..354ec5f --- /dev/null +++ b/src/reductive.resi @@ -0,0 +1,103 @@ +module Store: { + type t<'action, 'state> + let create: ( + ~reducer: ('state, 'action) => 'state, + ~preloadedState: 'state, + ~enhancer: (t<'action, 'state>, 'action => unit, 'action) => unit=?, + unit, + ) => t<'action, 'state> + let unsubscribe: (t<'action, 'state>, unit => unit, unit) => unit + let subscribe: (t<'action, 'state>, unit => unit, unit) => unit + /* skips all middleware and applies an update directly to the store */ + let nativeDispatch: (t<'action, 'state>, 'action) => unit + let dispatch: (t<'action, 'state>, 'action) => unit + let getState: t<'action, 'state> => 'state + let replaceReducer: (t<'action, 'state>, ('state, 'action) => 'state) => unit +} + +/* ** These are all visible apis of Redux that aren't needed in Reason. + * When used, build tools will provide explanation of alternatives. + */ +@ocaml.deprecated(` +Use the |> as an infix operator to chain the +result of one function into another: + +\`compose(f, g, h)(x)\` +in JS goes to +\`x |> h |> g |> f\` +in Reason. +`) +let compose: _ => unit + +@ocaml.deprecated(` +combineReducers uses some introspection to determine +the shape of your state. Instead, consider a declarative pattern like: + +type counterAction = +| Increment +| Decrement; +type stringAction = +| A +| B; +type action = +| StringAction stringAction +| CounterAction counterAction; +type state = {string, counter}; + +let combinedReducer state action => { +| StringAction action => {...state, string: stringReducer state action} +| CounterAction action => {...state, counter: counterReducer state action} +}; + +this pattern gives you full control over the shape of your state. +`) +let combineReducers: _ => unit + +@ocaml.deprecated(` +The enhancer attribute in Redux allows you +to provide a custom dispatch method (to perform more +actions before or after the dispatch function). You can simply pass in +a function directly which handles the exact actions you're looking for. + +To chain middlewares you can do something like: + +let thunkedLoggedTimeTravelLogger store next => + Middleware.thunk store @@ + Middleware.logger store @@ + Middleware.timeTravel store @@ + next; +`) +let applyMiddleware: _ => unit + +@ocaml.deprecated(` +bindActionCreators is not as useful in Reason, +since action creators are types, not functions. +The code is implemented as: + +let bindActionCreators actions dispatch => +List.map (fun action () => dispatch action) actions; + +Instead - you are free to build the action data type at dispatch time. +`) +let bindActionCreators: (list<'a>, 'a => 'b) => list 'b> + +type store<'action, 'state> = Store.t<'action, 'state> +type reducer<'action, 'state> = ('state, 'action) => 'state + +type middleware<'action, 'state> = (store<'action, 'state>, 'action => unit, 'action) => unit + +type storeCreator<'action, 'state> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'state, + ~enhancer: middleware<'action, 'state>=?, + unit, +) => store<'action, 'state> + +type storeEnhancer<'action, 'state> = storeCreator<'action, 'state> => storeCreator<'action, 'state> + +type liftedStoreEnhancer<'action, 'state, 'enhancedAction, 'enhancedState> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'enhancedState, + ~enhancer: middleware<'enhancedAction, 'enhancedState>=?, + unit, +) => store<'enhancedAction, 'enhancedState> diff --git a/src/reductiveContext.re b/src/reductiveContext.re deleted file mode 100644 index ff4199e..0000000 --- a/src/reductiveContext.re +++ /dev/null @@ -1,63 +0,0 @@ -module type Config = { - type state; - type action; -}; - -module Make = (Config: Config) => { - open Subscription; - let storeContext = React.createContext(None); - - module ContextProvider = { - let make = React.Context.provider(storeContext); - let makeProps = - ( - ~value: option(Reductive.Store.t(Config.action, Config.state)), - ~children, - (), - ) => { - "value": value, - "children": children, - }; - }; - - module Provider = { - [@react.component] - let make = (~children, ~store) => { - children ; - }; - }; - - let useStore = () => { - let storeFromContext = React.useContext(storeContext); - switch (storeFromContext) { - | None => - failwith( - "Could not find reductive context value; please ensure the component is wrapped in a ", - ) - | Some(store) => store - }; - }; - - let useSelector = selector => { - let store = useStore(); - - let source = - React.useMemo2( - () => - { - subscribe: Reductive.Store.subscribe(store), - getCurrentValue: () => selector(Reductive.Store.getState(store)), - }, - (selector, store), - ); - - let selectedState = useSubscription(source); - - selectedState; - }; - - let useDispatch = () => { - let store = useStore(); - React.useCallback1(Reductive.Store.dispatch(store), [|store|]); - }; -}; diff --git a/src/reductiveContext.rei b/src/reductiveContext.rei deleted file mode 100644 index 7c11f5c..0000000 --- a/src/reductiveContext.rei +++ /dev/null @@ -1,34 +0,0 @@ -module type Config = { - type state; - type action; -}; -module Make: - (Config: Config) => - { - module Provider: { - [@bs.obj] - external makeProps: - ( - ~children: 'children, - ~store: Reductive.Store.t(Config.action, Config.state), - ~key: string=?, - unit - ) => - { - . - "children": 'children, - "store": Reductive.Store.t(Config.action, Config.state), - } = - ""; - let make: - { - . - "children": React.element, - "store": Reductive.Store.t(Config.action, Config.state), - } => - React.element; - }; - let useSelector: (Config.state => 'selectedState) => 'selectedState; - let useDispatch: (unit, Config.action) => unit; - let useStore: unit => Reductive.Store.t(Config.action, Config.state); - }; diff --git a/src/reductiveContext.res b/src/reductiveContext.res new file mode 100644 index 0000000..1399703 --- /dev/null +++ b/src/reductiveContext.res @@ -0,0 +1,57 @@ +module type Config = { + type state + type action +} + +module Make = (Config: Config) => { + open Subscription + let storeContext = React.createContext(None) + + module ContextProvider = { + let make = React.Context.provider(storeContext) + let makeProps = ( + ~value: option>, + ~children, + (), + ) => + { + "value": value, + "children": children, + } + } + + module Provider = { + @react.component + let make = (~children, ~store) => + children + } + + let useStore = () => { + let storeFromContext = React.useContext(storeContext) + switch storeFromContext { + | None => + failwith( + "Could not find reductive context value; please ensure the component is wrapped in a ", + ) + | Some(store) => store + } + } + + let useSelector = selector => { + let store = useStore() + + let source = React.useMemo2(() => { + subscribe: Reductive.Store.subscribe(store), + getCurrentValue: () => selector(Reductive.Store.getState(store)), + }, (selector, store)) + + let selectedState = useSubscription(source) + + selectedState + } + + let useDispatch = () => { + let store = useStore() + React.useCallback1(Reductive.Store.dispatch(store), [store]) + } +} diff --git a/src/reductiveContext.resi b/src/reductiveContext.resi new file mode 100644 index 0000000..e3193a8 --- /dev/null +++ b/src/reductiveContext.resi @@ -0,0 +1,23 @@ +module type Config = { + type state + type action +} +module Make: (Config: Config) => +{ + module Provider: { + @obj + external makeProps: ( + ~children: 'children, + ~store: Reductive.Store.t, + ~key: string=?, + unit, + ) => {"children": 'children, "store": Reductive.Store.t} = "" + let make: { + "children": React.element, + "store": Reductive.Store.t, + } => React.element + } + let useSelector: (Config.state => 'selectedState) => 'selectedState + let useDispatch: (unit, Config.action) => unit + let useStore: unit => Reductive.Store.t +} diff --git a/src/subscription.re b/src/subscription.re deleted file mode 100644 index cffab86..0000000 --- a/src/subscription.re +++ /dev/null @@ -1,13 +0,0 @@ -[@bs.deriving {jsConverter: newType}] -type source('a) = { - subscribe: (unit => unit, unit) => unit, - getCurrentValue: unit => 'a, -}; - -[@bs.module "use-subscription"] -external useSubscriptionJs: abs_source('a) => 'a = "useSubscription"; - -let useSubscription = source => { - let sourceJs = React.useMemo1(() => sourceToJs(source), [|source|]); - useSubscriptionJs(sourceJs); -}; diff --git a/src/subscription.res b/src/subscription.res new file mode 100644 index 0000000..1a7a932 --- /dev/null +++ b/src/subscription.res @@ -0,0 +1,13 @@ +@deriving({jsConverter: newType}) +type source<'a> = { + subscribe: (unit => unit, unit) => unit, + getCurrentValue: unit => 'a, +} + +@module("use-subscription") +external useSubscriptionJs: abs_source<'a> => 'a = "useSubscription" + +let useSubscription = source => { + let sourceJs = React.useMemo1(() => sourceToJs(source), [source]) + useSubscriptionJs(sourceJs) +} diff --git a/yarn.lock b/yarn.lock index eec0af9..6e6c2f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,13 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.10.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + "@babel/core@^7.1.0": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30" @@ -68,6 +75,11 @@ dependencies: "@babel/types" "^7.4.4" +"@babel/helper-validator-identifier@^7.14.5": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz#32be33a756f29e278a0d644fa08a2c9e0f88a34c" + integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow== + "@babel/helpers@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.5.tgz#63908d2a73942229d1e6685bc2a0e730dde3b75e" @@ -86,6 +98,15 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b" @@ -98,12 +119,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/runtime@^7.3.1", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.3": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" - integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== +"@babel/runtime-corejs3@^7.10.2": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.14.8.tgz#68539e0129f13eb1ed9a9aa273d3542b93c88384" + integrity sha512-4dMD5QRBkumn45oweR0SxoNtt15oz3BUBAQ8cIx7HJqZTtE8zjpM0My8aHJHVnyf4XfRg6DNzaE1080WLBiC1w== dependencies: - regenerator-runtime "^0.13.2" + core-js-pure "^3.15.0" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446" + integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg== + dependencies: + regenerator-runtime "^0.13.4" "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": version "7.4.4" @@ -301,19 +330,67 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@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" - integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== - -"@testing-library/react-hooks@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-1.1.0.tgz#14b6b5c7c3d0e2cb3e55e9cbb248b44321641c64" - integrity sha512-piE/ceQoNf134FFVXBABDbttBJ8eLPD4eg7zIciVJv92RyvoIsBHCvvG8Vd4IG5pyuWYrkLsZTO8ucZBwa4twA== +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== dependencies: - "@babel/runtime" "^7.4.2" - "@types/react" "^16.8.22" - "@types/react-test-renderer" "^16.8.2" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@rescript/react@^0.10": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@rescript/react/-/react-0.10.3.tgz#a2a8bed6b017940ec26c2154764b350f50348889" + integrity sha512-Lf9rzrR3bQPKJjOK3PBRa/B3xrJ7CqQ1HYr9VHPVxJidarIJJFZBhj0Dg1uZURX+Wg/xiP0PHFxXmdj2bK8Vxw== + +"@rescriptbr/react-testing-library@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@rescriptbr/react-testing-library/-/react-testing-library-1.0.1.tgz#23815a778dc4834fa73799f47851b52cb2fd7f92" + integrity sha512-9hajpAGvdQZSGqnrVwNI3KhIiP5ewkWf1ff1/nLupOkH+Vg3eEG3grviPM/IjEh8h227hV8UDuDphdLDYuoajw== + dependencies: + "@testing-library/react" "^11.1.0" + bs-dom-testing-library "^0.7.0" + +"@testing-library/dom@^7.26.3", "@testing-library/dom@^7.28.1": + version "7.31.2" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a" + integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^4.2.2" + chalk "^4.1.0" + dom-accessibility-api "^0.5.6" + lz-string "^1.4.4" + pretty-format "^26.6.2" + +"@testing-library/react-hooks@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.1.tgz#8429d8bf55bfe82e486bd582dd06457c2464900a" + integrity sha512-bpEQ2SHSBSzBmfJ437NmnP+oArQ7aVmmULiAp6Ag2rtyLBLPNFSMmgltUbFGmQOJdPWo4Ub31kpUC5T46zXNwQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + react-error-boundary "^3.1.0" + +"@testing-library/react@^11.1.0": + version "11.2.7" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818" + integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" + +"@types/aria-query@^4.2.0": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" + integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== "@types/babel__core@^7.1.0": version "7.1.2" @@ -368,25 +445,50 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/node@*": + version "16.4.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.7.tgz#f7afa78769d4b477f5092d7c3468e2e8653d779c" + integrity sha512-aDDY54sst8sx47CWT6QQqIZp45yURq4dic0+HCYfYNcY5Ejlb/CLmFnRLfy3wQuYafOeh3lB/DAKaqRKBtcZmA== + "@types/prop-types@*": - version "15.7.1" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6" - integrity sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg== + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@types/react-test-renderer@^16.8.2": - version "16.9.0" - resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.0.tgz#d60f530ecf4c906721511603cca711b4fa830d41" - integrity sha512-bN5EyjtuTY35xX7N5j0KP1vg5MpUXHpFTX6tGsqkNOthjNvet4VQOYRxFh+NT5cDSJrATmAFK9NLeYZ4mp/o0Q== +"@types/react-dom@>=16.9.0": + version "17.0.9" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" + integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg== + dependencies: + "@types/react" "*" + +"@types/react-test-renderer@>=16.9.0": + version "17.0.1" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b" + integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw== dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.8.22": - version "16.9.2" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.2.tgz#6d1765431a1ad1877979013906731aae373de268" - integrity sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg== +"@types/react@*", "@types/react@>=16.9.0": + version "17.0.15" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.15.tgz#c7533dc38025677e312606502df7656a6ea626d0" + integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw== dependencies: "@types/prop-types" "*" - csstype "^2.2.0" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== "@types/stack-utils@^1.0.1": version "1.0.1" @@ -405,6 +507,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^15.0.0": + version "15.0.14" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" + integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + dependencies: + "@types/yargs-parser" "*" + "@webassemblyjs/ast@1.7.8": version "1.7.8" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.8.tgz#f31f480debeef957f01b623f27eabc695fa4fe8f" @@ -643,6 +752,11 @@ ansi-regex@^4.0.0, ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -650,6 +764,13 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -671,6 +792,14 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -951,30 +1080,12 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -bs-dom-testing-library@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/bs-dom-testing-library/-/bs-dom-testing-library-0.4.1.tgz#46fd1fea65465edd799f1e7dd1596a633ef44b05" - integrity sha512-UxpDl6Ain7ZvjieWFeeJZjmcaZOVe8KAKo9Olnji1kF5ne2w9x+b8AzSC0z20OKhpP3/SiiAuRnGNiO81NqI6A== - dependencies: - dom-testing-library "^3.19.0" - -bs-platform@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/bs-platform/-/bs-platform-2.2.3.tgz#d905ae10a5f3621e6a739041dfa0b58483a2174f" - integrity sha1-2QWuEKXzYh5qc5BB36C1hIOiF08= - -bs-platform@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/bs-platform/-/bs-platform-5.0.6.tgz#88c13041fb020479800de3d82c680bf971091425" - integrity sha512-6Boa2VEcWJp2WJr38L7bp3J929nYha7gDarjxb070jWzgfPJ/WbzjipmSfnu2eqqk1MfjEIpBipbPz6n1NISwA== - -bs-react-testing-library@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/bs-react-testing-library/-/bs-react-testing-library-0.5.0.tgz#3906666ffc6dfd22ff25ae01bfa8f5ba65c30db0" - integrity sha512-EmISyRVFXCipeaaymKKv34kf8Cnn40hg3oRU2E36VLvou6XznkvMtAaWqYIGupp1+cnWkFNXhQAMZJNwSd9JiA== +bs-dom-testing-library@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/bs-dom-testing-library/-/bs-dom-testing-library-0.7.0.tgz#6656561d471e99be8caa452d757c074c377f10df" + integrity sha512-bYbegL2TZ55DGWwdoeB3KugrJUIhIyZvMQablQ09hx9QD1Pzm2Sd6Rn17K2uHdoWj7kBVoSy5muTQOydzNmfWA== dependencies: - bs-dom-testing-library "^0.4.1" - react-testing-library "^5.2.0" + "@testing-library/dom" "^7.26.3" bser@^2.0.0: version "2.1.0" @@ -1086,6 +1197,14 @@ chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chokidar@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" @@ -1183,11 +1302,23 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1271,6 +1402,11 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +core-js-pure@^3.15.0: + version "3.15.2" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.15.2.tgz#c8e0874822705f3385d3197af9348f7c9ae2e3ce" + integrity sha512-D42L7RYh1J2grW8ttxoY1+17Y4wXZeKe7uyplAI3FkNQyI5OgBIAjUfFiTPfL1rs0qLpxaabITNbjKl1Sp82tA== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1347,10 +1483,10 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" -csstype@^2.2.0: - version "2.6.6" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.6.tgz#c34f8226a94bbb10c32cc0d714afdf942291fc41" - integrity sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg== +csstype@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" + integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== cyclist@~0.2.2: version "0.2.2" @@ -1502,15 +1638,10 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dom-testing-library@^3.13.1, dom-testing-library@^3.19.0: - version "3.19.4" - resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.19.4.tgz#f5b737f59ee9749a4568fa353f1f59be97c888c3" - integrity sha512-GJOx8CLpnkvM3takILOsld/itUUc9+7Qh6caN1Spj6+9jIgNPY36fsvoH7sEgYokC0lBRdttO7G7fIFYCXlmcA== - dependencies: - "@babel/runtime" "^7.4.3" - "@sheerun/mutationobserver-shim" "^0.3.2" - pretty-format "^24.7.0" - wait-for-expect "^1.1.1" +dom-accessibility-api@^0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz#3f5d43b52c7a3bd68b5fb63fa47b4e4c1fdf65a9" + integrity sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw== domain-browser@^1.1.1: version "1.2.0" @@ -2072,6 +2203,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" @@ -2202,12 +2338,6 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -"immutable-re@https://github.com/facebookincubator/immutable-re.git": - version "0.0.15" - resolved "https://github.com/facebookincubator/immutable-re.git#d55f65f7fc638ac17cfdd8064a0401d4cde5aad2" - dependencies: - bs-platform "^2.2.1" - import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -3093,6 +3223,11 @@ lru-cache@^4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -3828,7 +3963,7 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -pretty-format@^24.7.0, pretty-format@^24.9.0: +pretty-format@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== @@ -3838,6 +3973,16 @@ pretty-format@^24.7.0, pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -3985,7 +4130,7 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@>=16.8.1, react-dom@^16.8.6: +react-dom@^16.8.6: version "16.9.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.9.0.tgz#5e65527a5e26f22ae3701131bcccaee9fb0d3962" integrity sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ== @@ -3995,30 +4140,24 @@ react-dom@>=16.8.1, react-dom@^16.8.6: prop-types "^15.6.2" scheduler "^0.15.0" -react-is@^16.8.4, react-is@^16.9.0: +react-error-boundary@^3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.3.tgz#276bfa05de8ac17b863587c9e0647522c25e2a0b" + integrity sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA== + dependencies: + "@babel/runtime" "^7.12.5" + +react-is@^16.8.4: version "16.9.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw== -react-test-renderer@^16.8.6: - version "16.9.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.9.0.tgz#7ed657a374af47af88f66f33a3ef99c9610c8ae9" - integrity sha512-R62stB73qZyhrJo7wmCW9jgl/07ai+YzvouvCXIJLBkRlRqLx4j9RqcLEAfNfU3OxTGucqR2Whmn3/Aad6L3hQ== - dependencies: - object-assign "^4.1.1" - prop-types "^15.6.2" - react-is "^16.9.0" - scheduler "^0.15.0" - -react-testing-library@^5.2.0: - version "5.9.0" - resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-5.9.0.tgz#e1c8a586d2f2cbd5f0035474ca90258eef1fece6" - integrity sha512-T303PJZvrLKeeiPpjmMD1wxVpzEg9yI0qteH/cUvpFqNHOzPe3yN+Pu+jo9JlxuTMvVGPAmCAcgZ3sEtEDpJUQ== - dependencies: - "@babel/runtime" "^7.3.1" - dom-testing-library "^3.13.1" +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react@>=16.8.1, react@^16.8.6: +react@^16.8.6: version "16.9.0" resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa" integrity sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w== @@ -4074,26 +4213,10 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -reason-hooks-testing-library@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/reason-hooks-testing-library/-/reason-hooks-testing-library-0.2.0.tgz#6d6c2cd5e8725ab74ebe8c6f25f484afd34b65f6" - integrity sha512-lWWszdGYdyNmiPthq2OJKUu1HVufxZDOBaamedDk274KD/Bl1HDSo+X3QIeLAoyJHduboUPuhitdYpiINHLL+g== - dependencies: - "@testing-library/react-hooks" "^1.1.0" - react-test-renderer "^16.8.6" - -reason-react@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/reason-react/-/reason-react-0.7.0.tgz#46a975c321e81cd51310d7b1a02418ca7667b0d6" - integrity sha512-czR/f0lY5iyLCki9gwftOFF5Zs40l7ZSFmpGK/Z6hx2jBVeFDmIiXB8bAQW/cO6IvtuEt97OmsYueiuOYG9XjQ== - dependencies: - react ">=16.8.1" - react-dom ">=16.8.1" - -regenerator-runtime@^0.13.2: - version "0.13.3" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" - integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -4175,6 +4298,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +rescript@^9.1.4: + version "9.1.4" + resolved "https://registry.yarnpkg.com/rescript/-/rescript-9.1.4.tgz#1eb126f98d6c16942c0bf0df67c050198e580515" + integrity sha512-aXANK4IqecJzdnDpJUsU6pxMViCR5ogAxzuqS0mOr8TloMnzAjJFu63fjD6LCkWrKAhlMkFFzQvVQYaAaVkFXw== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -4661,6 +4789,13 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -4976,11 +5111,6 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" -wait-for-expect@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.3.0.tgz#65241ce355425f907f5d127bdb5e72c412ff830c" - integrity sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA== - walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"