diff --git a/Makefile b/Makefile
index 6ffd83c0e..6cdbbe039 100644
--- a/Makefile
+++ b/Makefile
@@ -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
@@ -54,7 +59,7 @@ 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 --with-dev-setup
- @npm install
+ @npm install --force
.PHONY: init
create-switch: ## Create a local opam switch
@@ -62,3 +67,11 @@ create-switch: ## Create a local opam switch
.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/
diff --git a/demo/dune b/demo/dune
new file mode 100644
index 000000000..851bbbc10
--- /dev/null
+++ b/demo/dune
@@ -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)))
diff --git a/demo/index.html b/demo/index.html
new file mode 100644
index 000000000..e333157fd
--- /dev/null
+++ b/demo/index.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ Demo reason-react
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/main.re b/demo/main.re
new file mode 100644
index 000000000..8994a58dc
--- /dev/null
+++ b/demo/main.re
@@ -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);
+
+
+ {React.string(title)}
+
+ children
+ ;
+ };
+};
+
+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);
+
+
+ {React.string("React.useReducer")}
+ state->React.int
+
+
+ ;
+ };
+};
+
+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);
+
+
+ {React.string("React.useReducerWithMapState")}
+ state->React.int
+
+
+ ;
+ };
+};
+
+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);
+ };
+
+
+ {React.string("RerenderOnEachClick")}
+
+ ;
+ };
+};
+
+module WithLayoutEffect = {
+ [@react.component]
+ let make = (~value=0, ~callback) => {
+ React.useLayoutEffect1(
+ () => {
+ callback(value);
+ Js.log("useLayoutEffect");
+ None;
+ },
+ [|value|],
+ );
+
+ {React.string("React.useLayoutEffect")}
;
+ };
+};
+
+module WithRefAndEffect = {
+ [@react.component]
+ let make = (~callback) => {
+ let myRef = React.useRef(1);
+ React.useEffect0(() => {
+ myRef.current = myRef.current + 1;
+ callback(myRef);
+ None;
+ });
+
+
+ {React.string("React.useRef and useEffect")}
+ {React.int(myRef.current)}
+ ;
+ };
+};
+
+[@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");
+ ;
+ };
+};
+
+module App = {
+ [@react.component]
+ let make = (~initialValue) => {
+ let value = 99;
+ let callback = _number => ();
+
+
+
+
+
+
+
+
+
+ ;
+ };
+};
+
+switch (ReactDOM.querySelector("#root")) {
+| Some(el) =>
+ let root = ReactDOM.Client.createRoot(el);
+ ReactDOM.Client.render(root, );
+| None => Js.log("No root element found")
+};
diff --git a/dune b/dune
index dc5b54052..5a812ba9c 100644
--- a/dune
+++ b/dune
@@ -1 +1 @@
-(dirs src test ppx)
+(dirs src test ppx demo)
diff --git a/package-lock.json b/package-lock.json
index 56484d8a6..c1006348b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,10 +9,12 @@
"version": "0.11.0",
"license": "MIT",
"devDependencies": {
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/react": "^16.0.1",
+ "http-server": "^14.1.1",
"jest": "^26.0.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-test-renderer": "^18.2.0"
+ "react": "^19.1.0-canary-662957cc-20250221",
+ "react-dom": "^19.1.0-canary-662957cc-20250221"
}
},
"node_modules/@ampproject/remapping": {
@@ -565,6 +567,19 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
+ "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
@@ -954,6 +969,82 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.1.tgz",
+ "integrity": "sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -963,6 +1054,13 @@
"node": ">= 6"
}
},
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/babel__core": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz",
@@ -1198,6 +1296,16 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
"node_modules/arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -1243,6 +1351,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/async": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+ "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.14"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -1405,6 +1523,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1500,6 +1631,26 @@
"node": ">=0.10.0"
}
},
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1782,6 +1933,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/corser": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
+ "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1884,6 +2045,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/define-property": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
@@ -1906,6 +2085,16 @@
"node": ">=0.4.0"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -1924,6 +2113,13 @@
"node": ">= 10.14.2"
}
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/domexception": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
@@ -1987,6 +2183,29 @@
"is-arrayish": "^0.2.1"
}
},
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -2057,6 +2276,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/exec-sh": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz",
@@ -2354,6 +2580,27 @@
"node": ">=8"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -2410,10 +2657,14 @@
}
},
"node_modules/function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
- "dev": true
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
@@ -2433,6 +2684,26 @@
"node": "6.* || 8.* || >= 10.*"
}
},
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -2495,6 +2766,19 @@
"node": ">=4"
}
},
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -2529,6 +2813,45 @@
"node": ">=8"
}
},
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@@ -2592,6 +2915,29 @@
"node": ">=0.10.0"
}
},
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@@ -2616,6 +2962,21 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/http-proxy-agent": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
@@ -2630,6 +2991,73 @@
"node": ">= 6"
}
},
+ "node_modules/http-server": {
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
+ "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "basic-auth": "^2.0.1",
+ "chalk": "^4.1.2",
+ "corser": "^2.0.1",
+ "he": "^1.2.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy": "^1.18.1",
+ "mime": "^1.6.0",
+ "minimist": "^1.2.6",
+ "opener": "^1.5.1",
+ "portfinder": "^1.0.28",
+ "secure-compare": "3.0.1",
+ "union": "~0.5.0",
+ "url-join": "^4.0.1"
+ },
+ "bin": {
+ "http-server": "bin/http-server"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/http-server/node_modules/html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/http-server/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/http-server/node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -3708,18 +4136,6 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3729,6 +4145,16 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -3826,6 +4252,19 @@
"node": ">=8.6"
}
},
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -3890,6 +4329,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -4041,15 +4493,6 @@
"integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==",
"dev": true
},
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object-copy": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
@@ -4135,6 +4578,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object-inspect": {
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
+ "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object-visit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
@@ -4183,6 +4639,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/opener": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
+ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
+ "dev": true,
+ "license": "(WTFPL OR MIT)",
+ "bin": {
+ "opener": "bin/opener-bin.js"
+ }
+ },
"node_modules/p-each-series": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
@@ -4345,6 +4811,31 @@
"node": ">=8"
}
},
+ "node_modules/portfinder": {
+ "version": "1.0.32",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
+ "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async": "^2.6.4",
+ "debug": "^3.2.7",
+ "mkdirp": "^0.5.6"
+ },
+ "engines": {
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/portfinder/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
"node_modules/posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -4407,6 +4898,22 @@
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz",
+ "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
@@ -4414,28 +4921,24 @@
"dev": true
},
"node_modules/react": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
- "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "version": "19.1.0-canary-662957cc-20250221",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0-canary-662957cc-20250221.tgz",
+ "integrity": "sha512-qU2bFGbAA6JlqOX3CKoVYojAJ1f1m4oX3p6KY3iIJgXtBk1YudtLdGZb1iRue/9bA3iO1DqAXposY6DImmZnYA==",
"dev": true,
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
- "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "version": "19.1.0-canary-662957cc-20250221",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0-canary-662957cc-20250221.tgz",
+ "integrity": "sha512-mkAphCiGqwXcsDnmKRHHb0kyvrfRfe4BztE7hJu/uydWepiUaJ5GVuVbFdIWkGuhFAHkDsHu0wlmFOcP49dq3Q==",
"dev": true,
"dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.0"
+ "scheduler": "0.26.0-canary-662957cc-20250221"
},
"peerDependencies": {
- "react": "^18.2.0"
+ "react": "19.1.0-canary-662957cc-20250221"
}
},
"node_modules/react-is": {
@@ -4444,39 +4947,6 @@
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true
},
- "node_modules/react-shallow-renderer": {
- "version": "16.15.0",
- "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz",
- "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==",
- "dev": true,
- "dependencies": {
- "object-assign": "^4.1.1",
- "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0"
- },
- "peerDependencies": {
- "react": "^16.0.0 || ^17.0.0 || ^18.0.0"
- }
- },
- "node_modules/react-test-renderer": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz",
- "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==",
- "dev": true,
- "dependencies": {
- "react-is": "^18.2.0",
- "react-shallow-renderer": "^16.15.0",
- "scheduler": "^0.23.0"
- },
- "peerDependencies": {
- "react": "^18.2.0"
- }
- },
- "node_modules/react-test-renderer/node_modules/react-is": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
- "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
- "dev": true
- },
"node_modules/read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -4527,6 +4997,13 @@
"node": ">=8"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -4663,6 +5140,13 @@
"node": "6.* || >= 7.*"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
@@ -4985,13 +5469,17 @@
}
},
"node_modules/scheduler": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
- "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "version": "0.26.0-canary-662957cc-20250221",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0-canary-662957cc-20250221.tgz",
+ "integrity": "sha512-bScDB7oIB3ZXyWEh/Bd8RMH9WU5A2BbX8+NQsOTOFt92obBxDx2CFeuX1vWSqQb6/h8uhiwO/AfAo5H08RPFWA==",
+ "dev": true
+ },
+ "node_modules/secure-compare": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
+ "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
"dev": true,
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
+ "license": "MIT"
},
"node_modules/semver": {
"version": "6.3.1",
@@ -5008,6 +5496,24 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true
},
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@@ -5072,6 +5578,25 @@
"dev": true,
"optional": true
},
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -5756,6 +6281,18 @@
"is-typedarray": "^1.0.0"
}
},
+ "node_modules/union": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
+ "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
+ "dev": true,
+ "dependencies": {
+ "qs": "^6.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -5874,6 +6411,13 @@
"deprecated": "Please see https://github.com/lydell/urix#deprecated",
"dev": true
},
+ "node_modules/url-join": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
diff --git a/package.json b/package.json
index 63bf49935..95f36c735 100644
--- a/package.json
+++ b/package.json
@@ -25,10 +25,12 @@
},
"homepage": "https://reasonml.github.io/reason-react/",
"devDependencies": {
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/react": "^16.0.1",
+ "http-server": "^14.1.1",
"jest": "^26.0.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-test-renderer": "^18.2.0"
+ "react": "^19.1.0-canary-662957cc-20250221",
+ "react-dom": "^19.1.0-canary-662957cc-20250221"
},
"jest": {
"moduleDirectories": [
diff --git a/src/React.re b/src/React.re
index 17a7f61b1..ae8cd326e 100644
--- a/src/React.re
+++ b/src/React.re
@@ -469,6 +469,7 @@ external displayName: component('props) => option(string) = "displayName";
/* This is used as return values */
type callback('input, 'output) = 'input => 'output;
+type callbackAsync('input, 'output) = 'input => Js.Promise.t('output);
/*
* Yeah, we know this api isn't great. tl;dr: useReducer instead.
@@ -884,8 +885,81 @@ external startTransition: ([@mel.uncurry] (unit => unit)) => unit =
external useDebugValue: ('value, ~format: 'value => string=?, unit) => unit =
"useDebugValue";
+[@mel.module "react"]
+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. */
+ /* https://react.dev/reference/react/use */
+ [@mel.module "react"] external usePromise: Js.Promise.t('a) => 'a = "use";
+ [@mel.module "react"] external useContext: Context.t('a) => 'a = "use";
+ /* https://react.dev/reference/react/useTransition */
+ [@mel.module "react"]
+ external useTransitionAsync:
+ unit => (bool, callbackAsync(callbackAsync(unit, unit), unit)) =
+ "useTransition";
+
+ /* https://react.dev/reference/react/useOptimistic */
+ [@mel.module "react"]
+ external useOptimistic:
+ ('state, ('state, 'optimisticValue) => 'state) =>
+ ('state, 'optimisticValue => unit) =
+ "useOptimistic";
+
+ module FormData = {
+ /* This file is embeded since https://github.com/melange-re/melange/pull/1153 gets merged */
+
+ type t;
+ type file;
+ type blob;
+ type entryValue;
+
+ [@mel.new] external make: unit => t = "FormData";
+ [@mel.send.pipe: t] external append: (string, string) => unit = "append";
+ [@mel.send.pipe: t] external delete: string => unit = "delete";
+ [@mel.send.pipe: t] external get: string => option(entryValue) = "get";
+ [@mel.send.pipe: t]
+ external getAll: string => array(entryValue) = "getAll";
+ [@mel.send.pipe: t] external set: (string, string) => unit = "set";
+ [@mel.send.pipe: t] external has: string => bool = "has";
+ [@mel.send] external keys: t => Js.Iterator.t(string) = "keys";
+ [@mel.send] external values: t => Js.Iterator.t(entryValue) = "values";
+
+ [@mel.send.pipe: t]
+ external appendObject: (string, Js.t({..}), ~filename: string=?) => unit =
+ "append";
+
+ [@mel.send.pipe: t]
+ external appendBlob: (string, blob, ~filename: string=?) => unit =
+ "append";
+
+ [@mel.send.pipe: t]
+ external appendFile: (string, file, ~filename: string=?) => unit =
+ "append";
+
+ [@mel.send.pipe: t]
+ external setObject: (string, Js.t({..}), ~filename: string=?) => unit =
+ "set";
+
+ [@mel.send.pipe: t]
+ external setBlob: (string, blob, ~filename: string=?) => unit = "set";
+
+ [@mel.send.pipe: t]
+ external setFile: (string, file, ~filename: string=?) => unit = "set";
- [@mel.module "react"] external use: Js.Promise.t('a) => 'a = "use";
+ [@mel.send]
+ external entries: t => Js.Iterator.t((string, entryValue)) = "entries";
+ };
+
+ type formAction;
+
+ /* https://react.dev/reference/react/useActionState */
+ [@mel.module "react"]
+ external useActionState:
+ (('state, FormData.t) => unit, 'state, ~permalink: bool=?) =>
+ ('state, formAction, bool) =
+ "useActionState";
};
diff --git a/src/React.rei b/src/React.rei
index c7664b2ba..66b4de0ee 100644
--- a/src/React.rei
+++ b/src/React.rei
@@ -186,8 +186,9 @@ external useReducerWithMapState:
('state, 'action => unit) =
"useReducer";
-/* This is used as return values */
+/* This is used as return values */
type callback('input, 'output) = 'input => 'output;
+type callbackAsync('input, 'output) = 'input => Js.Promise.t('output);
[@mel.module "react"]
external useSyncExternalStore:
@@ -565,15 +566,87 @@ module Uncurried: {
};
[@mel.module "react"]
-external startTransition: ([@mel.uncurry] (unit => unit)) => unit = "startTransition";
+external startTransition: ([@mel.uncurry] (unit => unit)) => unit =
+ "startTransition";
[@mel.module "react"]
external useTransition: unit => (bool, callback(callback(unit, unit), unit)) =
"useTransition";
+[@mel.module "react"]
+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. */
- [@mel.module "react"] external use: Js.Promise.t('a) => 'a = "use";
+ [@mel.module "react"] external usePromise: Js.Promise.t('a) => 'a = "use";
+ [@mel.module "react"] external useContext: Context.t('a) => 'a = "use";
+
+ [@mel.module "react"]
+ external useOptimistic:
+ ('state, ('state, 'optimisticValue) => 'state) =>
+ ('state, 'optimisticValue => unit) =
+ "useOptimistic";
+
+ [@mel.module "react"]
+ external useTransitionAsync:
+ unit => (bool, callbackAsync(callbackAsync(unit, unit), unit)) =
+ "useTransition";
+
+ module FormData: {
+ /* This file is embeded since https://github.com/melange-re/melange/pull/1153 gets merged */
+
+ type t;
+ type file;
+ type blob;
+ type entryValue;
+
+ [@mel.new] external make: unit => t = "FormData";
+ [@mel.send.pipe: t] external append: (string, string) => unit = "append";
+ [@mel.send.pipe: t] external delete: string => unit = "delete";
+ [@mel.send.pipe: t] external get: string => option(entryValue) = "get";
+ [@mel.send.pipe: t]
+ external getAll: string => array(entryValue) = "getAll";
+ [@mel.send.pipe: t] external set: (string, string) => unit = "set";
+ [@mel.send.pipe: t] external has: string => bool = "has";
+ [@mel.send] external keys: t => Js.Iterator.t(string) = "keys";
+ [@mel.send] external values: t => Js.Iterator.t(entryValue) = "values";
+
+ [@mel.send.pipe: t]
+ external appendObject: (string, Js.t({..}), ~filename: string=?) => unit =
+ "append";
+
+ [@mel.send.pipe: t]
+ external appendBlob: (string, blob, ~filename: string=?) => unit =
+ "append";
+
+ [@mel.send.pipe: t]
+ external appendFile: (string, file, ~filename: string=?) => unit =
+ "append";
+
+ [@mel.send.pipe: t]
+ external setObject: (string, Js.t({..}), ~filename: string=?) => unit =
+ "set";
+
+ [@mel.send.pipe: t]
+ external setBlob: (string, blob, ~filename: string=?) => unit = "set";
+
+ [@mel.send.pipe: t]
+ external setFile: (string, file, ~filename: string=?) => unit = "set";
+
+ [@mel.send]
+ external entries: t => Js.Iterator.t((string, entryValue)) = "entries";
+ };
+
+ type formAction;
+
+ [@mel.module "react"]
+ external useActionState:
+ (('state, FormData.t) => unit, 'state, ~permalink: bool=?) =>
+ ('state, formAction, bool) =
+ "useActionState";
};
[@mel.set]
diff --git a/src/ReactDOM.rei b/src/ReactDOM.rei
index 2310ca3e2..9a7eee88c 100644
--- a/src/ReactDOM.rei
+++ b/src/ReactDOM.rei
@@ -653,7 +653,7 @@ type domProps = {
[@mel.optional]
acceptCharset: option(string),
[@mel.optional]
- action: option(string), /* uri */
+ action: option(string),
[@mel.optional]
allowFullScreen: option(bool),
[@mel.optional]
diff --git a/src/ReactDOMServer.rei b/src/ReactDOMServer.rei
index b967a1465..95a1cd02d 100644
--- a/src/ReactDOMServer.rei
+++ b/src/ReactDOMServer.rei
@@ -4,5 +4,3 @@ external renderToString: React.element => string = "renderToString";
[@mel.module "react-dom/server"]
external renderToStaticMarkup: React.element => string =
"renderToStaticMarkup";
-
-
diff --git a/src/ReactDOMTestUtils.re b/src/ReactDOMTestUtils.re
index 8d1d1c258..24dafbff3 100644
--- a/src/ReactDOMTestUtils.re
+++ b/src/ReactDOMTestUtils.re
@@ -2,9 +2,10 @@ type undefined = Js.undefined(unit);
let undefined: undefined = Js.Undefined.empty;
-[@mel.module "react-dom/test-utils"]
+[@mel.module "react"]
external reactAct: ((. unit) => undefined) => unit = "act";
+[@deprecated "use React.act instead"]
let act: (unit => unit) => unit =
func => {
let reactFunc =
@@ -15,10 +16,11 @@ let act: (unit => unit) => unit =
reactAct(reactFunc);
};
-[@mel.module "react-dom/test-utils"]
+[@mel.module "react"]
external reactActAsync: ((. unit) => Js.Promise.t('a)) => Js.Promise.t(unit) =
"act";
+[@deprecated "use React.actAsync instead"]
let actAsync = func => {
let reactFunc =
(.) => {
@@ -27,35 +29,73 @@ let actAsync = func => {
reactActAsync(reactFunc);
};
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
[@mel.module "react-dom/test-utils"]
external isElement: 'element => bool = "isElement";
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
[@mel.module "react-dom/test-utils"]
external isElementOfType: ('element, React.component('props)) => bool =
"isElementOfType";
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
[@mel.module "react-dom/test-utils"]
external isDOMComponent: 'element => bool = "isDOMComponent";
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
[@mel.module "react-dom/test-utils"]
external isCompositeComponent: 'element => bool = "isCompositeComponent";
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
[@mel.module "react-dom/test-utils"]
external isCompositeComponentWithType:
('element, React.component('props)) => bool =
"isCompositeComponentWithType";
module Simulate = {
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external click: Dom.element => unit = "click";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external clickWithEvent: (Dom.element, 'event) => unit = "click";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external change: Dom.element => unit = "change";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external blur: Dom.element => unit = "blur";
+
[@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
external changeWithEvent: (Dom.element, 'event) => unit = "change";
+
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
let changeWithValue = (element, value) => {
let event = {
"target": {
@@ -64,6 +104,10 @@ module Simulate = {
};
changeWithEvent(element, event);
};
+
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
let changeWithChecked = (element, value) => {
let event = {
"target": {
@@ -72,11 +116,23 @@ module Simulate = {
};
changeWithEvent(element, event);
};
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external canPlay: Dom.element => unit = "canPlay";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external timeUpdate: Dom.element => unit = "timeUpdate";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external ended: Dom.element => unit = "ended";
[@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
external focus: Dom.element => unit = "focus";
@@ -101,7 +157,9 @@ external body: Dom.document => option(Dom.element) = "body";
[@mel.send]
external createElement: (Dom.document, string) => Dom.element =
"createElement";
+
[@mel.send] external remove: Dom.element => unit = "remove";
+
[@mel.send]
external appendChild: (Dom.element, Dom.element) => Dom.element =
"appendChild";
@@ -111,19 +169,35 @@ let querySelectorAll = (element, string) => {
};
module DOM = {
- [@mel.return nullable] [@mel.get]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ]
+ [@mel.return nullable]
+ [@mel.get]
external value: Dom.element => option(string) = "value";
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ]
let findBySelector = (element, selector) =>
querySelector(element, selector);
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ]
let findByAllSelector = (element, selector) =>
querySelectorAll(element, selector);
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ]
let findBySelectorAndTextContent = (element, selector, content) =>
querySelectorAll(element, selector)
|> Array.find_opt(node => node->textContent === content);
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ]
let findBySelectorAndPartialTextContent = (element, selector, content) =>
querySelectorAll(element, selector)
|> Array.find_opt(node =>
@@ -131,6 +205,9 @@ module DOM = {
);
};
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
let prepareContainer = (container: ref(option(Dom.element)), ()) => {
let containerElement = document->createElement("div");
let _: option(_) =
@@ -138,11 +215,17 @@ let prepareContainer = (container: ref(option(Dom.element)), ()) => {
container := Some(containerElement);
};
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
let cleanupContainer = (container: ref(option(Dom.element)), ()) => {
let _: option(_) = Option.map(remove, container^);
container := None;
};
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
let getContainer = container => {
container.contents->Option.get;
};
diff --git a/src/ReactDOMTestUtils.rei b/src/ReactDOMTestUtils.rei
index 5f67f3345..bfab45e81 100644
--- a/src/ReactDOMTestUtils.rei
+++ b/src/ReactDOMTestUtils.rei
@@ -1,60 +1,145 @@
-let act: (unit => unit) => unit;
+let act: [@deprecated "use React.act instead"] ((unit => unit) => unit);
-let actAsync: (unit => Js.Promise.t('a)) => Js.Promise.t(unit);
+let actAsync:
+ [@deprecated "use React.actAsync instead"] (
+ (unit => Js.Promise.t('a)) => Js.Promise.t(unit)
+ );
[@mel.module "react-dom/test-utils"]
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
external isElement: 'element => bool = "isElement";
[@mel.module "react-dom/test-utils"]
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
external isElementOfType: ('element, React.component('props)) => bool =
"isElementOfType";
[@mel.module "react-dom/test-utils"]
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
external isDOMComponent: 'element => bool = "isDOMComponent";
[@mel.module "react-dom/test-utils"]
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
external isCompositeComponent: 'element => bool = "isCompositeComponent";
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
[@mel.module "react-dom/test-utils"]
external isCompositeComponentWithType:
('element, React.component('props)) => bool =
"isCompositeComponentWithType";
module Simulate: {
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external click: Dom.element => unit = "click";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external clickWithEvent: (Dom.element, 'event) => unit = "click";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external change: Dom.element => unit = "change";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external blur: Dom.element => unit = "blur";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external changeWithEvent: (Dom.element, 'event) => unit = "change";
let changeWithValue: (Dom.element, string) => unit;
let changeWithChecked: (Dom.element, bool) => unit;
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external canPlay: Dom.element => unit = "canPlay";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external timeUpdate: Dom.element => unit = "timeUpdate";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external ended: Dom.element => unit = "ended";
- [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+ ]
+ [@mel.module "react-dom/test-utils"]
+ [@mel.scope "Simulate"]
external focus: Dom.element => unit = "focus";
};
module DOM: {
- [@mel.return nullable] [@mel.get]
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ]
+ [@mel.return nullable]
+ [@mel.get]
external value: Dom.element => option(string) = "value";
- let findBySelector: (Dom.element, string) => option(Dom.element);
- let findByAllSelector: (Dom.element, string) => array(Dom.element);
+ let findBySelector:
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ] (
+ (Dom.element, string) => option(Dom.element)
+ );
+ let findByAllSelector:
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ] (
+ (Dom.element, string) => array(Dom.element)
+ );
let findBySelectorAndTextContent:
- (Dom.element, string, string) => option(Dom.element);
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ] (
+ (Dom.element, string, string) => option(Dom.element)
+ );
let findBySelectorAndPartialTextContent:
- (Dom.element, string, string) => option(Dom.element);
+ [@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead."
+ ] (
+ (Dom.element, string, string) => option(Dom.element)
+ );
};
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
let prepareContainer: (Stdlib.ref(option(Dom.element)), unit) => unit;
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
let cleanupContainer: (Stdlib.ref(option(Dom.element)), unit) => unit;
+[@deprecated
+ "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead."
+]
let getContainer: Stdlib.ref(option(Dom.element)) => Dom.element;
diff --git a/src/ReactTestRenderer.re b/src/ReactTestRenderer.re
deleted file mode 100644
index 2c3494447..000000000
--- a/src/ReactTestRenderer.re
+++ /dev/null
@@ -1,24 +0,0 @@
-type t;
-
-[@mel.module "react-test-renderer"]
-external create: React.element => t = "create";
-
-[@mel.send] external toJSON: t => Js.Json.t = "toJSON";
-[@mel.send] external toObject: t => Js.t({..}) = "%identity";
-
-module Shallow = {
- type t;
-
- [@mel.module "react-test-renderer/shallow"]
- external createRenderer: unit => t = "createRenderer";
-
- [@mel.send]
- external render: (t, React.element) => option(React.element) = "render";
-
- [@mel.send]
- external getRenderOutput: t => option(React.element) = "getRenderOutput";
-
- [@mel.send] external unmount: t => unit = "unmount";
-
- let renderWithRenderer = render(createRenderer());
-};
diff --git a/src/ReactTestRenderer.rei b/src/ReactTestRenderer.rei
deleted file mode 100644
index c68d57b71..000000000
--- a/src/ReactTestRenderer.rei
+++ /dev/null
@@ -1,24 +0,0 @@
-type t;
-
-[@mel.module "react-test-renderer"]
-external create: React.element => t = "create";
-
-[@mel.send] external toJSON: t => Js.Json.t = "toJSON";
-[@mel.send] external toObject: t => Js.t({..}) = "%identity";
-
-module Shallow: {
- type t;
-
- [@mel.module "react-test-renderer/shallow"]
- external createRenderer: unit => t = "createRenderer";
-
- [@mel.send]
- external render: (t, React.element) => option(React.element) = "render";
-
- [@mel.send]
- external getRenderOutput: t => option(React.element) = "getRenderOutput";
-
- [@mel.send] external unmount: t => unit = "unmount";
-
- let renderWithRenderer: React.element => option(React.element);
-};
diff --git a/src/dune b/src/dune
index 502c83a2d..91ba8d459 100644
--- a/src/dune
+++ b/src/dune
@@ -9,12 +9,11 @@
ReactDOM
ReactDOMServer
ReactDOMTestUtils
- ReactTestRenderer
ReasonReactRouter
ReasonReactErrorBoundary)
(preprocess
(pps melange.ppx reason-react-ppx))
- (libraries melange.dom)
+ (libraries melange.dom melange.js)
(modes melange))
(library
diff --git a/test/Form__test.re b/test/Form__test.re
new file mode 100644
index 000000000..dbbe7eee2
--- /dev/null
+++ b/test/Form__test.re
@@ -0,0 +1,159 @@
+open Jest;
+
+module FormData = React.Experimental.FormData;
+
+type message = {
+ text: string,
+ sending: bool,
+ key: int,
+};
+
+[@mel.send.pipe: Dom.element] external reset: unit = "reset";
+
+let (let.await) = (p, f) => Js.Promise.then_(f, p);
+
+module Thread = {
+ [@react.component]
+ let make = (~messages, ~sendMessage) => {
+ let formRef = React.useRef(Js.Nullable.null);
+ let (optimisticMessages, addOptimisticMessage) =
+ React.Experimental.useOptimistic(messages, (state, newMessage) =>
+ [
+ {
+ text: newMessage,
+ sending: true,
+ key: List.length(state) + 1,
+ },
+ ...state,
+ ]
+ );
+
+ let formAction = formData => {
+ let formMessage = FormData.get("message", formData);
+ switch (formMessage) {
+ | Some(entry) =>
+ switch (Js.Types.classify(entry)) {
+ | JSString(text) =>
+ addOptimisticMessage(text);
+ switch (Js.Nullable.toOption(formRef.current)) {
+ | Some(form) => reset(form)
+ | None => ()
+ };
+ let.await _ = sendMessage(formData);
+ Js.Promise.resolve();
+ | _ => Js.Promise.resolve()
+ }
+ | None => Js.Promise.resolve()
+ };
+ };
+ <>
+
+ {{
+ optimisticMessages->Belt.List.map(message =>
+
+ {React.string(message.text)}
+ {message.sending
+ ? React.null
+ : {React.string("(Enviando...)")} }
+
+ );
+ }
+ ->Belt.List.toArray
+ ->React.array}
+
+ {React.cloneElement(
+ ReactDOM.createElement(
+ "form",
+ ~props=ReactDOM.domProps(~ref=ReactDOM.Ref.domRef(formRef), ()),
+ [|
+ ,
+ ,
+ |],
+ ),
+ {"action": formAction},
+ )}
+ >;
+ };
+};
+
+module App = {
+ let deliverMessage = message => {
+ Js.Promise.resolve(message);
+ };
+
+ [@react.component]
+ let make = () => {
+ let (messages, setMessages) =
+ React.useState(() =>
+ [
+ {
+ text: {j|¡Hola!|j},
+ sending: false,
+ key: 1,
+ },
+ ]
+ );
+
+ let sendMessage = formData => {
+ let formMessage = FormData.get("message", formData);
+ switch (formMessage) {
+ | Some(message) =>
+ let.await entry = deliverMessage(message);
+ switch (Js.Types.classify(entry)) {
+ | JSString(text) =>
+ let _ =
+ setMessages(messages =>
+ [
+ {
+ text,
+ sending: true,
+ key: 1,
+ },
+ ...messages,
+ ]
+ );
+ Js.Promise.resolve();
+ | _ => Js.Promise.resolve()
+ };
+ | None => Js.Promise.resolve()
+ };
+ };
+
+ ;
+ };
+};
+
+let (let.await) = (p, f) => Js.Promise.then_(f, p);
+
+let findByString = (text, container) =>
+ ReactTestingLibrary.findByText(~matcher=`Str(text), container);
+
+let findByPlaceholderText = (text, container) =>
+ ReactTestingLibrary.findByPlaceholderText(~matcher=`Str(text), container);
+
+describe("Form with useOptimistic", () => {
+ testPromise("should render the form", finish => {
+ let container = ReactTestingLibrary.render();
+
+ ReactTestingLibrary.actAsync(() => {
+ let.await _ = findByString({j|¡Hola!|j}, container);
+
+ let.await button = findByString("Enviar", container);
+ let.await input = findByPlaceholderText("message", container);
+
+ FireEvent.change(
+ input,
+ ~eventInit={
+ "target": {
+ "value": "Let's go!",
+ },
+ },
+ );
+
+ FireEvent.click(button);
+ let.await _newMessage = findByString("Let's go!", container);
+ /* If the promise resolve, means the node is found in the DOM */
+ finish();
+ });
+ })
+});
diff --git a/test/Hooks__test.re b/test/Hooks__test.re
index 20b942a46..71aa3dc4a 100644
--- a/test/Hooks__test.re
+++ b/test/Hooks__test.re
@@ -1,10 +1,5 @@
open Jest;
open Jest.Expect;
-open ReactDOMTestUtils;
-open Belt;
-
-/* https://react.dev/blog/2022/03/08/react-18-upgrade-guide#configuring-your-testing-environment */
-[%%mel.raw "globalThis.IS_REACT_ACT_ENVIRONMENT = true"];
type store('a) = {
subscribe: (unit => unit, unit) => unit,
@@ -47,17 +42,43 @@ let store = (initialState: 'a) => {
};
};
- {getState, setState, subscribe};
+ {
+ getState,
+ setState,
+ subscribe,
+ };
};
module DummyStatefulComponent = {
[@react.component]
let make = (~initialValue=0, ()) => {
- let (value, setValue) = React.useState(() => initialValue);
+ let (value, setValue) = React.Uncurried.useState(() => initialValue);
+ let onClick = _ => setValue(. value => value + 1);
+ ;
+ };
+};
+
+module DummyIncrementReducerComponent = {
+ type action =
+ | Increment;
+
+ [@react.component]
+ let make = (~initialValue=0) => {
+ let (state, send) =
+ React.Uncurried.useReducer(
+ (state, action) =>
+ switch (action) {
+ | Increment => state + 1
+ },
+ initialValue,
+ );
- ;
+
+ {React.int(state)}
+
+ ;
};
};
@@ -65,10 +86,11 @@ module DummyReducerComponent = {
type action =
| Increment
| Decrement;
+
[@react.component]
- let make = (~initialValue=0, ()) => {
+ let make = (~initialValue=0) => {
let (state, send) =
- React.useReducer(
+ React.Uncurried.useReducer(
(state, action) =>
switch (action) {
| Increment => state + 1
@@ -77,13 +99,15 @@ module DummyReducerComponent = {
initialValue,
);
+ Js.log2("state", state);
+
<>
- state->React.int
-