Skip to content

Commit 2d60883

Browse files
committed
Merge branch '19' of github.com:/reasonml/reason-react into ref-as-valid-prop
* '19' of github.com:/reasonml/reason-react: Replace react dom's testing library with ReactTestingLibrary (#859)
2 parents 75e2d7c + 66cd920 commit 2d60883

File tree

4 files changed

+140
-24
lines changed

4 files changed

+140
-24
lines changed

src/React.re

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,9 +885,11 @@ external startTransition: ([@mel.uncurry] (unit => unit)) => unit =
885885
external useDebugValue: ('value, ~format: 'value => string=?, unit) => unit =
886886
"useDebugValue";
887887

888-
[@mel.module "react"] external act: (unit => unit) => unit = "act";
889888
[@mel.module "react"]
890-
external actAsync: (unit => Js.Promise.t(unit)) => unit = "act";
889+
external act: (unit => unit) => Js.Promise.t(unit) = "act";
890+
[@mel.module "react"]
891+
external actAsync: (unit => Js.Promise.t(unit)) => Js.Promise.t(unit) =
892+
"act";
891893

892894
module Experimental = {
893895
/* This module is used to bind to APIs for future versions of React. There is no guarantee of backwards compatibility or stability. */

src/React.rei

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,9 +573,11 @@ external startTransition: ([@mel.uncurry] (unit => unit)) => unit =
573573
external useTransition: unit => (bool, callback(callback(unit, unit), unit)) =
574574
"useTransition";
575575

576-
[@mel.module "react"] external act: (unit => unit) => unit = "act";
577576
[@mel.module "react"]
578-
external actAsync: (unit => Js.Promise.t(unit)) => unit = "act";
577+
external act: (unit => unit) => Js.Promise.t(unit) = "act";
578+
[@mel.module "react"]
579+
external actAsync: (unit => Js.Promise.t(unit)) => Js.Promise.t(unit) =
580+
"act";
579581

580582
module Experimental: {
581583
/* This module is used to bind to APIs for future versions of React. There is no guarantee of backwards compatibility or stability. */

test/Form__test.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ describe("Form with useOptimistic", () => {
136136
let container = ReactTestingLibrary.render(<App />);
137137

138138
ReactTestingLibrary.actAsync(() => {
139-
let.await _ = findByString("Hola!", container);
139+
let.await _ = findByString({j|¡Hola!|j}, container);
140140

141141
let.await button = findByString("Enviar", container);
142142
let.await input = findByPlaceholderText("message", container);

test/React__test.re

Lines changed: 131 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module DummyContext = {
4747

4848
[@mel.get] external tagName: Dom.element => string = "tagName";
4949
[@mel.get] external innerHTML: Dom.element => string = "innerHTML";
50+
[@mel.set] external setInnerHTML: (Dom.element, string) => unit = "innerHTML";
5051

5152
let getByRole = (role, container) => {
5253
ReactTestingLibrary.getByRole(~matcher=`Str(role), container);
@@ -62,6 +63,24 @@ let getByTag = (tag, container) => {
6263
[@mel.send]
6364
external getAttribute: (Dom.element, string) => option(string) =
6465
"getAttribute";
66+
[@mel.set] external setTitle: (Dom.element, string) => unit = "title";
67+
[@mel.get] external getTitle: Dom.element => string = "title";
68+
69+
let (let.await) = (p, f) => Js.Promise.then_(f, p);
70+
71+
external createElement: string => Dom.element = "document.createElement";
72+
[@mel.send]
73+
external appendChild: (Dom.element, Dom.element) => unit = "appendChild";
74+
external document: Dom.element = "document";
75+
external body: Dom.element = "document.body";
76+
external querySelector: (string, Dom.element) => option(Dom.element) =
77+
"document.querySelector";
78+
79+
[@mel.new]
80+
external mouseEvent: (string, Js.t('a)) => Dom.event = "MouseEvent";
81+
82+
[@mel.send]
83+
external dispatchEvent: (Dom.element, Dom.event) => unit = "dispatchEvent";
6584

6685
describe("React", () => {
6786
test("can render DOM elements", () => {
@@ -233,36 +252,129 @@ describe("React", () => {
233252
expect(image->getAttribute("src"))->toEqual(Some("https://foo.png"));
234253
});
235254

236-
test("React.act", () => {
237-
module Counter = {
238-
[@react.component]
239-
let make = () => {
240-
let (count, setCount) = React.useState(() => 0);
241-
242-
<div>
243-
<button role="Increment" onClick={_ => setCount(prev => prev + 1)}>
244-
{React.string("Increment")}
245-
</button>
246-
<span role="counter"> {React.string(string_of_int(count))} </span>
247-
</div>;
248-
};
255+
module Counter = {
256+
[@react.component]
257+
let make = () => {
258+
let (count, setCount) = React.Uncurried.useState(() => 0);
259+
260+
<div>
261+
<button role="Increment" onClick={_ => setCount(. prev => prev + 1)}>
262+
{React.string("Increment")}
263+
</button>
264+
<span role="counter"> {React.string(string_of_int(count))} </span>
265+
</div>;
249266
};
267+
};
250268

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

254-
React.act(() => {
255-
let container = ReactTestingLibrary.render(<Counter />);
256-
let button = getByRole("Increment", container);
257-
FireEvent.click(button);
258-
containerRef.contents = Js.Nullable.return(container);
259-
});
273+
let.await _ =
274+
React.act(() => {
275+
let container = ReactTestingLibrary.render(<Counter />);
276+
let button = getByRole("Increment", container);
277+
FireEvent.click(button);
278+
containerRef.contents = Js.Nullable.return(container);
279+
});
260280

261281
switch (Js.Nullable.toOption(containerRef.contents)) {
262282
| Some(container) =>
263283
expect(getByRole("counter", container)->innerHTML)->toBe("1")
264284
| None => failwith("Container is null")
265285
};
286+
finish();
287+
});
288+
289+
testPromise("act", finish => {
290+
/* This test doesn't use ReactTestingLibrary to test the act API, and the code comes from
291+
https://react.dev/reference/react/act example */
292+
293+
let container: Dom.element = createElement("div");
294+
body->appendChild(container);
295+
296+
let.await () =
297+
React.act(() => {
298+
let root = ReactDOM.Client.createRoot(container);
299+
ReactDOM.Client.render(root, <Counter />);
300+
});
301+
302+
let valueElement = querySelector(".Value", container);
303+
switch (valueElement) {
304+
| Some(value) => expect(value->innerHTML)->toBe("0")
305+
| None => failwith("Can't find 'Value' element")
306+
};
307+
308+
let title = getTitle(document);
309+
expect(title)->toBe("You clicked 0 times");
310+
311+
let.await () =
312+
React.act(() => {
313+
let buttonElement = querySelector(".Increment", container);
314+
switch (buttonElement) {
315+
| Some(button) =>
316+
dispatchEvent(button, mouseEvent("click", {"bubbles": true}))
317+
| None => failwith("Can't find 'Increment' button")
318+
};
319+
});
320+
321+
let valueElement = querySelector(".Value", container);
322+
switch (valueElement) {
323+
| Some(value) => expect(value->innerHTML)->toBe("1")
324+
| None => failwith("Can't find 'Value' element")
325+
};
326+
327+
let title = getTitle(document);
328+
expect(title)->toBe("You clicked 1 times");
329+
330+
finish();
331+
});
332+
333+
testPromise("actAsync", finish => {
334+
/* This test doesn't use ReactTestingLibrary to test the act API, and the code comes from
335+
https://react.dev/reference/react/act example */
336+
337+
body->setInnerHTML("");
338+
let container: Dom.element = createElement("div");
339+
body->appendChild(container);
340+
341+
let.await () =
342+
React.actAsync(() => {
343+
let root = ReactDOM.Client.createRoot(container);
344+
ReactDOM.Client.render(root, <Counter />);
345+
Js.Promise.resolve();
346+
});
347+
348+
let valueElement = querySelector(".Value", container);
349+
switch (valueElement) {
350+
| Some(value) => expect(value->innerHTML)->toBe("0")
351+
| None => failwith("Can't find 'Value' element")
352+
};
353+
354+
let title = getTitle(document);
355+
expect(title)->toBe("You clicked 0 times");
356+
357+
let.await () =
358+
React.actAsync(() => {
359+
let buttonElement = querySelector(".Increment", container);
360+
switch (buttonElement) {
361+
| Some(button) =>
362+
dispatchEvent(button, mouseEvent("click", {"bubbles": true}))
363+
| None => failwith("Can't find 'Increment' button")
364+
};
365+
Js.Promise.resolve();
366+
});
367+
368+
let valueElement = querySelector(".Value", container);
369+
switch (valueElement) {
370+
| Some(value) => expect(value->innerHTML)->toBe("1")
371+
| None => failwith("Can't find 'Value' element")
372+
};
373+
374+
let title = getTitle(document);
375+
expect(title)->toBe("You clicked 1 times");
376+
377+
finish();
266378
});
267379

268380
test("ErrorBoundary + Suspense", () => {

0 commit comments

Comments
 (0)