Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace react dom's testing library with ReactTestingLibrary #859

Merged
merged 24 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jest: ## Run the jest unit tests
jest-watch: ## Run the jest unit tests in watch mode
@npx jest --watch

.PHONY: jest-devtools
jest-devtools: ## Run the jest unit tests in watch mode
@echo "open Chrome and go to chrome://inspect"
@node --inspect-brk node_modules/.bin/jest --runInBand --detectOpenHandles

.PHONY: test
test: ## Run the runtests from dune (snapshot)
@$(DUNE) build @runtest
Expand All @@ -54,11 +59,22 @@ format-check: ## Checks if format is correct
.PHONY: install
install: ## Update the package dependencies when new deps are added to dune-project
@opam install . --deps-only --with-test
@npm install
# --force is needed because we are installing react@19 while other dependencies
# require react@18. It's a good workaround to bypass the npm validation error
# and test the rc versions of React
@npm install --force
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was force needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because testinglib and other deps depend on react 18, so npm complains. It is a decent workaround to ensure the installation succeeds

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will write a comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would we still needed --force if you commit the lockfile from manual install and run npm ci here?


.PHONY: init
create-switch: ## Create a local opam switch
@opam switch create . 5.2.0 --no-install

.PHONY: init
init: create-switch install ## Create a local opam switch, install deps

.PHONY: demo-watch
demo-watch: ## Build the demo in watch mode
@$(DUNE) build @melange-app --watch

.PHONY: demo-serve
demo-serve: ## Build the demo and serve it
npx http-server -p 8080 _build/default/demo/
9 changes: 9 additions & 0 deletions demo/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(melange.emit
(target demo)
(alias melange-app)
(module_systems
(es6 mjs))
(libraries reason-react melange.belt melange.dom)
(runtime_deps index.html)
(preprocess
(pps melange.ppx reason-react-ppx)))
42 changes: 42 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Demo reason-react</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.8.1.min.css" />
<style>
body {
padding: 2rem;
}

#root {
margin-left: auto;
margin-right: auto;
width: 900px;
}
</style>
<script type="importmap">
{
"imports": {
"melange/": "./demo/node_modules/melange/",
"melange.belt/": "./demo/node_modules/melange.belt/",
"melange.js/": "./demo/node_modules/melange.js/",
"reason-react/": "./demo/node_modules/reason-react/",
"react/jsx-runtime": "https://esm.sh/[email protected]/jsx-runtime",
"react": "https://esm.sh/[email protected]",
"react-dom": "https://esm.sh/[email protected]",
"react-dom/client": "https://esm.sh/[email protected]/client"
}
}
</script>
</head>

<body>
<div id="root"></div>
</body>
<script type="module" src="./demo/demo/main.mjs"></script>

</html>
187 changes: 187 additions & 0 deletions demo/main.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
module Stateful = {
[@react.component]
let make = (~title, ~initialValue=0, ~children=React.null) => {
let (value, setValue) = React.useState(() => initialValue);
let onClick = _ => setValue(value => value + 1);

<section>
<h3> {React.string(title)} </h3>
<button key="asdf" onClick> value->React.int </button>
children
</section>;
};
};

module Reducer = {
type action =
| Increment
| Decrement;

[@react.component]
let make = (~initialValue=0) => {
let (state, send) =
React.useReducer(
(state, action) =>
switch (action) {
| Increment => state + 1
| Decrement => state - 1
},
initialValue,
);

Js.log2("Reducer state", state);

<section>
<h3> {React.string("React.useReducer")} </h3>
<main> state->React.int </main>
<button onClick={_ => send(Increment)}>
"Increment"->React.string
</button>
<button onClick={_ => send(Decrement)}>
"Decrement"->React.string
</button>
</section>;
};
};

module ReducerWithMapState = {
type action =
| Increment
| Decrement;

[@react.component]
let make = (~initialValue=0) => {
let (state, send) =
React.useReducerWithMapState(
(state, action) =>
switch (action) {
| Increment => state + 1
| Decrement => state - 1
},
initialValue,
initialValue => initialValue + 75,
);

Js.log2("ReducerWithMapState state", state);

<section>
<h3> {React.string("React.useReducerWithMapState")} </h3>
<main> state->React.int </main>
<button onClick={_ => send(Increment)}>
"Increment"->React.string
</button>
<button onClick={_ => send(Decrement)}>
"Decrement"->React.string
</button>
</section>;
};
};

module WithEffect = {
[@react.component]
let make = (~value) => {
React.useEffect1(
() => {
Js.log("useEffect");
None;
},
[|value|],
);

React.string("React.useEffect");
};
};

module RerenderOnEachClick = {
[@react.component]
let make = (~value=0, ~callback as _) => {
let (value, setValue) = React.useState(() => value);
let onClick = _ =>
if (value < 3) {
Js.log2("Clicked with:", value);
setValue(value => value + 1);
} else {
Js.log("Max value reached, not firing a rerender");
setValue(value => value);
};

<section>
<h3> {React.string("RerenderOnEachClick")} </h3>
<button onClick> <WithEffect value /> </button>
</section>;
};
};

module WithLayoutEffect = {
[@react.component]
let make = (~value=0, ~callback) => {
React.useLayoutEffect1(
() => {
callback(value);
Js.log("useLayoutEffect");
None;
},
[|value|],
);

<section> <h3> {React.string("React.useLayoutEffect")} </h3> </section>;
};
};

module WithRefAndEffect = {
[@react.component]
let make = (~callback) => {
let myRef = React.useRef(1);
React.useEffect0(() => {
myRef.current = myRef.current + 1;
callback(myRef);
None;
});

<section>
<h3> {React.string("React.useRef and useEffect")} </h3>
<main> {React.int(myRef.current)} </main>
</section>;
};
};

[@mel.module "react"]
external useReducer:
([@mel.uncurry] (('state, 'action) => 'state), 'state) =>
('state, 'action => unit) =
"useReducer";

module UseReducerNoProblemo = {
[@react.component]
let make = () => {
let reducer = (v, _) => v + 1;
let (state, send) = useReducer(reducer, 0);
Js.log("asdfasd");
<button onClick={_ => send(0)}> {React.int(state)} </button>;
};
};

module App = {
[@react.component]
let make = (~initialValue) => {
let value = 99;
let callback = _number => ();

<main>
<Stateful title="Stateful" initialValue />
<Reducer key="reducer" initialValue />
<ReducerWithMapState key="reducer-with-map-state" initialValue />
<RerenderOnEachClick key="rerender-on-each-click" value=0 callback />
<WithLayoutEffect key="layout-effect" value callback />
<WithRefAndEffect key="ref-and-effect" callback />
<UseReducerNoProblemo />
</main>;
};
};

switch (ReactDOM.querySelector("#root")) {
| Some(el) =>
let root = ReactDOM.Client.createRoot(el);
ReactDOM.Client.render(root, <App initialValue=0 />);
| None => Js.log("No root element found")
};
2 changes: 1 addition & 1 deletion dune
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(dirs src test ppx)
(dirs src test ppx demo)
Loading
Loading