Skip to content

Commit

Permalink
Merge branch '19' of github.com:/reasonml/reason-react into ref-as-va…
Browse files Browse the repository at this point in the history
…lid-prop

* '19' of github.com:/reasonml/reason-react:
  Replace react dom's testing library with ReactTestingLibrary (#859)
  • Loading branch information
davesnx committed Nov 25, 2024
2 parents 75e2d7c + 66cd920 commit 2d60883
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 24 deletions.
6 changes: 4 additions & 2 deletions src/React.re
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
6 changes: 4 additions & 2 deletions src/React.rei
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
2 changes: 1 addition & 1 deletion test/Form__test.re
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ describe("Form with useOptimistic", () => {
let container = ReactTestingLibrary.render(<App />);

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);
Expand Down
150 changes: 131 additions & 19 deletions test/React__test.re
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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", () => {
Expand Down Expand Up @@ -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);

<div>
<button role="Increment" onClick={_ => setCount(prev => prev + 1)}>
{React.string("Increment")}
</button>
<span role="counter"> {React.string(string_of_int(count))} </span>
</div>;
};
module Counter = {
[@react.component]
let make = () => {
let (count, setCount) = React.Uncurried.useState(() => 0);

<div>
<button role="Increment" onClick={_ => setCount(. prev => prev + 1)}>
{React.string("Increment")}
</button>
<span role="counter"> {React.string(string_of_int(count))} </span>
</div>;
};
};

testPromise("act", finish => {
let containerRef: ref(Js.nullable(ReactTestingLibrary.renderResult)) =
ref(Js.Nullable.null);

React.act(() => {
let container = ReactTestingLibrary.render(<Counter />);
let button = getByRole("Increment", container);
FireEvent.click(button);
containerRef.contents = Js.Nullable.return(container);
});
let.await _ =
React.act(() => {
let container = ReactTestingLibrary.render(<Counter />);
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, <Counter />);
});

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, <Counter />);
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", () => {
Expand Down

0 comments on commit 2d60883

Please sign in to comment.