diff --git a/src/React.re b/src/React.re
index 22ff09744..ae8cd326e 100644
--- a/src/React.re
+++ b/src/React.re
@@ -885,9 +885,11 @@ external startTransition: ([@mel.uncurry] (unit => unit)) => unit =
external useDebugValue: ('value, ~format: 'value => string=?, unit) => unit =
"useDebugValue";
-[@mel.module "react"] external act: (unit => unit) => unit = "act";
[@mel.module "react"]
-external actAsync: (unit => Js.Promise.t(unit)) => unit = "act";
+external act: (unit => unit) => Js.Promise.t(unit) = "act";
+[@mel.module "react"]
+external actAsync: (unit => Js.Promise.t(unit)) => Js.Promise.t(unit) =
+ "act";
module Experimental = {
/* This module is used to bind to APIs for future versions of React. There is no guarantee of backwards compatibility or stability. */
diff --git a/src/React.rei b/src/React.rei
index 1c3be073e..66b4de0ee 100644
--- a/src/React.rei
+++ b/src/React.rei
@@ -573,9 +573,11 @@ external startTransition: ([@mel.uncurry] (unit => unit)) => unit =
external useTransition: unit => (bool, callback(callback(unit, unit), unit)) =
"useTransition";
-[@mel.module "react"] external act: (unit => unit) => unit = "act";
[@mel.module "react"]
-external actAsync: (unit => Js.Promise.t(unit)) => unit = "act";
+external act: (unit => unit) => Js.Promise.t(unit) = "act";
+[@mel.module "react"]
+external actAsync: (unit => Js.Promise.t(unit)) => Js.Promise.t(unit) =
+ "act";
module Experimental: {
/* This module is used to bind to APIs for future versions of React. There is no guarantee of backwards compatibility or stability. */
diff --git a/test/Form__test.re b/test/Form__test.re
index 2269a9c0e..dbbe7eee2 100644
--- a/test/Form__test.re
+++ b/test/Form__test.re
@@ -136,7 +136,7 @@ describe("Form with useOptimistic", () => {
let container = ReactTestingLibrary.render();
ReactTestingLibrary.actAsync(() => {
- let.await _ = findByString("Hola!", container);
+ let.await _ = findByString({j|¡Hola!|j}, container);
let.await button = findByString("Enviar", container);
let.await input = findByPlaceholderText("message", container);
diff --git a/test/React__test.re b/test/React__test.re
index fa447654b..fbb4fe069 100644
--- a/test/React__test.re
+++ b/test/React__test.re
@@ -47,6 +47,7 @@ module DummyContext = {
[@mel.get] external tagName: Dom.element => string = "tagName";
[@mel.get] external innerHTML: Dom.element => string = "innerHTML";
+[@mel.set] external setInnerHTML: (Dom.element, string) => unit = "innerHTML";
let getByRole = (role, container) => {
ReactTestingLibrary.getByRole(~matcher=`Str(role), container);
@@ -62,6 +63,24 @@ let getByTag = (tag, container) => {
[@mel.send]
external getAttribute: (Dom.element, string) => option(string) =
"getAttribute";
+[@mel.set] external setTitle: (Dom.element, string) => unit = "title";
+[@mel.get] external getTitle: Dom.element => string = "title";
+
+let (let.await) = (p, f) => Js.Promise.then_(f, p);
+
+external createElement: string => Dom.element = "document.createElement";
+[@mel.send]
+external appendChild: (Dom.element, Dom.element) => unit = "appendChild";
+external document: Dom.element = "document";
+external body: Dom.element = "document.body";
+external querySelector: (string, Dom.element) => option(Dom.element) =
+ "document.querySelector";
+
+[@mel.new]
+external mouseEvent: (string, Js.t('a)) => Dom.event = "MouseEvent";
+
+[@mel.send]
+external dispatchEvent: (Dom.element, Dom.event) => unit = "dispatchEvent";
describe("React", () => {
test("can render DOM elements", () => {
@@ -233,36 +252,129 @@ describe("React", () => {
expect(image->getAttribute("src"))->toEqual(Some("https://foo.png"));
});
- test("React.act", () => {
- module Counter = {
- [@react.component]
- let make = () => {
- let (count, setCount) = React.useState(() => 0);
-
-
-
- {React.string(string_of_int(count))}
-
;
- };
+ module Counter = {
+ [@react.component]
+ let make = () => {
+ let (count, setCount) = React.Uncurried.useState(() => 0);
+
+
+
+ {React.string(string_of_int(count))}
+
;
};
+ };
+ testPromise("act", finish => {
let containerRef: ref(Js.nullable(ReactTestingLibrary.renderResult)) =
ref(Js.Nullable.null);
- React.act(() => {
- let container = ReactTestingLibrary.render();
- let button = getByRole("Increment", container);
- FireEvent.click(button);
- containerRef.contents = Js.Nullable.return(container);
- });
+ let.await _ =
+ React.act(() => {
+ let container = ReactTestingLibrary.render();
+ let button = getByRole("Increment", container);
+ FireEvent.click(button);
+ containerRef.contents = Js.Nullable.return(container);
+ });
switch (Js.Nullable.toOption(containerRef.contents)) {
| Some(container) =>
expect(getByRole("counter", container)->innerHTML)->toBe("1")
| None => failwith("Container is null")
};
+ finish();
+ });
+
+ testPromise("act", finish => {
+ /* This test doesn't use ReactTestingLibrary to test the act API, and the code comes from
+ https://react.dev/reference/react/act example */
+
+ let container: Dom.element = createElement("div");
+ body->appendChild(container);
+
+ let.await () =
+ React.act(() => {
+ let root = ReactDOM.Client.createRoot(container);
+ ReactDOM.Client.render(root, );
+ });
+
+ let valueElement = querySelector(".Value", container);
+ switch (valueElement) {
+ | Some(value) => expect(value->innerHTML)->toBe("0")
+ | None => failwith("Can't find 'Value' element")
+ };
+
+ let title = getTitle(document);
+ expect(title)->toBe("You clicked 0 times");
+
+ let.await () =
+ React.act(() => {
+ let buttonElement = querySelector(".Increment", container);
+ switch (buttonElement) {
+ | Some(button) =>
+ dispatchEvent(button, mouseEvent("click", {"bubbles": true}))
+ | None => failwith("Can't find 'Increment' button")
+ };
+ });
+
+ let valueElement = querySelector(".Value", container);
+ switch (valueElement) {
+ | Some(value) => expect(value->innerHTML)->toBe("1")
+ | None => failwith("Can't find 'Value' element")
+ };
+
+ let title = getTitle(document);
+ expect(title)->toBe("You clicked 1 times");
+
+ finish();
+ });
+
+ testPromise("actAsync", finish => {
+ /* This test doesn't use ReactTestingLibrary to test the act API, and the code comes from
+ https://react.dev/reference/react/act example */
+
+ body->setInnerHTML("");
+ let container: Dom.element = createElement("div");
+ body->appendChild(container);
+
+ let.await () =
+ React.actAsync(() => {
+ let root = ReactDOM.Client.createRoot(container);
+ ReactDOM.Client.render(root, );
+ Js.Promise.resolve();
+ });
+
+ let valueElement = querySelector(".Value", container);
+ switch (valueElement) {
+ | Some(value) => expect(value->innerHTML)->toBe("0")
+ | None => failwith("Can't find 'Value' element")
+ };
+
+ let title = getTitle(document);
+ expect(title)->toBe("You clicked 0 times");
+
+ let.await () =
+ React.actAsync(() => {
+ let buttonElement = querySelector(".Increment", container);
+ switch (buttonElement) {
+ | Some(button) =>
+ dispatchEvent(button, mouseEvent("click", {"bubbles": true}))
+ | None => failwith("Can't find 'Increment' button")
+ };
+ Js.Promise.resolve();
+ });
+
+ let valueElement = querySelector(".Value", container);
+ switch (valueElement) {
+ | Some(value) => expect(value->innerHTML)->toBe("1")
+ | None => failwith("Can't find 'Value' element")
+ };
+
+ let title = getTitle(document);
+ expect(title)->toBe("You clicked 1 times");
+
+ finish();
});
test("ErrorBoundary + Suspense", () => {