diff --git a/src/UI/Node.re b/src/UI/Node.re index eedcf44ba..5901623b7 100644 --- a/src/UI/Node.re +++ b/src/UI/Node.re @@ -8,6 +8,8 @@ open Revery_Math; module UniqueId = Revery_Core.UniqueId.Make({}); +type callback = unit => unit; + class node ('a) (()) = { as _this; val _children: ref(list(node('a))) = ref([]); @@ -110,6 +112,7 @@ class node ('a) (()) = { List.filter(c => c#getInternalId() != n#getInternalId(), _children^); n#_setParent(None); }; + pub firstChild = () => List.hd(_children^); pub getParent = () => _parent^; pub getChildren = () => _children^; pub getMeasureFunction = (_pixelRatio: float) => None; diff --git a/src/UI/Revery_UI.re b/src/UI/Revery_UI.re index 0e7d3bd57..d43d223d7 100644 --- a/src/UI/Revery_UI.re +++ b/src/UI/Revery_UI.re @@ -44,7 +44,7 @@ let start = let rootNode = (new viewNode)(); let mouseCursor: Mouse.Cursor.t = Mouse.Cursor.make(); - let container = React.Container.createContainer(rootNode); + let container = React.Container.create(rootNode); let ui = UiContainer.create( window, diff --git a/src/UI/UiReact.re b/src/UI/UiReact.re index c81c2a02f..8e82dfac8 100644 --- a/src/UI/UiReact.re +++ b/src/UI/UiReact.re @@ -15,10 +15,9 @@ module Container = { state: option(state), }; - let createContainer: UiReconciler.reveryNode => t = - n => {node: n, state: None}; + let create: UiReconciler.reveryNode => t = n => {node: n, state: None}; - let updateContainer: (t, React.syntheticElement) => t = + let update: (t, React.syntheticElement) => t = ({node, state}, element) => { let newRendered = switch (state) { diff --git a/src/UI/UiRender.re b/src/UI/UiRender.re index 0d42259ff..c97b047a3 100644 --- a/src/UI/UiRender.re +++ b/src/UI/UiRender.re @@ -24,7 +24,7 @@ let render = (container: UiContainer.t, component: UiReact.syntheticElement) => AnimationTicker.tick(); /* Perform reconciliation */ - container := UiReact.Container.updateContainer(container^, component); + container := UiReact.Container.update(container^, component); /* Layout */ let size = Window.getSize(window); diff --git a/src/UI/ViewNode.re b/src/UI/ViewNode.re index f86ac14bc..8e15aa2c3 100644 --- a/src/UI/ViewNode.re +++ b/src/UI/ViewNode.re @@ -277,11 +277,11 @@ let renderShadow = (~boxShadow, ~width, ~height, ~world, ~m) => { class viewNode (()) = { as _this; - val solidShader = Assets.solidShader(); inherit (class node(renderPass))() as _super; pub! draw = (pass: renderPass, parentContext: NodeDrawContext.t) => { switch (pass) { | AlphaPass(m) => + let solidShader = Assets.solidShader(); let dimensions = _this#measurements(); let width = float_of_int(dimensions.width); let height = float_of_int(dimensions.height); diff --git a/test/UI/MouseTest.re b/test/UI/MouseTest.re index cd126d095..693480128 100644 --- a/test/UI/MouseTest.re +++ b/test/UI/MouseTest.re @@ -63,7 +63,9 @@ test("Mouse", () => { expect(count^).toBe(1); }); - test("does trigger onBlur for node after cursor is pressed outside the node", () => { + test( + "does trigger onBlur for node after cursor is pressed outside the node", + () => { let cursor = Mouse.Cursor.make(); Mouse.Cursor.set(cursor, Revery_Math.Vec2.create(50.0, 50.0)); diff --git a/test/UI/ReconcilerTests.re b/test/UI/ReconcilerTests.re new file mode 100644 index 000000000..96a0d5fc4 --- /dev/null +++ b/test/UI/ReconcilerTests.re @@ -0,0 +1,89 @@ +open Rejest; + +open Revery_UI; + +/* + * This module tests the integration of the Brisk reconciler - `React` - + * with our Node tree. + */ + +module TestRefComponent = { + let component = React.component("TestRefComponent"); + + let make = (~latestRef, ()) => + component(slots => { + let (refFromState, setRef, _slots: React.Hooks.empty) = + React.Hooks.state(None, slots); + + latestRef := refFromState; + + let setRefInState = r => { + prerr_endline("SET REF IN STATE"); + setRef(Some(r)); + }; + + ; + }); + + let createElement = (~children as _, ~latestRef, ()) => + React.element(make(~latestRef, ())); +}; + +test("Reconciler", () => { + test("reconcile adds a child", () => { + let rootNode = (new viewNode)(); + + let container = React.Container.create(rootNode); + React.Container.update(container, ) |> ignore; + + expect(List.length(rootNode#getChildren())).toBe(1); + }); + + test("ref: returns value of node", () => { + let rootNode = (new viewNode)(); + + let container = React.Container.create(rootNode); + + /* Use a ref to track the latest value of the `ref={..}` callback */ + let referenceNode = ref(None); + + let refCallback = r => { + referenceNode := Some(r); + }; + + React.Container.update(container, ) |> ignore; + + /* And validate that we actually got the right one, based on the node ID! */ + switch (referenceNode^) { + | Some(r) => + let actualChild = rootNode#firstChild(); + expect(actualChild#getInternalId()).toBe(r#getInternalId()); + | None => expect(0).toBe(1) + }; + }); + + test("ref: validate ref gets passed back to component", () => { + let rootNode = (new viewNode)(); + + let container = React.Container.create(rootNode); + + /* + * Hold a `ref` that tracks the last refNode that comes from RENDER - + * this also exercises the interplay between the `ref` callback and `useState` + */ + let latestRef: ref(option(viewNode)) = ref(None); + + /* First update - this will end up 'mounting' the node and dispatching the 'ref' callback */ + /* However - the state won't be updated - it will just be queued up */ + let update1 = + React.Container.update(container, ); + + /* We need to update again to pick up the state update */ + React.Container.update(update1, ) |> ignore; + + switch (latestRef^) { + | Some(_) => expect(1).toBe(1) + | None => expect(0).toBe(1) + }; + }); +});