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)
+ };
+ });
+});