From 4c859cdc95789dc62830d5202b619a79ef441f5e Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Mon, 31 Dec 2018 16:20:08 -0800 Subject: [PATCH 1/2] Add test case reproducing failure --- lib/Reactify.re | 3 +- lib/Reactify.rei | 1 + test/HooksUseStateTest.re | 58 +++++++++++++++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/Reactify.re b/lib/Reactify.re index 37342f3..3feef9e 100644 --- a/lib/Reactify.re +++ b/lib/Reactify.re @@ -518,6 +518,7 @@ module Make = (ReconcilerImpl: Reconciler) => { }; module State = State; +module Effects = Effects; module Event = Event; module Utility = Utility; -module Object = Object; \ No newline at end of file +module Object = Object; diff --git a/lib/Reactify.rei b/lib/Reactify.rei index e9af404..18a3329 100644 --- a/lib/Reactify.rei +++ b/lib/Reactify.rei @@ -7,6 +7,7 @@ module Make: type node = ReconcilerImpl.node and type primitives = ReconcilerImpl.primitives; +module Effects = Effects; module State = State; module Event = Event; module Utility = Utility; diff --git a/test/HooksUseStateTest.re b/test/HooksUseStateTest.re index 2fd154b..c60a0d4 100644 --- a/test/HooksUseStateTest.re +++ b/test/HooksUseStateTest.re @@ -5,6 +5,8 @@ open TestReconciler; open TestUtility; module Event = Reactify.Event; +module Effects = Reactify.Effects; + /* Use our Reconciler to create our own instance */ module TestReact = Reactify.Make(TestReconciler); @@ -32,11 +34,21 @@ module ComponentWithState = ( ) ); +module ComponentThatRendersChildren = ( + val component((render, ~children, ()) => + render(() => { + ...children + }, ~children)) +); + type renderOption = /* | Nothing */ | RenderAComponentWithState | RenderAComponent(int); +type innerEventCallback = int => unit; + + test("useState", () => { test("useState uses initial state", () => { let rootNode = createRootNode(); @@ -51,19 +63,19 @@ test("useState", () => { }); module ComponentThatUpdatesState = ( - val component((render, ~children, ~event: Event.t(int), ()) => + val component((render, ~condition=Effects.Always, ~children, ~event: Event.t(int), ()) => render( () => { /* Hooks */ let (s, setS) = useState(2); /* End hooks */ - useEffect(() => { + useEffect(~condition, () => { let unsubscribe = Event.subscribe(event, v => setS(v)); () => unsubscribe(); }); - ; + ; }, ~children, ) @@ -157,13 +169,13 @@ test("useState", () => { }); module ComponentThatUpdatesStateAndRendersChildren = ( - val component((render, ~children, ~event: Event.t(int), ()) => + val component((render, ~condition=Effects.Always, ~children, ~event: Event.t(int), ()) => render( () => { /* Hooks */ let (s, setS) = useState(2); - useEffect(() => { + useEffect(~condition, () => { let unsubscribe = Event.subscribe(event, v => setS(v)); () => unsubscribe(); }); @@ -176,7 +188,7 @@ test("useState", () => { ) ); - test("nested state works as expected", () => { + test("nested state works", () => { let rootNode = createRootNode(); let container = createContainer(rootNode); @@ -210,6 +222,40 @@ test("useState", () => { validateStructure(rootNode, expectedStructure); }); + test("regression test: nested state w/ long-lived handle to setState", () => { + let rootNode = createRootNode(); + let container = createContainer(rootNode); + + let outerEvent = Event.create(); + let innerEvent = Event.create(); + + updateContainer( + container, + + + , + ); + + let expectedStructure: tree(primitives) = + TreeNode(Root, [TreeNode(A(2), [TreeLeaf(A(2))])]); + validateStructure(rootNode, expectedStructure); + + Event.dispatch(outerEvent, 5); + let expectedStructure: tree(primitives) = + TreeNode(Root, [TreeNode(A(5), [TreeLeaf(A(2))])]); + validateStructure(rootNode, expectedStructure); + + Event.dispatch(innerEvent, 6); + let expectedStructure: tree(primitives) = + TreeNode(Root, [TreeNode(A(5), [TreeLeaf(A(6))])]); + validateStructure(rootNode, expectedStructure); + + Event.dispatch(outerEvent, 7); + let expectedStructure: tree(primitives) = + TreeNode(Root, [TreeNode(A(7), [TreeLeaf(A(6))])]); + validateStructure(rootNode, expectedStructure); + }); + module ComponentThatWrapsEitherPrimitiveOrComponent = ( val component((render, ~children, ~event: Event.t(renderOption), ()) => render( From 64dfe5e7848a8d59c98bc22188a9271f387059da Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Mon, 31 Dec 2018 16:36:26 -0800 Subject: [PATCH 2/2] Clean up tests --- test/HooksUseStateTest.re | 10 ---------- test/StateTest.ts | 28 ---------------------------- 2 files changed, 38 deletions(-) delete mode 100644 test/StateTest.ts diff --git a/test/HooksUseStateTest.re b/test/HooksUseStateTest.re index c60a0d4..6b43cc9 100644 --- a/test/HooksUseStateTest.re +++ b/test/HooksUseStateTest.re @@ -34,21 +34,11 @@ module ComponentWithState = ( ) ); -module ComponentThatRendersChildren = ( - val component((render, ~children, ()) => - render(() => { - ...children - }, ~children)) -); - type renderOption = /* | Nothing */ | RenderAComponentWithState | RenderAComponent(int); -type innerEventCallback = int => unit; - - test("useState", () => { test("useState uses initial state", () => { let rootNode = createRootNode(); diff --git a/test/StateTest.ts b/test/StateTest.ts deleted file mode 100644 index eb93dca..0000000 --- a/test/StateTest.ts +++ /dev/null @@ -1,28 +0,0 @@ -module State = Reactify.State; - -test("Object conversion works with ints", () => { - let i = 1; - let stateI = Object.to_object(i); - let rehydratedI = Object.of_object(stateI); - assert(i == rehydratedI); -}); - -test("Object conversion works with tuples", () => { - let p = (1, "a"); - let stateP = Object.to_object(p); - let rehydratedP = Object.of_object(stateP); - assert(p == rehydratedP); -}); - -test("Object can be used to create list of different types", () => { - let stateList: list(State.t) = []; - - let stateList = [Object.to_object(1), ...stateList]; - let stateList = [Object.to_object(("a", "b")), ...stateList]; - - let firstElement = List.nth(stateList, 0); - assert(Object.of_object(firstElement) == ("a", "b")); - - let lastElement = List.nth(stateList, 1); - assert(Object.of_object(lastElement) == 1); -});