From ca13b5aa8fed1530bccb4208eb7f0f8575607e3f Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 20 Nov 2024 10:10:10 +0100 Subject: [PATCH 1/8] Snapshot with lower {} --- ppx/test/lower.t/run.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ppx/test/lower.t/run.t b/ppx/test/lower.t/run.t index 0898fb7b4..808a6a190 100644 --- a/ppx/test/lower.t/run.t +++ b/ppx/test/lower.t/run.t @@ -94,7 +94,7 @@ ([@merlin.hide] ReactDOM.domProps)( ~children= examples - |> List.map(e => + |> List.map(e => { let Key = e.path; ReactDOM.jsxKeyed( ~key=Key, @@ -120,7 +120,7 @@ ), (), ); - ) + }) |> React.list, (), ), From 563d8af51e7968a42bc03f9899b1e5ad8784112e Mon Sep 17 00:00:00 2001 From: David Sancho Date: Wed, 20 Nov 2024 14:19:06 +0100 Subject: [PATCH 2/8] Update src/React.re MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Javier Chávarri --- src/React.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/React.re b/src/React.re index ba02b4222..c870c9eae 100644 --- a/src/React.re +++ b/src/React.re @@ -894,7 +894,7 @@ module Experimental = { unit => (bool, callbackAsync(callbackAsync(unit, unit), unit)) = "useTransition"; - /* https://es.react.dev/reference/react/useOptimistic */ + /* https://react.dev/reference/react/useOptimistic */ [@mel.module "react"] external useOptimistic: ('state, ('state, 'optimisticValue) => 'state) => From 9ce19ea8fe92313a54ae6c028f135f26637ca35f Mon Sep 17 00:00:00 2001 From: David Sancho Date: Wed, 20 Nov 2024 14:19:14 +0100 Subject: [PATCH 3/8] Update src/React.re MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Javier Chávarri --- src/React.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/React.re b/src/React.re index c870c9eae..92ae0199a 100644 --- a/src/React.re +++ b/src/React.re @@ -889,6 +889,7 @@ 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 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)) = From 5636b99b453970029ca12ffd9ce500480fd70b57 Mon Sep 17 00:00:00 2001 From: David Sancho Date: Wed, 20 Nov 2024 14:19:46 +0100 Subject: [PATCH 4/8] Update src/React.re MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Javier Chávarri --- src/React.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/React.re b/src/React.re index 92ae0199a..431b7a05a 100644 --- a/src/React.re +++ b/src/React.re @@ -887,6 +887,7 @@ external useDebugValue: ('value, ~format: 'value => string=?, unit) => unit = 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 */ From cb48b76ca4c48d36b45562858366e3c52c03ff6d Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Mon, 25 Nov 2024 15:27:55 +0100 Subject: [PATCH 5/8] Add uri comment back on action --- src/ReactDOM.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReactDOM.re b/src/ReactDOM.re index 92b7c4d6c..bedb622f2 100644 --- a/src/ReactDOM.re +++ b/src/ReactDOM.re @@ -652,7 +652,7 @@ type domProps = { [@mel.optional] acceptCharset: option(string), [@mel.optional] - action: option(string), + action: option(string), /* uri */ [@mel.optional] allowFullScreen: option(bool), [@mel.optional] From e78adccc60fa3043944bfa3bf69d0795ce5b4268 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Mon, 25 Nov 2024 15:28:22 +0100 Subject: [PATCH 6/8] Add deprecations on ReactDOMTestUtils --- src/ReactDOMTestUtils.re | 276 +++++++++++++++++++++++++++++---------- 1 file changed, 209 insertions(+), 67 deletions(-) diff --git a/src/ReactDOMTestUtils.re b/src/ReactDOMTestUtils.re index 80615b4ec..0c841bf0e 100644 --- a/src/ReactDOMTestUtils.re +++ b/src/ReactDOMTestUtils.re @@ -2,76 +2,139 @@ type undefined = Js.undefined(unit); let undefined: undefined = Js.Undefined.empty; -[@mel.module "react"] +[@deprecated "use React.act instead"] [@mel.module "react"] external reactAct: ((. unit) => undefined) => unit = "act"; +[@deprecated "use React.act instead"] let act: (unit => unit) => unit = - func => { - let reactFunc = - (.) => { - func(); - undefined; - }; - reactAct(reactFunc); - }; - -[@mel.module "react"] + [@alert "-deprecated"] + ( + func => { + let reactFunc = + (.) => { + func(); + undefined; + }; + reactAct(reactFunc); + } + ); + +[@deprecated "use React.actAsync instead"] [@mel.module "react"] external reactActAsync: ((. unit) => Js.Promise.t('a)) => Js.Promise.t(unit) = "act"; -let actAsync = func => { - let reactFunc = - (.) => { - func(); - }; - reactActAsync(reactFunc); -}; +[@deprecated "use React.actAsync instead"] +let actAsync = + [@alert "-deprecated"] + ( + func => { + let reactFunc = + (.) => { + 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"] + + [@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 = (element, value) => { - let event = { - "target": { - "value": value, - }, - }; - changeWithEvent(element, event); - }; - let changeWithChecked = (element, value) => { - let event = { - "target": { - "checked": value, - }, - }; - changeWithEvent(element, event); - }; + + [@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead." + ] + let changeWithValue = + [@alert "-deprecated"] + ( + (element, value) => { + let event = { + "target": { + "value": value, + }, + }; + changeWithEvent(element, event); + } + ); + + [@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead." + ] + let changeWithChecked = + [@alert "-deprecated"] + ( + (element, value) => { + let event = { + "target": { + "checked": value, + }, + }; + changeWithEvent(element, event); + } + ); [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"] external canPlay: Dom.element => unit = "canPlay"; [@mel.module "react-dom/test-utils"] [@mel.scope "Simulate"] @@ -84,65 +147,144 @@ module Simulate = { external document: Dom.document = "document"; -[@mel.return nullable] [@mel.send] +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." +] +[@mel.return nullable] +[@mel.send] external querySelector: (Dom.element, string) => option(Dom.element) = "querySelector"; +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." +] [@mel.send] external querySelectorAll: (Dom.element, string) => Js.Array.array_like(Dom.element) = "querySelectorAll"; -[@mel.get] external textContent: Dom.element => string = "textContent"; +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." +] +[@mel.get] +external textContent: Dom.element => string = "textContent"; -[@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 body: Dom.document => option(Dom.element) = "body"; +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." +] [@mel.send] external createElement: (Dom.document, string) => Dom.element = "createElement"; -[@mel.send] external remove: Dom.element => unit = "remove"; + +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." +] +[@mel.send] +external remove: Dom.element => unit = "remove"; + +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." +] [@mel.send] external appendChild: (Dom.element, Dom.element) => Dom.element = "appendChild"; -let querySelectorAll = (element, string) => { - Js.Array.from(querySelectorAll(element, string)); -}; +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." +] +let querySelectorAll = + [@alert "-deprecated"] + ( + (element, string) => { + Js.Array.from(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"; - let findBySelector = (element, selector) => - querySelector(element, selector); + [@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." + ] + let findBySelector = + [@alert "-deprecated"] + ((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); - 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 findBySelectorAndTextContent = + [@alert "-deprecated"] + ( + (element, selector, content) => + querySelectorAll(element, selector) + |> Array.find_opt(node => node->textContent === content) + ); - let findBySelectorAndPartialTextContent = (element, selector, content) => - querySelectorAll(element, selector) - |> Array.find_opt(node => - Js.String.includes(~search=content, node->textContent) - ); + [@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-webapi instead." + ] + let findBySelectorAndPartialTextContent = + [@alert "-deprecated"] + ( + (element, selector, content) => + querySelectorAll(element, selector) + |> Array.find_opt(node => + Js.String.includes(~search=content, node->textContent) + ) + ); }; -let prepareContainer = (container: ref(option(Dom.element)), ()) => { - let containerElement = document->createElement("div"); - let _: option(_) = - Option.map(body => body->appendChild(containerElement), document->body); - container := Some(containerElement); -}; +[@deprecated + "ReactDOMTestUtils is deprecated, and will be removed in next version. Please use melange-testing-library instead." +] +let prepareContainer = + [@alert "-deprecated"] + ( + (container: ref(option(Dom.element)), ()) => { + let containerElement = document->createElement("div"); + let _: option(_) = + Option.map( + body => body->appendChild(containerElement), + document->body, + ); + container := Some(containerElement); + } + ); -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 cleanupContainer = + [@alert "-deprecated"] + ( + (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; }; From 66cd920f17f3817722d829c59a7294e232913aa7 Mon Sep 17 00:00:00 2001 From: David Sancho Date: Mon, 25 Nov 2024 18:36:00 +0100 Subject: [PATCH 7/8] Replace react dom's testing library with ReactTestingLibrary (#859) * Install melange-testing-library * Install melange-testing-library npm deps * Vendor melange-testing-library * Fix Form__test with RTL * Start migrating Hooks__test * Remove dependency * Remove unused code from Form__test * Add a jest-devtoolsgs * Add a jest-devtools * Migrate Hooks and Form into RTL * Add demo to manually test easily * Use Uncurried for tests * Migrate all React__test * Force install since we are dealing with R19 * Snapshot with lower {} * Remove jest from demo/dune * Add comment on install --force * Bind React.act and React.actAsync * Bind React.act and React.actAsync * Use React.act as async version only * Test react.act and react.actasync * Fix hola test :( --- Makefile | 18 +- demo/dune | 9 + demo/index.html | 42 + demo/main.re | 187 +++ dune | 2 +- package-lock.json | 707 +++++++++-- package.json | 8 +- src/React.re | 6 + src/React.rei | 6 + src/ReactTestRenderer.re | 24 - src/ReactTestRenderer.rei | 24 - src/dune | 1 - test/Form__test.re | 99 +- test/Hooks__test.re | 478 +++---- test/ReactRouter__test.re | 12 +- test/ReactTestRenderer__test.re | 106 -- test/React__test.re | 534 ++++---- test/dune | 9 +- test/jest/Jest.re | 19 + test/melange-testing-library/README.md | 5 + .../dom/DomTestingLibrary.re | 4 + .../dom/DomTestingLibrary.rei | 855 +++++++++++++ test/melange-testing-library/dom/FireEvent.re | 404 ++++++ .../melange-testing-library/dom/FireEvent.rei | 71 ++ test/melange-testing-library/dom/Queries.re | 1126 +++++++++++++++++ test/melange-testing-library/dom/Utils.re | 180 +++ test/melange-testing-library/dom/dune | 7 + .../react/ReactTestingLibrary.re | 1044 +++++++++++++++ .../react/ReactTestingLibrary.rei | 702 ++++++++++ test/melange-testing-library/react/dune | 7 + 30 files changed, 5859 insertions(+), 837 deletions(-) create mode 100644 demo/dune create mode 100644 demo/index.html create mode 100644 demo/main.re delete mode 100644 src/ReactTestRenderer.re delete mode 100644 src/ReactTestRenderer.rei delete mode 100644 test/ReactTestRenderer__test.re create mode 100644 test/melange-testing-library/README.md create mode 100644 test/melange-testing-library/dom/DomTestingLibrary.re create mode 100644 test/melange-testing-library/dom/DomTestingLibrary.rei create mode 100644 test/melange-testing-library/dom/FireEvent.re create mode 100644 test/melange-testing-library/dom/FireEvent.rei create mode 100644 test/melange-testing-library/dom/Queries.re create mode 100644 test/melange-testing-library/dom/Utils.re create mode 100644 test/melange-testing-library/dom/dune create mode 100644 test/melange-testing-library/react/ReactTestingLibrary.re create mode 100644 test/melange-testing-library/react/ReactTestingLibrary.rei create mode 100644 test/melange-testing-library/react/dune diff --git a/Makefile b/Makefile index 3512619a7..d5a930595 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,10 @@ 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 .PHONY: init create-switch: ## Create a local opam switch @@ -62,3 +70,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..b08e74e85 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.0.0-rc.1", + "react-dom": "19.0.0-rc.1" } }, "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,69 +4921,41 @@ "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.0.0-rc.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc.1.tgz", + "integrity": "sha512-NZKln+uyPuyHchzP07I6GGYFxdAoaKhehgpCa3ltJGzwE31OYumLeshGaitA1R/fS5d9D2qpZVwTFAr6zCLM9w==", "dev": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, + "license": "MIT", "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.0.0-rc.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc.1.tgz", + "integrity": "sha512-k8MfDX+4G+eaa1cXXI9QF4d+pQtYol3nx8vauqRWUEOPqC7NQn2qmEqUsLoSd28rrZUL+R3T2VC+kZ2Hyx1geQ==", "dev": true, + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "0.25.0-rc.1" }, "peerDependencies": { - "react": "^18.2.0" + "react": "19.0.0-rc.1" } }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.25.0-rc.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc.1.tgz", + "integrity": "sha512-fVinv2lXqYpKConAMdergOl5owd0rY1O4P/QTe0aWKCqGtu7VsCt1iqQFxSJtqK4Lci/upVSBpGwVC7eWcuS9Q==", + "dev": true, + "license": "MIT" + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "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 +5006,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 +5149,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", @@ -4984,14 +5477,12 @@ "node": ">=10" } }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "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 +5499,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 +5581,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 +6284,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 +6414,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..f6f991768 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,11 @@ "homepage": "https://reasonml.github.io/reason-react/", "devDependencies": { "jest": "^26.0.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-test-renderer": "^18.2.0" + "react": "19.0.0-rc.1", + "react-dom": "19.0.0-rc.1", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.0.1", + "http-server": "^14.1.1" }, "jest": { "moduleDirectories": [ diff --git a/src/React.re b/src/React.re index 431b7a05a..ae8cd326e 100644 --- a/src/React.re +++ b/src/React.re @@ -885,6 +885,12 @@ 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 */ diff --git a/src/React.rei b/src/React.rei index 372337618..66b4de0ee 100644 --- a/src/React.rei +++ b/src/React.rei @@ -573,6 +573,12 @@ external startTransition: ([@mel.uncurry] (unit => unit)) => unit = 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 usePromise: Js.Promise.t('a) => 'a = "use"; 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 1226d33d9..91ba8d459 100644 --- a/src/dune +++ b/src/dune @@ -9,7 +9,6 @@ ReactDOM ReactDOMServer ReactDOMTestUtils - ReactTestRenderer ReasonReactRouter ReasonReactErrorBoundary) (preprocess diff --git a/test/Form__test.re b/test/Form__test.re index 123f47ad7..dbbe7eee2 100644 --- a/test/Form__test.re +++ b/test/Form__test.re @@ -1,15 +1,7 @@ -[@alert "-deprecated"]; - open Jest; -open Jest.Expect; -open ReactDOMTestUtils; -open Belt; module FormData = React.Experimental.FormData; -/* https://react.dev/blog/2022/03/08/react-18-upgrade-guide#configuring-your-testing-environment */ -[%%mel.raw "globalThis.IS_REACT_ACT_ENVIRONMENT = true"]; - type message = { text: string, sending: bool, @@ -55,24 +47,26 @@ module Thread = { }; }; <> - {{ - optimisticMessages->Belt.List.map(message => -
- {React.string(message.text)} - {message.sending - ? React.null - : {React.string("(Enviando...)")} } -
- ); - } - ->Belt.List.toArray - ->React.array} +
+ {{ + 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), ()), [| - , + , , |], ), @@ -93,7 +87,7 @@ module App = { React.useState(() => [ { - text: "¡Hola!", + text: {j|¡Hola!|j}, sending: false, key: 1, }, @@ -129,46 +123,37 @@ module App = { }; }; -describe("Form with useOptimistic", () => { - let container = ref(None); +let (let.await) = (p, f) => Js.Promise.then_(f, p); - beforeEach(prepareContainer(container)); - afterEach(cleanupContainer(container)); +let findByString = (text, container) => + ReactTestingLibrary.findByText(~matcher=`Str(text), container); - test("should render the form", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); +let findByPlaceholderText = (text, container) => + ReactTestingLibrary.findByPlaceholderText(~matcher=`Str(text), container); - act(() => ReactDOM.Client.render(root, )); +describe("Form with useOptimistic", () => { + testPromise("should render the form", finish => { + let container = ReactTestingLibrary.render(); - expect( - container - ->DOM.findBySelectorAndTextContent("button", "0") - ->Option.isSome, - ) - ->toBe(true); + ReactTestingLibrary.actAsync(() => { + let.await _ = findByString({j|¡Hola!|j}, container); - let button = container->DOM.findBySelector("button"); + let.await button = findByString("Enviar", container); + let.await input = findByPlaceholderText("message", container); - act(() => { - switch (button) { - | Some(button) => Simulate.click(button) - | None => () - } - }); + FireEvent.change( + input, + ~eventInit={ + "target": { + "value": "Let's go!", + }, + }, + ); - expect( - container - ->DOM.findBySelectorAndTextContent("button", "0") - ->Option.isSome, - ) - ->toBe(false); - - expect( - container - ->DOM.findBySelectorAndTextContent("button", "1") - ->Option.isSome, - ) - ->toBe(true); - }); + 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 41a5e1fed..71aa3dc4a 100644 --- a/test/Hooks__test.re +++ b/test/Hooks__test.re @@ -1,12 +1,5 @@ -[@alert "-deprecated"]; - 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, @@ -59,11 +52,33 @@ let store = (initialState: 'a) => { 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)}
+ +
; }; }; @@ -71,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 @@ -83,13 +99,15 @@ module DummyReducerComponent = { initialValue, ); + Js.log2("state", state); + <> -
state->React.int
- - ; }; @@ -99,10 +117,11 @@ module DummyReducerWithMapStateComponent = { type action = | Increment | Decrement; + [@react.component] let make = (~initialValue=0, ()) => { let (state, send) = - React.useReducerWithMapState( + React.Uncurried.useReducerWithMapState( (state, action) => switch (action) { | Increment => state + 1 @@ -112,21 +131,21 @@ module DummyReducerWithMapStateComponent = { initialValue => initialValue + 1, ); - <> -
state->React.int
- - - ; + ; }; }; -module DummyComponentWithEffect = { +module WithEffect = { [@react.component] - let make = (~value=0, ~callback, ()) => { + let make = (~value, ~callback) => { React.useEffect1( () => { callback(value); @@ -135,7 +154,26 @@ module DummyComponentWithEffect = { [|value|], ); -
; + {React.int(value)} ; + }; +}; + +module RerenderOnEachClick = { + [@react.component] + let make = (~initialValue=0, ~maxValue=3, ~callback) => { + let (value, setValue) = React.Uncurried.useState(() => initialValue); + let onClick = _ => + if (value < maxValue) { + setValue(. value => value + 1); + } else { + /* Fire a setState with the same value */ + setValue(. value => value); + }; + +
+

{React.string("RerenderOnEachClick")}

+ +
; }; }; @@ -156,7 +194,7 @@ module DummyComponentWithLayoutEffect = { module DummyComponentWithRefAndEffect = { [@react.component] - let make = (~callback, ()) => { + let make = (~callback) => { let myRef = React.useRef(1); React.useEffect0(() => { myRef.current = myRef.current + 1; @@ -167,311 +205,185 @@ module DummyComponentWithRefAndEffect = { }; }; -describe("Hooks", () => { - let container = ref(None); +let getByString = (text, container) => + ReactTestingLibrary.getByText(~matcher=`Str(text), container); - beforeEach(prepareContainer(container)); - afterEach(cleanupContainer(container)); +let findByPlaceholderText = (text, container) => + ReactTestingLibrary.findByPlaceholderText(~matcher=`Str(text), container); - test("can render react components", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); +[@mel.get] external tagName: Dom.element => string = "tagName"; +[@mel.get] external innerHTML: Dom.element => string = "innerHTML"; - act(() => ReactDOM.Client.render(root, )); +let getByTag = (tag, container) => { + let fn = (_string, element: Dom.element) => { + element->tagName->Js.String.toLowerCase == tag; + }; + ReactTestingLibrary.getByText(~matcher=`Func(fn), container); +}; - expect( - container - ->DOM.findBySelectorAndTextContent("button", "0") - ->Option.isSome, - ) - ->toBe(true); +let getByRole = (role, container) => { + ReactTestingLibrary.getByRole(~matcher=`Str(role), container); +}; - let button = container->DOM.findBySelector("button"); +let getByInnerHTML = (text, container) => { + let fn = (inner, _element: Dom.element) => { + String.equal(inner, text); + }; + ReactTestingLibrary.getByText(~matcher=`Func(fn), container); +}; - act(() => { - switch (button) { - | Some(button) => Simulate.click(button) - | None => () - } - }); +describe("Hooks", () => { + beforeEach(() => ReactTestingLibrary.cleanup()); - expect( - container - ->DOM.findBySelectorAndTextContent("button", "0") - ->Option.isSome, - ) - ->toBe(false); - - expect( - container - ->DOM.findBySelectorAndTextContent("button", "1") - ->Option.isSome, - ) - ->toBe(true); + test("can render react components", () => { + let container = ReactTestingLibrary.render(); + let button = getByTag("button", container); + expect(DomTestingLibrary.getNodeText(button))->toBe("0"); + FireEvent.click(button); + expect(DomTestingLibrary.getNodeText(button))->toBe("1"); }); - test("can render react components with reducers", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => {ReactDOM.Client.render(root, )}); - - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "0") - ->Option.isSome, - ) - ->toBe(true); - - let button = - container->DOM.findBySelectorAndPartialTextContent( - "button", - "Increment", + test("can render react components with effects", () => { + let container = + ReactTestingLibrary.render( + ()} />, ); + let button = getByTag("button", container); + let counter = getByTag("span", container); + FireEvent.click(button); /* 0 -> 1 */ + expect(DomTestingLibrary.getNodeText(counter))->toBe("1"); + FireEvent.click(button); /* 1 -> 2 */ + expect(DomTestingLibrary.getNodeText(counter))->toBe("2"); + /* Limit reached, counter doesn't increase, so no rerenders */ + FireEvent.click(button); /* 2 -> 2 */ + expect(DomTestingLibrary.getNodeText(counter))->toBe("2"); + }); - act(() => { - switch (button) { - | Some(button) => Simulate.click(button) - | None => () - } - }); + test("can render react components with effects", () => { + let callback = Mock.fn(); - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "0") - ->Option.isSome, - ) - ->toBe(false); - - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "1") - ->Option.isSome, - ) - ->toBe(true); - - let button = - container->DOM.findBySelectorAndPartialTextContent( - "button", - "Decrement", + let container = + ReactTestingLibrary.render( + , ); - - act(() => { - switch (button) { - | Some(button) => Simulate.click(button) - | None => () - } - }); - - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "0") - ->Option.isSome, - ) - ->toBe(true); - - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "1") - ->Option.isSome, - ) - ->toBe(false); + let button = getByTag("button", container); + FireEvent.click(button); /* 0 -> 1 */ + FireEvent.click(button); /* 1 -> 2 */ + /* Limit reached, counter doesn't increase, so no rerenders */ + FireEvent.click(button); /* 2 -> 2 */ + FireEvent.click(button); /* 2 -> 2 */ + + let allCalls = callback->Mock.getMock->Mock.calls; + expect(allCalls)->toEqual([|[|0|], [|1|], [|2|]|]); }); - test("can render react components with reducers (map state)", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); + test("useSyncExternalStore", () => { + let mock = Mock.fn(); + let {subscribe, getState, setState} = store("initial"); - act(() => { - ReactDOM.Client.render(root, ) - }); + let subscribeWithMock = args => { + let _ = mock(.); + subscribe(args); + }; - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "1") - ->Option.isSome, - ) - ->toBe(true); - - let button = - container->DOM.findBySelectorAndPartialTextContent( - "button", - "Increment", - ); + module App = { + [@react.component] + let make = () => { + let snapshot = + React.useSyncExternalStore( + ~subscribe=subscribeWithMock, + ~getSnapshot=getState, + ); + {React.string(snapshot)} ; + }; + }; - act(() => { - switch (button) { - | Some(button) => Simulate.click(button) - | None => () - } - }); + let container = ReactTestingLibrary.render(); - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "1") - ->Option.isSome, - ) - ->toBe(false); - - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "2") - ->Option.isSome, - ) - ->toBe(true); - - let button = - container->DOM.findBySelectorAndPartialTextContent( - "button", - "Decrement", - ); + /* Ensure initial value is passed */ + let span = getByTag("span", container); + expect(DomTestingLibrary.getNodeText(span))->toBe("initial"); - act(() => { - switch (button) { - | Some(button) => Simulate.click(button) - | None => () - } - }); + ReactTestingLibrary.act(() => setState("changed")); - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "1") - ->Option.isSome, - ) - ->toBe(true); - - expect( - container - ->DOM.findBySelectorAndTextContent(".value", "2") - ->Option.isSome, - ) - ->toBe(false); - }); + /* Ensure store is being updated */ + let nextState = getState(); + expect(nextState)->toBe("changed"); - test("can render react components with effects", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - let callback = Mock.fn(); + /* the state should be propagated to App */ + expect(DomTestingLibrary.getNodeText(span))->toBe("changed"); - act(() => { - ReactDOM.Client.render( - root, - , - ) - }); - act(() => { - ReactDOM.Client.render( - root, - , - ) - }); - act(() => { - ReactDOM.Client.render( - root, - , - ) - }); + /* on the next render */ + ReactTestingLibrary.rerender(, container); - expect(callback->Mock.getMock->Mock.calls)->toEqual([|[|0|], [|1|]|]); + /* the state should stay 'changed' */ + expect(DomTestingLibrary.getNodeText(span))->toBe("changed"); }); test("can render react components with layout effects", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); let callback = Mock.fn(); - act(() => { - ReactDOM.Client.render( - root, + let container = + ReactTestingLibrary.render( , - ) - }); - act(() => { - ReactDOM.Client.render( - root, + ); + + ReactTestingLibrary.act(() => { + ReactTestingLibrary.rerender( , + container, ) }); - act(() => { - ReactDOM.Client.render( - root, + ReactTestingLibrary.act(() => { + ReactTestingLibrary.rerender( , + container, ) }); - expect(callback->Mock.getMock->Mock.calls)->toEqual([|[|0|], [|1|]|]); }); test("can work with useRef", () => { - let container = getContainer(container); let myRef = ref(None); let callback = reactRef => { myRef := Some(reactRef); }; - let root = ReactDOM.Client.createRoot(container); - act(() => { - ReactDOM.Client.render( - root, - , - ) - }); + let _ = + ReactTestingLibrary.render(); - expect(myRef.contents->Option.map(item => item.current)) - ->toEqual(Some(2)); - }); - - testAsync("useSyncExternalStore", finish => { - let {subscribe, getState, setState} = store("initial"); - let mock = Mock.fn(); - - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - let subscribeWithMock = args => { - let _ = mock(.); - subscribe(args); - }; - - module App = { - [@react.component] - let make = () => { - let snapshot = - React.useSyncExternalStore( - ~subscribe=subscribeWithMock, - ~getSnapshot=getState, - ); - {React.string(snapshot)} ; - }; + switch (myRef.contents) { + | Some(ref) => expect(ref.current)->toBe(2) + | None => failwith("no ref") }; + }); - act(() => ReactDOM.Client.render(root, )); + test("can render react components with reducers", () => { + let container = + ReactTestingLibrary.render( + , + ); - /* Ensure initial value is passed */ - expect( - container - ->DOM.findBySelectorAndTextContent("span", "initial") - ->Option.isSome, - ) - ->toBe(true); + let counter = getByTag("main", container); + let button = getByTag("button", container); + expect(counter->innerHTML)->toBe("0"); + FireEvent.click(button); + expect(counter->innerHTML)->toBe("1"); + }); - act(() => setState("changed")); + test("can render react components with reducers and initial map state", () => { + let container = + ReactTestingLibrary.render(); - /* Ensure store is being updated */ - let nextState = getState(); - expect(nextState)->toBe("changed"); + let counter = getByRole("Counter", container); + expect(counter->innerHTML)->toBe("1"); - /* on next render */ - act(() => ReactDOM.Client.render(root, )); + let incrementButton = getByRole("Increment", container); + FireEvent.click(incrementButton); + expect(counter->innerHTML)->toBe("2"); - /* the state should be propagated to App */ - expect( - container - ->DOM.findBySelectorAndTextContent("span", "changed") - ->Option.isSome, - ) - ->toBe(true); - - finish(); + let decrementButton = getByRole("Decrement", container); + FireEvent.click(decrementButton); + expect(counter->innerHTML)->toBe("1"); }); }); diff --git a/test/ReactRouter__test.re b/test/ReactRouter__test.re index ed0900d1d..f69471371 100644 --- a/test/ReactRouter__test.re +++ b/test/ReactRouter__test.re @@ -4,14 +4,22 @@ open ReasonReactRouter; describe("it allows to create url from string", () => { test("it supports basic paths", () => { - let expected = {path: ["foo", "bar"], hash: "", search: ""}; + let expected = { + path: ["foo", "bar"], + hash: "", + search: "", + }; let generated = dangerouslyGetInitialUrl(~serverUrlString="/foo/bar", ()); expect(generated == expected)->toBe(true); }); test("it creates with search", () => { - let expected = {path: ["foo", "bar"], hash: "", search: "q=term"}; + let expected = { + path: ["foo", "bar"], + hash: "", + search: "q=term", + }; let generated = dangerouslyGetInitialUrl(~serverUrlString="/foo/bar?q=term", ()); diff --git a/test/ReactTestRenderer__test.re b/test/ReactTestRenderer__test.re deleted file mode 100644 index 2db87305d..000000000 --- a/test/ReactTestRenderer__test.re +++ /dev/null @@ -1,106 +0,0 @@ -open Jest; - -module Tester = { - [@react.component] - let make = () =>
{React.string("Tester")}
; -}; - -describe("ReactTestRenderer", () => { - open Expect; - - test("create returns ReactTestInstance", () => { - let component = ReactTestRenderer.create(); - let keys = Js.Obj.keys(ReactTestRenderer.toObject(component)); - - expect(keys) - ->toEqual( - arrayContaining([| - "_Scheduler", - "root", - "toJSON", - "toTree", - "update", - "unmount", - "unstable_flushSync", - "getInstance", - |]), - ); - }); - - test("toJSON returns test rendered JSON", () => { - let component = ReactTestRenderer.create(); - let json = ReactTestRenderer.toJSON(component); - let expected = - Js.Json.parseExn( - {| - { - "type": "div", - "props": {}, - "children": [ "Tester" ] - } - |}, - ); - expect(json == expected)->toBe(true); - }); -}); - -let element =
{React.string("Tester")}
; - -describe("reactShallowRenderer", () => { - open Expect; - - test("createRenderer", () => { - let renderer = ReactTestRenderer.Shallow.createRenderer(); - let isDefined = - renderer - |> Js.Undefined.return - |> Js.Undefined.toOption - |> Option.is_some; - expect(isDefined)->toBe(true); - }); - - test("render accepts renderer", () => { - let renderer = ReactTestRenderer.Shallow.createRenderer(); - let render = ReactTestRenderer.Shallow.render(renderer); - expect(Js.typeof(render))->toEqual("function"); - }); - - test("render will render a component", () => { - let renderer = ReactTestRenderer.Shallow.createRenderer(); - let component = - ReactTestRenderer.Shallow.render(renderer, )->Option.get; - expect(component == element)->toBe(true); - }); - - test("renderWithRenderer will render a component", () => { - let component = - ReactTestRenderer.Shallow.renderWithRenderer()->Option.get; - - expect(component == element)->toBe(true); - }); - - test("getRenderOutput returns element", () => { - let renderer = ReactTestRenderer.Shallow.createRenderer(); - - ReactTestRenderer.Shallow.render(renderer, ) |> ignore; - - let component = - ReactTestRenderer.Shallow.getRenderOutput(renderer)->Option.get; - - expect(component == element)->toBe(true); - }); - - test("unmount removes the node", () => { - let renderer = ReactTestRenderer.Shallow.createRenderer(); - - ReactTestRenderer.Shallow.render(renderer, ) |> ignore; - ReactTestRenderer.Shallow.unmount(renderer); - - let component = - ReactTestRenderer.Shallow.getRenderOutput(renderer) - ->Option.get - ->Js.Null.return; - - expect(component)->toEqual(Js.null); - }); -}); diff --git a/test/React__test.re b/test/React__test.re index 1866e07e2..184716112 100644 --- a/test/React__test.re +++ b/test/React__test.re @@ -1,12 +1,5 @@ -[@alert "-deprecated"]; - 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"]; +open Expect; module ComponentThatThrows = { exception TestError; @@ -28,7 +21,7 @@ module DummyComponentThatMapsChildren = { element, { "key": string_of_int(index), - "data-index": index, + "role": Int.to_string(index), }, ) })} @@ -47,151 +40,121 @@ module DummyContext = { [@react.component] let make = () => { let value = React.useContext(context); -
value->React.int
; +
value->React.int
; }; }; }; -describe("React", () => { - let container = ref(None); +[@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); +}; + +let getByTag = (tag, container) => { + let fn = (_string, element: Dom.element) => { + element->tagName->Js.String.toLowerCase == tag; + }; + ReactTestingLibrary.getByText(~matcher=`Func(fn), 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"; - beforeEach(prepareContainer(container)); - afterEach(cleanupContainer(container)); +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", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render(root,
"Hello world!"->React.string
) - }); - - expect( - container - ->DOM.findBySelectorAndTextContent("div", "Hello world!") - ->Option.isSome, - ) - ->toBe(true); + let container = + ReactTestingLibrary.render(
); + let div = getByRole("display", container); + expect(div->innerHTML)->toBe(""); }); test("can render null elements", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => ReactDOM.Client.render(root,
React.null
)); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "") - ->Option.isSome, - ) - ->toBe(true); + let container = + ReactTestingLibrary.render(
React.null
); + let div = getByRole("display", container); + expect(div->innerHTML)->toBe(""); }); test("can render string elements", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render(root,
"Hello"->React.string
) - }); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "Hello") - ->Option.isSome, - ) - ->toBe(true); + let container = + ReactTestingLibrary.render( +
"Hello world!"->React.string
, + ); + let div = getByRole("display", container); + expect(div->innerHTML)->toBe("Hello world!"); }); test("can render int elements", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => ReactDOM.Client.render(root,
12345->React.int
)); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "12345") - ->Option.isSome, - ) - ->toBe(true); + let container = + ReactTestingLibrary.render( +
12345->React.int
, + ); + let div = getByRole("display", container); + expect(div->innerHTML)->toBe("12345"); }); test("can render float elements", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render(root,
12.345->React.float
) - }); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "12.345") - ->Option.isSome, - ) - ->toBe(true); + let container = + ReactTestingLibrary.render( +
12.345->React.float
, + ); + let div = getByRole("display", container); + expect(div->innerHTML)->toBe("12.345"); }); test("can render array of elements", () => { - let container = getContainer(container); let array = - [|1, 2, 3|] - ->Array.map(item => { -
item->React.int
- }); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render(root,
array->React.array
) - }); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "1") - ->Option.isSome, - ) - ->toBe(true); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "2") - ->Option.isSome, - ) - ->toBe(true); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "3") - ->Option.isSome, - ) - ->toBe(true); + Array.map( + item => { +
+ item->React.int +
+ }, + [|1, 2, 3|], + ); + let container = + ReactTestingLibrary.render(
{React.array(array)}
); + let div = getByRole("1", container); + expect(div->innerHTML)->toBe("1"); + let div = getByRole("2", container); + expect(div->innerHTML)->toBe("2"); + let div = getByRole("3", container); + expect(div->innerHTML)->toBe("3"); }); - test("can clone an element", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render( - root, + test("can clone an element with data-* attributes", () => { + let container = + ReactTestingLibrary.render( React.cloneElement(
"Hello"->React.string
, {"data-name": "World"}, ), - ) - }); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent( - "div[data-name='World']", - "Hello", - ) - ->Option.isSome, - ) - ->toBe(true); + ); + let output = ReactTestingLibrary.container(container)->innerHTML; + let found = Js.String.includes(~search="data-name=\"World\"", output); + expect(found)->toBe(true); }); test("can work with React refs", () => { @@ -202,112 +165,70 @@ describe("React", () => { }); test("Children", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render( - root, + let container = + ReactTestingLibrary.render(
1->React.int
2->React.int
3->React.int
, - ) - }); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div[data-index='0']", "1") - ->Option.isSome, - ) - ->toBe(true); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div[data-index='1']", "2") - ->Option.isSome, - ) - ->toBe(true); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div[data-index='2']", "3") - ->Option.isSome, - ) - ->toBe(true); + ); + + let div = getByRole("0", container); + expect(div->innerHTML)->toBe("1"); + let div = getByRole("1", container); + expect(div->innerHTML)->toBe("2"); + let div = getByRole("2", container); + expect(div->innerHTML)->toBe("3"); }); test("Context", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render( - root, + let container = + ReactTestingLibrary.render( , - ) - }); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "10") - ->Option.isSome, - ) - ->toBe(true); + ); + + expect(getByRole("value", container)->innerHTML)->toBe("10"); }); test("Events", () => { - let container = getContainer(container); let value = ref(""); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render( - root, + let container = + ReactTestingLibrary.render( {value := event->React.Event.Form.target##value}} />, - ) - }); - - switch (container->DOM.findBySelector("input[name='test-input']")) { - | Some(input) => input->Simulate.changeWithValue("My value") - | None => () - }; - - expect(value.contents)->toEqual("My value"); + ); + + let input = getByRole("input", container); + FireEvent.change( + input, + ~eventInit={ + "target": { + "value": "Let's go!", + }, + }, + ); + + expect(value.contents)->toEqual("Let's go!"); }); test("React.Fragment with key", () => { - let container = getContainer(container); let title = Some("foo"); - let root = ReactDOM.Client.createRoot(container); - - act(() => { - ReactDOM.Client.render( - root, + let container = + ReactTestingLibrary.render( -
"Child"->React.string
+
"Child"->React.string
, - ) - }); - - expect( - container - ->DOM.findBySelectorAndPartialTextContent("div", "Child") - ->Option.isSome, - ) - ->toBe(true); + ); + + expect(getByRole("child", container)->innerHTML)->toBe("Child"); }); test("Type inference with keys", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - module Author = { type t = { name: string, @@ -320,27 +241,139 @@ describe("React", () => {
; - act(() => { - ReactDOM.Client.render( - root, + let container = + ReactTestingLibrary.render( render({ name: "Joe", imageUrl: "https://foo.png", }), - ) - }); + ); + let image = getByRole("img", container); + expect(image->getAttribute("src"))->toEqual(Some("https://foo.png")); + }); + + module Counter = { + [@react.component] + let make = () => { + let (count, setCount) = React.Uncurried.useState(() => 0); + + React.useEffect1( + () => { + document->setTitle( + "You clicked " ++ Int.to_string(count) ++ " times", + ); + None; + }, + [|count|], + ); + +
+ + {React.string(string_of_int(count))} +
; + }; + }; - expect(container->DOM.findBySelector("img")->Option.isSome)->toBe(true); + 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(); }); - try( - test("ErrorBoundary", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); + test("ErrorBoundary + Suspense", () => { + [%mel.raw "console.error = () => {}"] |> ignore; - act(() => { - ReactDOM.Client.render( - root, + try({ + let container = + ReactTestingLibrary.render( { expect( @@ -348,31 +381,23 @@ describe("React", () => { ->Js.String.includes(~search="ComponentThatThrows"), ) ->toBe(true); - "An error occured"->React.string ; + "An error occured"->React.string ; }}> , - ) - }); - - expect( - container - ->DOM.findBySelectorAndTextContent("strong", "An error occured") - ->Option.isSome, - ) - ->toBe(true); - }) - ) { - | _error => - /* We catch the exception here to not populate the error to the toplevel */ - () - }; + ); + + let error = getByRole("error", container); + expect(error->innerHTML)->toBe("An error occured"); + }) { + | _error => + /* We catch the exception here to not populate the error to the toplevel */ + () + }; + }); test( "Memo and normal components rendering with equal and different props", () => { - let container = getContainer(container); - let root = ReactDOM.Client.createRoot(container); - module Normal = { let renders = ref(0); @@ -394,27 +419,28 @@ describe("React", () => { }); }; - act(() => { - ReactDOM.Client.render( - root, + let container = + ReactTestingLibrary.render(
, - ) - }); - act(() => { - ReactDOM.Client.render( - root, -
, - ) - }); - act(() => { - ReactDOM.Client.render( - root, -
, - ) - }); + ); - expect(Normal.renders^)->toBe(3); + expect(Normal.renders^)->toBe(1); + expect(Memo.renders^)->toBe(1); + ReactTestingLibrary.rerender( +
, + container, + ); + + expect(Normal.renders^)->toBe(2); + expect(Memo.renders^)->toBe(1); + + ReactTestingLibrary.rerender( +
, + container, + ); + + expect(Normal.renders^)->toBe(3); expect(Memo.renders^)->toBe(2); }); }); diff --git a/test/dune b/test/dune index a50a84e20..0dde05c81 100644 --- a/test/dune +++ b/test/dune @@ -3,6 +3,13 @@ (target test) (module_systems (commonjs bs.js)) - (libraries reason-react reason-react.node jest melange.belt) + (libraries + reason-react + reason-react.node + jest + melange.belt + melange.dom + melange_testing_library_dom + melange_testing_library_react) (preprocess (pps melange.ppx reason-react-ppx))) diff --git a/test/jest/Jest.re b/test/jest/Jest.re index 3c9a6bc5f..fa3a29742 100644 --- a/test/jest/Jest.re +++ b/test/jest/Jest.re @@ -22,6 +22,25 @@ let testAsync: (~timeout: int=?, string, (unit => unit) => unit) => unit = (~timeout=?, name, f) => testAsyncU(name, callback => f(callback), timeout); +external testPromiseU: + ( + string, + (unit => Js.Promise.t(unit)) => Js.Promise.t(unit), + option(int) + ) => + unit = + "test"; + +let testPromise: + ( + ~timeout: int=?, + string, + (unit => Js.Promise.t(unit)) => Js.Promise.t(unit) + ) => + unit = + (~timeout=?, name, f) => + testPromiseU(name, callback => f(callback), timeout); + module Only = { [@mel.scope "describe"] external describeU: (string, (. unit) => unit) => unit = "only"; diff --git a/test/melange-testing-library/README.md b/test/melange-testing-library/README.md new file mode 100644 index 000000000..9711773d1 --- /dev/null +++ b/test/melange-testing-library/README.md @@ -0,0 +1,5 @@ +### vendored melange-testing-library + +This directory contains a vendored version of the melange-testing-library. + +The original repository is https://github.com/melange-community/melange-testing-library diff --git a/test/melange-testing-library/dom/DomTestingLibrary.re b/test/melange-testing-library/dom/DomTestingLibrary.re new file mode 100644 index 000000000..b2845661b --- /dev/null +++ b/test/melange-testing-library/dom/DomTestingLibrary.re @@ -0,0 +1,4 @@ +include Queries; +include Utils; + +module FireEvent = FireEvent; diff --git a/test/melange-testing-library/dom/DomTestingLibrary.rei b/test/melange-testing-library/dom/DomTestingLibrary.rei new file mode 100644 index 000000000..0ec173659 --- /dev/null +++ b/test/melange-testing-library/dom/DomTestingLibrary.rei @@ -0,0 +1,855 @@ +module FireEvent = FireEvent; + +module ByLabelTextQuery: { + type options = { + . + "selector": Js.undefined(string), + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + [@mel.obj] + external makeOptions: + ( + ~selector: string=?, + ~exact: bool=?, + ~normalizer: string => string=?, + unit + ) => + options; +}; +module ByPlaceholderTextQuery: { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByTextQuery: { + type options = { + . + "exact": Js.undefined(bool), + "selector": Js.undefined(string), + "ignore": Js.undefined(string), + "normalizer": Js.undefined(string => string), + }; + [@mel.obj] + external makeOptions: + ( + ~exact: bool=?, + ~selector: string=?, + ~ignore: string=?, + ~normalizer: string => string=?, + unit + ) => + options; +}; +module ByAltTextQuery: { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByTitleQuery: { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByDisplayValueQuery: { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByRoleQuery: { + type options = { + . + "checked": Js.undefined(bool), + "collapseWhitespace": Js.undefined(bool), + "exact": Js.undefined(bool), + "hidden": Js.undefined(bool), + "level": Js.undefined(int), + "pressed": Js.undefined(bool), + "name": Js.undefined(string), + "normalizer": Js.undefined(string => string), + "queryFallbacks": Js.undefined(bool), + "selected": Js.undefined(bool), + "trim": Js.undefined(bool), + }; + [@mel.obj] + external makeOptions: + ( + ~checked: bool=?, + ~collapseWhitespace: bool=?, + ~exact: bool=?, + ~hidden: bool=?, + ~level: int=?, + ~pressed: bool=?, + ~name: string=?, + ~normalizer: string => string=?, + ~queryFallbacks: bool=?, + ~selected: bool=?, + ~trim: bool=?, + unit + ) => + options; +}; +module ByTestIdQuery: { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module MutationObserver: { + type options = { + . + "attributeFilter": Js.undefined(array(string)), + "attributeOldValue": Js.undefined(bool), + "attributes": Js.undefined(bool), + "characterData": Js.undefined(bool), + "characterDataOldValue": Js.undefined(bool), + "childList": Js.undefined(bool), + "subtree": Js.undefined(bool), + }; + [@mel.obj] + external makeOptions: + ( + ~attributeFilter: array(string)=?, + ~attributeOldValue: bool=?, + ~attributes: bool=?, + ~characterData: bool=?, + ~characterDataOldValue: bool=?, + ~childList: bool=?, + ~subtree: bool=?, + unit + ) => + options; +}; +module WaitFor: { + type options = { + . + "container": Js.undefined(Dom.element), + "interval": Js.undefined(int), + "mutationObserverOptions": Js.undefined(MutationObserver.options), + "onTimeout": Js.undefined(Js.Exn.t => Js.Exn.t), + "showOriginalStackTrace": Js.undefined(bool), + "stackTraceError": Js.undefined(Js.Exn.t), + "timeout": Js.undefined(int), + }; + [@mel.obj] + external makeOptions: + ( + ~container: Dom.element=?, + ~interval: int=?, + ~mutationObserverOptions: MutationObserver.options=?, + ~onTimeout: Js.Exn.t => Js.Exn.t=?, + ~showOriginalStackTrace: bool=?, + ~stackTraceError: Js.Exn.t=?, + ~timeout: int=?, + unit + ) => + options; +}; +module WaitForElement: { + type options = { + . + "container": Js.undefined(Dom.element), + "timeout": Js.undefined(int), + }; + [@mel.obj] + external makeOptions: + ( + ~container: Dom.element=?, + ~mutationObserverInit: MutationObserver.options=?, + ~timeout: int=?, + unit + ) => + options; +}; + +let waitFor: (~options: WaitFor.options=?, unit => unit) => Js.Promise.t('a); + +let waitForPromise: + (~options: WaitFor.options=?, unit => Js.Promise.t('a)) => Js.Promise.t('b); + +let waitForElement: + (~callback: unit => 'a=?, ~options: WaitForElement.options=?, unit) => + Js.Promise.t('a); + +let waitForElementToBeRemoved: + ( + ~callback: [ + | `Func(unit => 'a) + | `Value('a) + ], + ~options: WaitFor.options=?, + unit + ) => + Js.Promise.t(unit); + +let prettyDOM: (~maxLength: int=?, Dom.element) => string; + +let logDOM: (~maxLength: int=?, Dom.element) => unit; + +module Configure: { + type options = { + . + "_disableExpensiveErrorDiagnostics": Js.undefined(bool), + "asyncUtilTimeout": Js.undefined(int), + "asyncWrapper": Js.undefined(unit => unit), + "computedStyleSupportsPseudoElements": Js.undefined(bool), + "defaultHidden": Js.undefined(bool), + "eventWrapper": Js.undefined(unit => unit), + "getElementError": Js.undefined((string, Dom.element) => Js.Exn.t), + "showOriginalStackTrace": Js.undefined(bool), + "testIdAttribute": Js.undefined(string), + "throwSuggestions": Js.undefined(bool), + }; + [@mel.obj] + external makeOptions: + ( + ~_disableExpensiveErrorDiagnostics: bool=?, + ~asyncUtilTimeout: int=?, + ~asyncWrapper: unit => unit=?, + ~computedStyleSupportsPseudoElements: bool=?, + ~defaultHidden: bool=?, + ~eventWrapper: unit => unit=?, + ~getElementError: (string, Dom.element) => Js.Exn.t=?, + ~showOriginalStackTrace: bool=?, + ~testIdAttribute: string=?, + ~throwSuggestions: bool=?, + unit + ) => + options; +}; + +let configure: + ( + ~update: [ + | `Func(Configure.options => Configure.options) + | `Object(Configure.options) + ] + ) => + unit; + +[@mel.module "@testing-library/dom"] +external getNodeText: Dom.element => string = "getNodeText"; + +/** + * ByLabelText + */ +let getByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByLabelTextQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByLabelTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByLabelTextQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByLabelTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByLabelTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByLabelTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); + +/** + * ByPlaceholderText + */ +let getByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByPlaceholderTextQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByPlaceholderTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByPlaceholderTextQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByPlaceholderTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByPlaceholderTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByPlaceholderTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); + +/** + * ByText + */ +let getByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTextQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTextQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); + +/** + * ByAltText + */ +let getByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByAltTextQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByAltTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByAltTextQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByAltTextQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByAltTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByAltTextQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); + +/** + * ByTitle + */ +let getByTitle: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTitleQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByTitle: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTitleQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByTitle: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTitleQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByTitle: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTitleQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByTitle: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTitleQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByTitle: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTitleQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); + +/** + * ByDisplayValue + */ +let getByDisplayValue: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByDisplayValueQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByDisplayValue: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByDisplayValueQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByDisplayValue: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByDisplayValueQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByDisplayValue: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByDisplayValueQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByDisplayValue: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByDisplayValueQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByDisplayValue: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByDisplayValueQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); + +/** + * ByRole + */ +let getByRole: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByRoleQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByRole: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByRoleQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByRole: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByRoleQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByRole: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByRoleQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByRole: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByRoleQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByRole: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByRoleQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); + +/** + * ByTestId + */ +let getByTestId: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTestIdQuery.options=?, + Dom.element + ) => + Dom.element; + +let getAllByTestId: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTestIdQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let queryByTestId: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTestIdQuery.options=?, + Dom.element + ) => + Js.null(Dom.element); + +let queryAllByTestId: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTestIdQuery.options=?, + Dom.element + ) => + array(Dom.element); + +let findByTestId: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTestIdQuery.options=?, + Dom.element + ) => + Js.Promise.t(Dom.element); + +let findAllByTestId: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: ByTestIdQuery.options=?, + Dom.element + ) => + Js.Promise.t(array(Dom.element)); diff --git a/test/melange-testing-library/dom/FireEvent.re b/test/melange-testing-library/dom/FireEvent.re new file mode 100644 index 000000000..0fb3b560b --- /dev/null +++ b/test/melange-testing-library/dom/FireEvent.re @@ -0,0 +1,404 @@ +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _abort: (Dom.element, Js.undefined(Js.t({..}))) => unit = "abort"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _animationEnd: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "animationEnd"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _animationIteration: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "animationIteration"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _animationStart: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "animationStart"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _blur: (Dom.element, Js.undefined(Js.t({..}))) => unit = "blur"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _canPlay: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "canPlay"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _canPlayThrough: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "canPlayThrough"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _change: (Dom.element, Js.undefined(Js.t({..}))) => unit = "change"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _click: (Dom.element, Js.undefined(Js.t({..}))) => unit = "click"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _compositionEnd: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "compositionEnd"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _compositionStart: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "compositionStart"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _compositionUpdate: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "compositionUpdate"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _contextMenu: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "contextMenu"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _copy: (Dom.element, Js.undefined(Js.t({..}))) => unit = "copy"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _cut: (Dom.element, Js.undefined(Js.t({..}))) => unit = "cut"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _dblClick: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "dblClick"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _doubleClick: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "doubleClick"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _drag: (Dom.element, Js.undefined(Js.t({..}))) => unit = "drag"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _dragEnd: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "dragEnd"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _dragEnter: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "dragEnter"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _dragExit: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "dragExit"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _dragLeave: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "dragLeave"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _dragOver: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "dragOver"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _dragStart: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "dragStart"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _drop: (Dom.element, Js.undefined(Js.t({..}))) => unit = "drop"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _durationChange: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "durationChange"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _emptied: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "emptied"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _encrypted: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "encrypted"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _ended: (Dom.element, Js.undefined(Js.t({..}))) => unit = "ended"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _error: (Dom.element, Js.undefined(Js.t({..}))) => unit = "error"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _focus: (Dom.element, Js.undefined(Js.t({..}))) => unit = "focus"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _focusIn: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "focusIn"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _focusOut: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "focusOut"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _input: (Dom.element, Js.undefined(Js.t({..}))) => unit = "input"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _invalid: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "invalid"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _keyDown: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "keyDown"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _keyPress: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "keyPress"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _keyUp: (Dom.element, Js.undefined(Js.t({..}))) => unit = "keyUp"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _load: (Dom.element, Js.undefined(Js.t({..}))) => unit = "load"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _loadStart: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "loadStart"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _loadedData: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "loadedData"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _loadedMetadata: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "loadedMetadata"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _mouseDown: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "mouseDown"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _mouseEnter: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "mouseEnter"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _mouseLeave: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "mouseLeave"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _mouseMove: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "mouseMove"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _mouseOut: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "mouseOut"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _mouseOver: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "mouseOver"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _mouseUp: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "mouseUp"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _paste: (Dom.element, Js.undefined(Js.t({..}))) => unit = "paste"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _pause: (Dom.element, Js.undefined(Js.t({..}))) => unit = "pause"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _play: (Dom.element, Js.undefined(Js.t({..}))) => unit = "play"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _playing: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "playing"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _progress: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "progress"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _rateChange: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "rateChange"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _scroll: (Dom.element, Js.undefined(Js.t({..}))) => unit = "scroll"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _seeked: (Dom.element, Js.undefined(Js.t({..}))) => unit = "seeked"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _seeking: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "seeking"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _select: (Dom.element, Js.undefined(Js.t({..}))) => unit = "select"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _stalled: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "stalled"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _submit: (Dom.element, Js.undefined(Js.t({..}))) => unit = "submit"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _suspend: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "suspend"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _timeUpdate: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "timeUpdate"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _touchCancel: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "touchCancel"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _touchEnd: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "touchEnd"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _touchMove: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "touchMove"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _touchStart: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "touchStart"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _transitionEnd: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "transitionEnd"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _volumeChange: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "volumeChange"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _waiting: (Dom.element, Js.undefined(Js.t({..}))) => unit = + "waiting"; + +[@mel.module "@testing-library/dom"] [@mel.scope "fireEvent"] +external _wheel: (Dom.element, Js.undefined(Js.t({..}))) => unit = "wheel"; + +let abort = (~eventInit=?, el) => + _abort(el, Js.Undefined.fromOption(eventInit)); +let animationEnd = (~eventInit=?, el) => + _animationEnd(el, Js.Undefined.fromOption(eventInit)); +let animationIteration = (~eventInit=?, el) => + _animationIteration(el, Js.Undefined.fromOption(eventInit)); +let animationStart = (~eventInit=?, el) => + _animationStart(el, Js.Undefined.fromOption(eventInit)); +let blur = (~eventInit=?, el) => + _blur(el, Js.Undefined.fromOption(eventInit)); +let canPlay = (~eventInit=?, el) => + _canPlay(el, Js.Undefined.fromOption(eventInit)); +let canPlayThrough = (~eventInit=?, el) => + _canPlayThrough(el, Js.Undefined.fromOption(eventInit)); +let change = (~eventInit=?, el) => + _change(el, Js.Undefined.fromOption(eventInit)); +let click = (~eventInit=?, el) => + _click(el, Js.Undefined.fromOption(eventInit)); +let compositionEnd = (~eventInit=?, el) => + _compositionEnd(el, Js.Undefined.fromOption(eventInit)); +let compositionStart = (~eventInit=?, el) => + _compositionStart(el, Js.Undefined.fromOption(eventInit)); +let compositionUpdate = (~eventInit=?, el) => + _compositionUpdate(el, Js.Undefined.fromOption(eventInit)); +let contextMenu = (~eventInit=?, el) => + _contextMenu(el, Js.Undefined.fromOption(eventInit)); +let copy = (~eventInit=?, el) => + _copy(el, Js.Undefined.fromOption(eventInit)); +let cut = (~eventInit=?, el) => + _cut(el, Js.Undefined.fromOption(eventInit)); +let dblClick = (~eventInit=?, el) => + _dblClick(el, Js.Undefined.fromOption(eventInit)); +let doubleClick = (~eventInit=?, el) => + _doubleClick(el, Js.Undefined.fromOption(eventInit)); +let drag = (~eventInit=?, el) => + _drag(el, Js.Undefined.fromOption(eventInit)); +let dragEnd = (~eventInit=?, el) => + _dragEnd(el, Js.Undefined.fromOption(eventInit)); +let dragEnter = (~eventInit=?, el) => + _dragEnter(el, Js.Undefined.fromOption(eventInit)); +let dragExit = (~eventInit=?, el) => + _dragExit(el, Js.Undefined.fromOption(eventInit)); +let dragLeave = (~eventInit=?, el) => + _dragLeave(el, Js.Undefined.fromOption(eventInit)); +let dragOver = (~eventInit=?, el) => + _dragOver(el, Js.Undefined.fromOption(eventInit)); +let dragStart = (~eventInit=?, el) => + _dragStart(el, Js.Undefined.fromOption(eventInit)); +let drop = (~eventInit=?, el) => + _drop(el, Js.Undefined.fromOption(eventInit)); +let durationChange = (~eventInit=?, el) => + _durationChange(el, Js.Undefined.fromOption(eventInit)); +let emptied = (~eventInit=?, el) => + _emptied(el, Js.Undefined.fromOption(eventInit)); +let encrypted = (~eventInit=?, el) => + _encrypted(el, Js.Undefined.fromOption(eventInit)); +let ended = (~eventInit=?, el) => + _ended(el, Js.Undefined.fromOption(eventInit)); +let error = (~eventInit=?, el) => + _error(el, Js.Undefined.fromOption(eventInit)); +let focus = (~eventInit=?, el) => + _focus(el, Js.Undefined.fromOption(eventInit)); +let focusIn = (~eventInit=?, el) => + _focusIn(el, Js.Undefined.fromOption(eventInit)); +let focusOut = (~eventInit=?, el) => + _focusOut(el, Js.Undefined.fromOption(eventInit)); +let input = (~eventInit=?, el) => + _input(el, Js.Undefined.fromOption(eventInit)); +let invalid = (~eventInit=?, el) => + _invalid(el, Js.Undefined.fromOption(eventInit)); +let keyDown = (~eventInit=?, el) => + _keyDown(el, Js.Undefined.fromOption(eventInit)); +let keyPress = (~eventInit=?, el) => + _keyPress(el, Js.Undefined.fromOption(eventInit)); +let keyUp = (~eventInit=?, el) => + _keyUp(el, Js.Undefined.fromOption(eventInit)); +let load = (~eventInit=?, el) => + _load(el, Js.Undefined.fromOption(eventInit)); +let loadStart = (~eventInit=?, el) => + _loadStart(el, Js.Undefined.fromOption(eventInit)); +let loadedData = (~eventInit=?, el) => + _loadedData(el, Js.Undefined.fromOption(eventInit)); +let loadedMetadata = (~eventInit=?, el) => + _loadedMetadata(el, Js.Undefined.fromOption(eventInit)); +let mouseDown = (~eventInit=?, el) => + _mouseDown(el, Js.Undefined.fromOption(eventInit)); +let mouseEnter = (~eventInit=?, el) => + _mouseEnter(el, Js.Undefined.fromOption(eventInit)); +let mouseLeave = (~eventInit=?, el) => + _mouseLeave(el, Js.Undefined.fromOption(eventInit)); +let mouseMove = (~eventInit=?, el) => + _mouseMove(el, Js.Undefined.fromOption(eventInit)); +let mouseOut = (~eventInit=?, el) => + _mouseOut(el, Js.Undefined.fromOption(eventInit)); +let mouseOver = (~eventInit=?, el) => + _mouseOver(el, Js.Undefined.fromOption(eventInit)); +let mouseUp = (~eventInit=?, el) => + _mouseUp(el, Js.Undefined.fromOption(eventInit)); +let paste = (~eventInit=?, el) => + _paste(el, Js.Undefined.fromOption(eventInit)); +let pause = (~eventInit=?, el) => + _pause(el, Js.Undefined.fromOption(eventInit)); +let play = (~eventInit=?, el) => + _play(el, Js.Undefined.fromOption(eventInit)); +let playing = (~eventInit=?, el) => + _playing(el, Js.Undefined.fromOption(eventInit)); +let progress = (~eventInit=?, el) => + _progress(el, Js.Undefined.fromOption(eventInit)); +let rateChange = (~eventInit=?, el) => + _rateChange(el, Js.Undefined.fromOption(eventInit)); +let scroll = (~eventInit=?, el) => + _scroll(el, Js.Undefined.fromOption(eventInit)); +let seeked = (~eventInit=?, el) => + _seeked(el, Js.Undefined.fromOption(eventInit)); +let seeking = (~eventInit=?, el) => + _seeking(el, Js.Undefined.fromOption(eventInit)); +let select = (~eventInit=?, el) => + _select(el, Js.Undefined.fromOption(eventInit)); +let stalled = (~eventInit=?, el) => + _stalled(el, Js.Undefined.fromOption(eventInit)); +let submit = (~eventInit=?, el) => + _submit(el, Js.Undefined.fromOption(eventInit)); +let suspend = (~eventInit=?, el) => + _suspend(el, Js.Undefined.fromOption(eventInit)); +let timeUpdate = (~eventInit=?, el) => + _timeUpdate(el, Js.Undefined.fromOption(eventInit)); +let touchCancel = (~eventInit=?, el) => + _touchCancel(el, Js.Undefined.fromOption(eventInit)); +let touchEnd = (~eventInit=?, el) => + _touchEnd(el, Js.Undefined.fromOption(eventInit)); +let touchMove = (~eventInit=?, el) => + _touchMove(el, Js.Undefined.fromOption(eventInit)); +let touchStart = (~eventInit=?, el) => + _touchStart(el, Js.Undefined.fromOption(eventInit)); +let transitionEnd = (~eventInit=?, el) => + _transitionEnd(el, Js.Undefined.fromOption(eventInit)); +let volumeChange = (~eventInit=?, el) => + _volumeChange(el, Js.Undefined.fromOption(eventInit)); +let waiting = (~eventInit=?, el) => + _waiting(el, Js.Undefined.fromOption(eventInit)); +let wheel = (~eventInit=?, el) => + _wheel(el, Js.Undefined.fromOption(eventInit)); diff --git a/test/melange-testing-library/dom/FireEvent.rei b/test/melange-testing-library/dom/FireEvent.rei new file mode 100644 index 000000000..1c791995b --- /dev/null +++ b/test/melange-testing-library/dom/FireEvent.rei @@ -0,0 +1,71 @@ +let abort: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let animationEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let animationIteration: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let animationStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let blur: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let canPlay: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let canPlayThrough: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let change: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let click: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let compositionEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let compositionStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let compositionUpdate: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let contextMenu: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let copy: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let cut: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let dblClick: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let doubleClick: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let drag: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let dragEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let dragEnter: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let dragExit: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let dragLeave: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let dragOver: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let dragStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let drop: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let durationChange: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let emptied: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let encrypted: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let ended: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let error: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let focus: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let focusIn: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let focusOut: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let input: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let invalid: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let keyDown: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let keyPress: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let keyUp: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let load: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let loadStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let loadedData: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let loadedMetadata: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let mouseDown: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let mouseEnter: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let mouseLeave: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let mouseMove: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let mouseOut: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let mouseOver: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let mouseUp: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let paste: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let pause: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let play: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let playing: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let progress: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let rateChange: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let scroll: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let seeked: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let seeking: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let select: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let stalled: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let submit: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let suspend: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let timeUpdate: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let touchCancel: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let touchEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let touchMove: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let touchStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let transitionEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let volumeChange: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let waiting: (~eventInit: Js.t({..})=?, Dom.element) => unit; +let wheel: (~eventInit: Js.t({..})=?, Dom.element) => unit; diff --git a/test/melange-testing-library/dom/Queries.re b/test/melange-testing-library/dom/Queries.re new file mode 100644 index 000000000..0c05fd206 --- /dev/null +++ b/test/melange-testing-library/dom/Queries.re @@ -0,0 +1,1126 @@ +module ByLabelTextQuery = { + type options = { + . + "selector": Js.undefined(string), + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + + [@mel.obj] + external makeOptions: + ( + ~selector: string=?, + ~exact: bool=?, + ~normalizer: string => string=?, + unit + ) => + options; +}; +module ByPlaceholderTextQuery = { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByTextQuery = { + type options = { + . + "exact": Js.undefined(bool), + "selector": Js.undefined(string), + "ignore": Js.undefined(string), + "normalizer": Js.undefined(string => string), + }; + + [@mel.obj] + external makeOptions: + ( + ~exact: bool=?, + ~selector: string=?, + ~ignore: string=?, + ~normalizer: string => string=?, + unit + ) => + options; +}; +module ByAltTextQuery = { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByTitleQuery = { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByDisplayValueQuery = { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; +module ByRoleQuery = { + type options = { + . + "checked": Js.undefined(bool), + "collapseWhitespace": Js.undefined(bool), + "exact": Js.undefined(bool), + "hidden": Js.undefined(bool), + "level": Js.undefined(int), + "pressed": Js.undefined(bool), + "name": Js.undefined(string), + "normalizer": Js.undefined(string => string), + "queryFallbacks": Js.undefined(bool), + "selected": Js.undefined(bool), + "trim": Js.undefined(bool), + }; + + [@mel.obj] + external makeOptions: + ( + ~checked: bool=?, + ~collapseWhitespace: bool=?, + ~exact: bool=?, + ~hidden: bool=?, + ~level: int=?, + ~pressed: bool=?, + ~name: string=?, + ~normalizer: string => string=?, + ~queryFallbacks: bool=?, + ~selected: bool=?, + ~trim: bool=?, + unit + ) => + options; +}; +module ByTestIdQuery = { + type options = { + . + "exact": Js.undefined(bool), + "normalizer": Js.undefined(string => string), + }; + + [@mel.obj] + external makeOptions: + (~exact: bool=?, ~normalizer: string => string=?, unit) => options; +}; + +[@mel.module "@testing-library/dom"] +external getNodeText: Dom.element => string = "getNodeText"; + +/** + * ByLabelText + */ +[@mel.module "@testing-library/dom"] +external _getByLabelText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Dom.element = + "getByLabelText"; + +let getByLabelText = (~matcher, ~options=?, element) => + _getByLabelText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _getAllByLabelText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + array(Dom.element) = + "getAllByLabelText"; + +let getAllByLabelText = (~matcher, ~options=?, element) => + _getAllByLabelText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByLabelText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByLabelText"; + +let queryByLabelText = (~matcher, ~options=?, element) => + _queryByLabelText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryAllByLabelText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + array(Dom.element) = + "queryAllByLabelText"; + +let queryAllByLabelText = (~matcher, ~options=?, element) => + _queryAllByLabelText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByLabelText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByLabelText"; + +let findByLabelText = (~matcher, ~options=?, element) => + _findByLabelText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findAllByLabelText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByLabelText"; + +let findAllByLabelText = (~matcher, ~options=?, element) => + _findAllByLabelText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +/** + * ByPlaceholderText + */ +[@mel.module "@testing-library/dom"] +external _getByPlaceholderText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Dom.element = + "getByPlaceholderText"; + +let getByPlaceholderText = (~matcher, ~options=?, element) => + _getByPlaceholderText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _getAllByPlaceholderText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + array(Dom.element) = + "getAllByPlaceholderText"; + +let getAllByPlaceholderText = (~matcher, ~options=?, element) => + _getAllByPlaceholderText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByPlaceholderText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByPlaceholderText"; + +let queryByPlaceholderText = (~matcher, ~options=?, element) => + _queryByPlaceholderText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryAllByPlaceholderText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + array(Dom.element) = + "queryAllByPlaceholderText"; + +let queryAllByPlaceholderText = (~matcher, ~options=?, element) => + _queryAllByPlaceholderText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByPlaceholderText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByPlaceholderText"; + +let findByPlaceholderText = (~matcher, ~options=?, element) => + _findByPlaceholderText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findAllByPlaceholderText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByPlaceholderText"; + +let findAllByPlaceholderText = (~matcher, ~options=?, element) => + _findAllByPlaceholderText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +/** + * ByText + */ +[@mel.module "@testing-library/dom"] +external _getByText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Dom.element = + "getByText"; + +let getByText = (~matcher, ~options=?, element) => + _getByText(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _getAllByText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + array(Dom.element) = + "getAllByText"; + +let getAllByText = (~matcher, ~options=?, element) => + _getAllByText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByText"; + +let queryByText = (~matcher, ~options=?, element) => + _queryByText(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _queryAllByText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + array(Dom.element) = + "queryAllByText"; + +let queryAllByText = (~matcher, ~options=?, element) => + _queryAllByText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByText"; + +let findByText = (~matcher, ~options=?, element) => + _findByText(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _findAllByText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByText"; + +let findAllByText = (~matcher, ~options=?, element) => + _findAllByText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +/** + * ByAltText + */ +[@mel.module "@testing-library/dom"] +external _getByAltText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Dom.element = + "getByAltText"; + +let getByAltText = (~matcher, ~options=?, element) => + _getByAltText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _getAllByAltText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + array(Dom.element) = + "getAllByAltText"; + +let getAllByAltText = (~matcher, ~options=?, element) => + _getAllByAltText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByAltText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByAltText"; + +let queryByAltText = (~matcher, ~options=?, element) => + _queryByAltText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryAllByAltText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + array(Dom.element) = + "queryAllByAltText"; + +let queryAllByAltText = (~matcher, ~options=?, element) => + _queryAllByAltText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByAltText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByAltText"; + +let findByAltText = (~matcher, ~options=?, element) => + _findByAltText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findAllByAltText: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByAltText"; + +let findAllByAltText = (~matcher, ~options=?, element) => + _findAllByAltText( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +/** + * ByTitle + */ +[@mel.module "@testing-library/dom"] +external _getByTitle: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Dom.element = + "getByTitle"; + +let getByTitle = (~matcher, ~options=?, element) => + _getByTitle(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _getAllByTitle: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + array(Dom.element) = + "getAllByTitle"; + +let getAllByTitle = (~matcher, ~options=?, element) => + _getAllByTitle( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByTitle: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Js.null(Dom.element) = + "queryByTitle"; + +let queryByTitle = (~matcher, ~options=?, element) => + _queryByTitle( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryAllByTitle: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + array(Dom.element) = + "queryAllByTitle"; + +let queryAllByTitle = (~matcher, ~options=?, element) => + _queryAllByTitle( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByTitle: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByTitle"; + +let findByTitle = (~matcher, ~options=?, element) => + _findByTitle(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _findAllByTitle: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByTitle"; + +let findAllByTitle = (~matcher, ~options=?, element) => + _findAllByTitle( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +/** + * ByDisplayValue + */ +[@mel.module "@testing-library/dom"] +external _getByDisplayValue: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Dom.element = + "getByDisplayValue"; + +let getByDisplayValue = (~matcher, ~options=?, element) => + _getByDisplayValue( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _getAllByDisplayValue: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + array(Dom.element) = + "getAllByDisplayValue"; + +let getAllByDisplayValue = (~matcher, ~options=?, element) => + _getAllByDisplayValue( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByDisplayValue: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Js.null(Dom.element) = + "queryByDisplayValue"; + +let queryByDisplayValue = (~matcher, ~options=?, element) => + _queryByDisplayValue( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryAllByDisplayValue: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + array(Dom.element) = + "queryAllByDisplayValue"; + +let queryAllByDisplayValue = (~matcher, ~options=?, element) => + _queryAllByDisplayValue( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByDisplayValue: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByDisplayValue"; + +let findByDisplayValue = (~matcher, ~options=?, element) => + _findByDisplayValue( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findAllByDisplayValue: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByDisplayValue"; + +let findAllByDisplayValue = (~matcher, ~options=?, element) => + _findAllByDisplayValue( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +/** + * ByRole + */ +[@mel.module "@testing-library/dom"] +external _getByRole: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Dom.element = + "getByRole"; + +let getByRole = (~matcher, ~options=?, element) => + _getByRole(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _getAllByRole: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + array(Dom.element) = + "getAllByRole"; + +let getAllByRole = (~matcher, ~options=?, element) => + _getAllByRole( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByRole: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Js.null(Dom.element) = + "queryByRole"; + +let queryByRole = (~matcher, ~options=?, element) => + _queryByRole(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _queryAllByRole: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + array(Dom.element) = + "queryAllByRole"; + +let queryAllByRole = (~matcher, ~options=?, element) => + _queryAllByRole( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByRole: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByRole"; + +let findByRole = (~matcher, ~options=?, element) => + _findByRole(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _findAllByRole: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByRole"; + +let findAllByRole = (~matcher, ~options=?, element) => + _findAllByRole( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +/** + * ByTestId + */ +[@mel.module "@testing-library/dom"] +external _getByTestId: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Dom.element = + "getByTestId"; + +let getByTestId = (~matcher, ~options=?, element) => + _getByTestId(element, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _getAllByTestId: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + array(Dom.element) = + "getAllByTestId"; + +let getAllByTestId = (~matcher, ~options=?, element) => + _getAllByTestId( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryByTestId: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Js.null(Dom.element) = + "queryByTestId"; + +let queryByTestId = (~matcher, ~options=?, element) => + _queryByTestId( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _queryAllByTestId: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + array(Dom.element) = + "queryAllByTestId"; + +let queryAllByTestId = (~matcher, ~options=?, element) => + _queryAllByTestId( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findByTestId: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByTestId"; + +let findByTestId = (~matcher, ~options=?, element) => + _findByTestId( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _findAllByTestId: + ( + Dom.element, + ~matcher: [@mel.unwrap] [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByTestId"; + +let findAllByTestId = (~matcher, ~options=?, element) => + _findAllByTestId( + element, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); diff --git a/test/melange-testing-library/dom/Utils.re b/test/melange-testing-library/dom/Utils.re new file mode 100644 index 000000000..b4de999ed --- /dev/null +++ b/test/melange-testing-library/dom/Utils.re @@ -0,0 +1,180 @@ +module MutationObserver = { + type options = { + . + "attributeFilter": Js.undefined(array(string)), + "attributeOldValue": Js.undefined(bool), + "attributes": Js.undefined(bool), + "characterData": Js.undefined(bool), + "characterDataOldValue": Js.undefined(bool), + "childList": Js.undefined(bool), + "subtree": Js.undefined(bool), + }; + + [@mel.obj] + external makeOptions: + ( + ~attributeFilter: array(string)=?, + ~attributeOldValue: bool=?, + ~attributes: bool=?, + ~characterData: bool=?, + ~characterDataOldValue: bool=?, + ~childList: bool=?, + ~subtree: bool=?, + unit + ) => + options; +}; + +module WaitFor = { + type options = { + . + "container": Js.undefined(Dom.element), + "interval": Js.undefined(int), + "mutationObserverOptions": Js.undefined(MutationObserver.options), + "onTimeout": Js.undefined(Js.Exn.t => Js.Exn.t), + "showOriginalStackTrace": Js.undefined(bool), + "stackTraceError": Js.undefined(Js.Exn.t), + "timeout": Js.undefined(int), + }; + + [@mel.obj] + external makeOptions: + ( + ~container: Dom.element=?, + ~interval: int=?, + ~mutationObserverOptions: MutationObserver.options=?, + ~onTimeout: Js.Exn.t => Js.Exn.t=?, + ~showOriginalStackTrace: bool=?, + ~stackTraceError: Js.Exn.t=?, + ~timeout: int=?, + unit + ) => + options; +}; + +module WaitForElement = { + type options = { + . + "container": Js.undefined(Dom.element), + "timeout": Js.undefined(int), + }; + + [@mel.obj] + external makeOptions: + ( + ~container: Dom.element=?, + ~mutationObserverInit: MutationObserver.options=?, + ~timeout: int=?, + unit + ) => + options; +}; + +[@mel.module "@testing-library/dom"] +external _waitFor: + (unit => unit, Js.undefined(WaitFor.options)) => Js.Promise.t('a) = + "waitFor"; + +let waitFor = (~options=?, callback) => + _waitFor(callback, Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _waitForPromise: + (unit => Js.Promise.t('a), Js.undefined(WaitFor.options)) => + Js.Promise.t('b) = + "waitFor"; + +let waitForPromise = (~options=?, callback) => + _waitForPromise(callback, Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _waitForElement: + (Js.undefined(unit => 'a), Js.undefined(WaitForElement.options)) => + Js.Promise.t('a) = + "waitForElement"; + +let waitForElement = (~callback=?, ~options=?, ()) => + _waitForElement( + Js.Undefined.fromOption(callback), + Js.Undefined.fromOption(options), + ); + +[@mel.module "@testing-library/dom"] +external _waitForElementToBeRemoved: + ( + ~callback: + [@mel.unwrap] [ + | `Func(unit => 'a) + | `Value('a) + ], + Js.undefined(WaitFor.options) + ) => + Js.Promise.t(unit) = + "waitForElementToBeRemoved"; + +let waitForElementToBeRemoved = (~callback, ~options=?, ()) => + _waitForElementToBeRemoved(~callback, Js.Undefined.fromOption(options)); + +[@mel.module "@testing-library/dom"] +external _prettyDOM: (Dom.element, Js.undefined(int)) => string = "prettyDOM"; + +let prettyDOM = (~maxLength=?, element) => + _prettyDOM(element, Js.Undefined.fromOption(maxLength)); + +[@mel.module "@testing-library/dom"] +external _logDOM: (Dom.element, Js.undefined(int)) => unit = "logDOM"; + +let logDOM = (~maxLength=?, element) => + _logDOM(element, Js.Undefined.fromOption(maxLength)); + +module Configure = { + type options = { + . + "_disableExpensiveErrorDiagnostics": Js.undefined(bool), + "asyncUtilTimeout": Js.undefined(int), + "asyncWrapper": Js.undefined(unit => unit), + "computedStyleSupportsPseudoElements": Js.undefined(bool), + "defaultHidden": Js.undefined(bool), + "eventWrapper": Js.undefined(unit => unit), + "getElementError": Js.undefined((string, Dom.element) => Js.Exn.t), + "showOriginalStackTrace": Js.undefined(bool), + "testIdAttribute": Js.undefined(string), + "throwSuggestions": Js.undefined(bool), + }; + + [@mel.obj] + external makeOptions: + ( + ~_disableExpensiveErrorDiagnostics: bool=?, + ~asyncUtilTimeout: int=?, + ~asyncWrapper: unit => unit=?, + ~computedStyleSupportsPseudoElements: bool=?, + ~defaultHidden: bool=?, + ~eventWrapper: unit => unit=?, + ~getElementError: (string, Dom.element) => Js.Exn.t=?, + ~showOriginalStackTrace: bool=?, + ~testIdAttribute: string=?, + ~throwSuggestions: bool=?, + unit + ) => + options; +}; + +[@mel.module "@testing-library/dom"] +external configureWithFn: (Js.t({..}) => Js.t({..})) => unit = "configure"; + +[@mel.module "@testing-library/dom"] +external configureWithObject: Configure.options => unit = "configure"; + +let configure = + ( + ~update: [ + | `Func(Configure.options => Configure.options) + | `Object(Configure.options) + ], + ) => { + switch (update) { + | `Func(fn) => configureWithFn(fn) + | `Object(obj) => configureWithObject(obj) + }; +}; diff --git a/test/melange-testing-library/dom/dune b/test/melange-testing-library/dom/dune new file mode 100644 index 000000000..8ae145e24 --- /dev/null +++ b/test/melange-testing-library/dom/dune @@ -0,0 +1,7 @@ +(library + (name melange_testing_library_dom) + (wrapped false) + (modes melange) + (libraries melange.dom) + (preprocess + (pps melange.ppx))) diff --git a/test/melange-testing-library/react/ReactTestingLibrary.re b/test/melange-testing-library/react/ReactTestingLibrary.re new file mode 100644 index 000000000..879157830 --- /dev/null +++ b/test/melange-testing-library/react/ReactTestingLibrary.re @@ -0,0 +1,1044 @@ +open DomTestingLibrary; + +module FireEvent = FireEvent; + +type renderResult; +type queries; +type renderOptions = { + . + "container": Js.undefined(Dom.element), + "baseElement": Js.undefined(Dom.element), + "hydrate": Js.undefined(bool), + "wrapper": Js.undefined(Dom.element), + "queries": Js.undefined(queries), +}; + +[@mel.module "@testing-library/react"] +external cleanup: unit => unit = "cleanup"; + +[@mel.module "@testing-library/react"] +external actAsync: (unit => Js.Promise.t('a)) => Js.Promise.t(unit) = "act"; + +[@mel.module "@testing-library/react"] +external _act: (unit => Js.undefined(Js.Promise.t('a))) => unit = "act"; + +let act = callback => + _act(() => { + callback(); + // (work-around) BuckleScript compiles `unit` to `0`, this will cause a warning as following: + // Warning: The callback passed to act(...) function must return undefined, or a Promise. + Js.Undefined.empty; + }); + +[@mel.module "@testing-library/react"] +external _render: (React.element, renderOptions) => renderResult = "render"; + +[@mel.get] external container: renderResult => Dom.element = "container"; + +[@mel.get] external baseElement: renderResult => Dom.element = "baseElement"; + +[@mel.send.pipe: renderResult] +external _debug: (Js.undefined(Dom.element), Js.undefined(int)) => unit = + "debug"; + +[@mel.send.pipe: renderResult] external unmount: unit => bool = "unmount"; + +[@mel.send.pipe: renderResult] +external rerender: React.element => unit = "rerender"; + +[@mel.send.pipe: renderResult] +external asFragment: unit => Dom.element = "asFragment"; + +// ByLabelText +[@mel.send.pipe: renderResult] +external _getByLabelText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Dom.element = + "getByLabelText"; + +let getByLabelText = (~matcher, ~options=?, result) => + _getByLabelText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _getAllByLabelText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + array(Dom.element) = + "getAllByLabelText"; + +let getAllByLabelText = (~matcher, ~options=?, result) => + _getAllByLabelText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryByLabelText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByLabelText"; + +let queryByLabelText = (~matcher, ~options=?, result) => + _queryByLabelText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryAllByLabelText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + array(Dom.element) = + "queryAllByLabelText"; + +let queryAllByLabelText = (~matcher, ~options=?, result) => + _queryAllByLabelText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByLabelText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByLabelText"; + +let findByLabelText = (~matcher, ~options=?, result) => + _findByLabelText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findAllByLabelText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByLabelTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByLabelText"; + +let findAllByLabelText = (~matcher, ~options=?, result) => + _findAllByLabelText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +// ByPlaceholderText +[@mel.send.pipe: renderResult] +external _getByPlaceholderText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Dom.element = + "getByPlaceholderText"; + +let getByPlaceholderText = (~matcher, ~options=?, result) => + _getByPlaceholderText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _getAllByPlaceholderText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + array(Dom.element) = + "getAllByPlaceholderText"; + +let getAllByPlaceholderText = (~matcher, ~options=?, result) => + _getAllByPlaceholderText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryByPlaceholderText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByPlaceholderText"; + +let queryByPlaceholderText = (~matcher, ~options=?, result) => + _queryByPlaceholderText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryAllByPlaceholderText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + array(Dom.element) = + "queryAllByPlaceholderText"; + +let queryAllByPlaceholderText = (~matcher, ~options=?, result) => + _queryAllByPlaceholderText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByPlaceholderText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByPlaceholderText"; + +let findByPlaceholderText = (~matcher, ~options=?, result) => + _findByPlaceholderText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findAllByPlaceholderText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByPlaceholderTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByPlaceholderText"; + +let findAllByPlaceholderText = (~matcher, ~options=?, result) => + _findAllByPlaceholderText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +// ByText +[@mel.send.pipe: renderResult] +external _getByText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Dom.element = + "getByText"; + +let getByText = (~matcher, ~options=?, result) => + _getByText(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _getAllByText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + array(Dom.element) = + "getAllByText"; + +let getAllByText = (~matcher, ~options=?, result) => + _getAllByText(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _queryByText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByText"; + +let queryByText = (~matcher, ~options=?, result) => + _queryByText(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _queryAllByText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + array(Dom.element) = + "queryAllByText"; + +let queryAllByText = (~matcher, ~options=?, result) => + _queryAllByText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByText"; + +let findByText = (~matcher, ~options=?, result) => + _findByText(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _findAllByText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByText"; + +let findAllByText = (~matcher, ~options=?, result) => + _findAllByText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +// ByAltText +[@mel.send.pipe: renderResult] +external _getByAltText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Dom.element = + "getByAltText"; + +let getByAltText = (~matcher, ~options=?, result) => + _getByAltText(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _getAllByAltText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + array(Dom.element) = + "getAllByAltText"; + +let getAllByAltText = (~matcher, ~options=?, result) => + _getAllByAltText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryByAltText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Js.null(Dom.element) = + "queryByAltText"; + +let queryByAltText = (~matcher, ~options=?, result) => + _queryByAltText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryAllByAltText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + array(Dom.element) = + "queryAllByAltText"; + +let queryAllByAltText = (~matcher, ~options=?, result) => + _queryAllByAltText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByAltText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByAltText"; + +let findByAltText = (~matcher, ~options=?, result) => + _findByAltText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findAllByAltText: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByAltTextQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByAltText"; + +let findAllByAltText = (~matcher, ~options=?, result) => + _findAllByAltText( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +// ByTitle +[@mel.send.pipe: renderResult] +external _getByTitle: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Dom.element = + "getByTitle"; + +let getByTitle = (~matcher, ~options=?, result) => + _getByTitle(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _getAllByTitle: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + array(Dom.element) = + "getAllByTitle"; + +let getAllByTitle = (~matcher, ~options=?, result) => + _getAllByTitle( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryByTitle: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Js.null(Dom.element) = + "queryByTitle"; + +let queryByTitle = (~matcher, ~options=?, result) => + _queryByTitle(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _queryAllByTitle: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + array(Dom.element) = + "queryAllByTitle"; + +let queryAllByTitle = (~matcher, ~options=?, result) => + _queryAllByTitle( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByTitle: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByTitle"; + +let findByTitle = (~matcher, ~options=?, result) => + _findByTitle(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _findAllByTitle: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTitleQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByTitle"; + +let findAllByTitle = (~matcher, ~options=?, result) => + _findAllByTitle( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +// ByDisplayValue +[@mel.send.pipe: renderResult] +external _getByDisplayValue: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Dom.element = + "getByDisplayValue"; + +let getByDisplayValue = (~matcher, ~options=?, result) => + _getByDisplayValue( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _getAllByDisplayValue: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + array(Dom.element) = + "getAllByDisplayValue"; + +let getAllByDisplayValue = (~matcher, ~options=?, result) => + _getAllByDisplayValue( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryByDisplayValue: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Js.null(Dom.element) = + "queryByDisplayValue"; + +let queryByDisplayValue = (~matcher, ~options=?, result) => + _queryByDisplayValue( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryAllByDisplayValue: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + array(Dom.element) = + "queryAllByDisplayValue"; + +let queryAllByDisplayValue = (~matcher, ~options=?, result) => + _queryAllByDisplayValue( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByDisplayValue: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByDisplayValue"; + +let findByDisplayValue = (~matcher, ~options=?, result) => + _findByDisplayValue( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findAllByDisplayValue: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByDisplayValueQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByDisplayValue"; + +let findAllByDisplayValue = (~matcher, ~options=?, result) => + _findAllByDisplayValue( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +// ByRole +[@mel.send.pipe: renderResult] +external _getByRole: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Dom.element = + "getByRole"; + +let getByRole = (~matcher, ~options=?, result) => + _getByRole(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _getAllByRole: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + array(Dom.element) = + "getAllByRole"; + +let getAllByRole = (~matcher, ~options=?, result) => + _getAllByRole(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _queryByRole: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Js.null(Dom.element) = + "queryByRole"; + +let queryByRole = (~matcher, ~options=?, result) => + _queryByRole(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _queryAllByRole: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + array(Dom.element) = + "queryAllByRole"; + +let queryAllByRole = (~matcher, ~options=?, result) => + _queryAllByRole( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByRole: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByRole"; + +let findByRole = (~matcher, ~options=?, result) => + _findByRole(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _findAllByRole: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByRoleQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByRole"; + +let findAllByRole = (~matcher, ~options=?, result) => + _findAllByRole( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +// ByTestId +[@mel.send.pipe: renderResult] +external _getByTestId: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Dom.element = + "getByTestId"; + +let getByTestId = (~matcher, ~options=?, result) => + _getByTestId(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _getAllByTestId: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + array(Dom.element) = + "getAllByTestId"; + +let getAllByTestId = (~matcher, ~options=?, result) => + _getAllByTestId( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryByTestId: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Js.null(Dom.element) = + "queryByTestId"; + +let queryByTestId = (~matcher, ~options=?, result) => + _queryByTestId( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _queryAllByTestId: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + array(Dom.element) = + "queryAllByTestId"; + +let queryAllByTestId = (~matcher, ~options=?, result) => + _queryAllByTestId( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +[@mel.send.pipe: renderResult] +external _findByTestId: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Js.Promise.t(Dom.element) = + "findByTestId"; + +let findByTestId = (~matcher, ~options=?, result) => + _findByTestId(result, ~matcher, ~options=Js.Undefined.fromOption(options)); + +[@mel.send.pipe: renderResult] +external _findAllByTestId: + ( + ~matcher: + [@mel.unwrap] [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: Js.undefined(ByTestIdQuery.options) + ) => + Js.Promise.t(array(Dom.element)) = + "findAllByTestId"; + +let findAllByTestId = (~matcher, ~options=?, result) => + _findAllByTestId( + result, + ~matcher, + ~options=Js.Undefined.fromOption(options), + ); + +let render = + ( + ~baseElement=?, + ~container=?, + ~hydrate=?, + ~wrapper=?, + ~queries=?, + element, + ) => { + let baseElement_ = + switch (container) { + | Some(container') => Js.Undefined.return(container') + | None => Js.Undefined.fromOption(baseElement) + }; + let container_ = Js.Undefined.fromOption(container); + + _render( + element, + { + "baseElement": baseElement_, + "container": container_, + "hydrate": Js.Undefined.fromOption(hydrate), + "wrapper": Js.Undefined.fromOption(wrapper), + "queries": Js.Undefined.fromOption(queries), + }, + ); +}; + +let debug = (~el=?, ~maxLengthToPrint=?) => + _debug( + Js.Undefined.fromOption(el), + Js.Undefined.fromOption(maxLengthToPrint), + ); diff --git a/test/melange-testing-library/react/ReactTestingLibrary.rei b/test/melange-testing-library/react/ReactTestingLibrary.rei new file mode 100644 index 000000000..e2608f7e5 --- /dev/null +++ b/test/melange-testing-library/react/ReactTestingLibrary.rei @@ -0,0 +1,702 @@ +module FireEvent: { + let abort: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let animationEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let animationIteration: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let animationStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let blur: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let canPlay: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let canPlayThrough: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let change: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let click: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let compositionEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let compositionStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let compositionUpdate: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let contextMenu: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let copy: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let cut: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let dblClick: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let doubleClick: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let drag: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let dragEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let dragEnter: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let dragExit: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let dragLeave: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let dragOver: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let dragStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let drop: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let durationChange: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let emptied: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let encrypted: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let ended: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let error: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let focus: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let focusIn: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let focusOut: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let input: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let invalid: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let keyDown: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let keyPress: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let keyUp: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let load: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let loadStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let loadedData: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let loadedMetadata: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let mouseDown: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let mouseEnter: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let mouseLeave: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let mouseMove: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let mouseOut: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let mouseOver: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let mouseUp: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let paste: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let pause: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let play: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let playing: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let progress: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let rateChange: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let scroll: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let seeked: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let seeking: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let select: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let stalled: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let submit: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let suspend: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let timeUpdate: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let touchCancel: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let touchEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let touchMove: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let touchStart: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let transitionEnd: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let volumeChange: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let waiting: (~eventInit: Js.t({..})=?, Dom.element) => unit; + let wheel: (~eventInit: Js.t({..})=?, Dom.element) => unit; +}; + +type renderResult; +type queries; +type renderOptions = { + . + "container": Js.undefined(Dom.element), + "baseElement": Js.undefined(Dom.element), + "hydrate": Js.undefined(bool), + "wrapper": Js.undefined(Dom.element), + "queries": Js.undefined(queries), +}; + +[@mel.module "@testing-library/react"] +external cleanup: unit => unit = "cleanup"; + +[@mel.module "@testing-library/react"] +external actAsync: (unit => Js.Promise.t('a)) => Js.Promise.t(unit) = "act"; + +let act: (unit => unit) => unit; + +[@mel.get] external container: renderResult => Dom.element = "container"; + +[@mel.get] external baseElement: renderResult => Dom.element = "baseElement"; + +[@mel.send.pipe: renderResult] external unmount: unit => bool = "unmount"; + +[@mel.send.pipe: renderResult] +external asFragment: unit => Dom.element = "asFragment"; + +// ByLabelText +let getByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByLabelTextQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByLabelTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByLabelTextQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByLabelTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByLabelTextQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByLabelText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByLabelTextQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +// ByPlaceholderText +let getByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByPlaceholderTextQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByPlaceholderTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByPlaceholderTextQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByPlaceholderTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByPlaceholderTextQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByPlaceholderText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByPlaceholderTextQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +// ByText +let getByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByTextQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByTextQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByTextQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByTextQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +// ByAltText +let getByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByAltTextQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByAltTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByAltTextQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByAltTextQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByAltTextQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByAltText: + ( + ~matcher: [ + | `Func((string, Dom.element) => bool) + | `RegExp(Js.Re.t) + | `Str(string) + ], + ~options: DomTestingLibrary.ByAltTextQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +// ByTitle +let getByTitle: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTitleQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByTitle: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTitleQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByTitle: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTitleQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByTitle: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTitleQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByTitle: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTitleQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByTitle: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTitleQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +// ByDisplayValue +let getByDisplayValue: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByDisplayValueQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByDisplayValue: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByDisplayValueQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByDisplayValue: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByDisplayValueQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByDisplayValue: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByDisplayValueQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByDisplayValue: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByDisplayValueQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByDisplayValue: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByDisplayValueQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +// ByRole +let getByRole: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByRoleQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByRole: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByRoleQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByRole: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByRoleQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByRole: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByRoleQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByRole: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByRoleQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByRole: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByRoleQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +// ByTestId +let getByTestId: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTestIdQuery.options=?, + renderResult + ) => + Dom.element; + +let getAllByTestId: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTestIdQuery.options=?, + renderResult + ) => + array(Dom.element); + +let queryByTestId: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTestIdQuery.options=?, + renderResult + ) => + Js.null(Dom.element); + +let queryAllByTestId: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTestIdQuery.options=?, + renderResult + ) => + array(Dom.element); + +let findByTestId: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTestIdQuery.options=?, + renderResult + ) => + Js.Promise.t(Dom.element); + +let findAllByTestId: + ( + ~matcher: [ + | `Str(string) + | `RegExp(Js.Re.t) + | `Func((string, Dom.element) => bool) + ], + ~options: DomTestingLibrary.ByTestIdQuery.options=?, + renderResult + ) => + Js.Promise.t(array(Dom.element)); + +[@mel.send.pipe: renderResult] +external rerender: React.element => unit = "rerender"; + +let render: + ( + ~baseElement: Dom.element=?, + ~container: Dom.element=?, + ~hydrate: bool=?, + ~wrapper: Dom.element=?, + ~queries: queries=?, + React.element + ) => + renderResult; + +let debug: + (~el: Dom.element=?, ~maxLengthToPrint: int=?, renderResult) => unit; diff --git a/test/melange-testing-library/react/dune b/test/melange-testing-library/react/dune new file mode 100644 index 000000000..ecc0a0ab4 --- /dev/null +++ b/test/melange-testing-library/react/dune @@ -0,0 +1,7 @@ +(library + (name melange_testing_library_react) + (libraries reason-react melange_testing_library_dom) + (wrapped false) + (modes melange) + (preprocess + (pps melange.ppx))) From 08f5e196af91fe4ae05109545477a186f029eea4 Mon Sep 17 00:00:00 2001 From: David Sancho Date: Mon, 25 Nov 2024 19:01:49 +0100 Subject: [PATCH 8/8] Enable ref as valid prop (#862) * Install melange-testing-library * Install melange-testing-library npm deps * Vendor melange-testing-library * Fix Form__test with RTL * Start migrating Hooks__test * Remove dependency * Remove unused code from Form__test * Add a jest-devtoolsgs * Add a jest-devtools * Migrate Hooks and Form into RTL * Add demo to manually test easily * Use Uncurried for tests * Migrate all React__test * Force install since we are dealing with R19 * Snapshot with lower {} * Enable ref in ppx * Add jest test for ref * Remove jest from demo/dune * Add comment on install --force * Improve test from checking ref * Bind React.act and React.actAsync * Bind React.act and React.actAsync --- ppx/reason_react_ppx.ml | 5 ----- ppx/test/component.t/input.re | 7 +++++++ ppx/test/component.t/run.t | 21 +++++++++++++++++++++ test/Ref__test.re | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 test/Ref__test.re diff --git a/ppx/reason_react_ppx.ml b/ppx/reason_react_ppx.ml index 9646da10c..b66fa424a 100644 --- a/ppx/reason_react_ppx.ml +++ b/ppx/reason_react_ppx.ml @@ -653,11 +653,6 @@ let jsxMapper = (Invalid_argument "Key cannot be accessed inside of a component. Don't worry - you \ can always key a component from its parent!") - | Pexp_fun (Labelled "ref", _, _, _) | Pexp_fun (Optional "ref", _, _, _) -> - raise - (Invalid_argument - "Ref cannot be passed as a normal prop. Please use `forwardRef` \ - API instead.") | Pexp_fun ( ((Optional label | Labelled label) as arg), default, diff --git a/ppx/test/component.t/input.re b/ppx/test/component.t/input.re index 7fdccd901..3c9839e68 100644 --- a/ppx/test/component.t/input.re +++ b/ppx/test/component.t/input.re @@ -41,6 +41,13 @@ module Forward_Ref = { }); }; +module Ref_as_prop = { + [@react.component] + let make = (~children, ~ref) => { + ; + }; +}; + module Onclick_handler_button = { [@react.component] let make = (~name, ~isDisabled=?) => { diff --git a/ppx/test/component.t/run.t b/ppx/test/component.t/run.t index aa9583ce1..22e4bcbf5 100644 --- a/ppx/test/component.t/run.t +++ b/ppx/test/component.t/run.t @@ -68,6 +68,27 @@ We need to output ML syntax here, otherwise refmt could not parse it. make ~buttonRef:(Props ## buttonRef) ~children:(Props ## children) in Output$Forward_Ref) end + module Ref_as_prop = + struct + external makeProps : + children:'children -> + ref:'ref -> + ?key:string -> unit -> < children: 'children ;ref: 'ref > Js.t + = ""[@@mel.obj ] + let make = + ((fun ~children -> + ((fun ~ref -> + ReactDOM.jsx "button" + (((ReactDOM.domProps)[@merlin.hide ]) ~children ~ref + ~className:"FancyButton" ())) + [@warning "-16"])) + [@warning "-16"]) + let make = + let Output$Ref_as_prop + (Props : < children: 'children ;ref: 'ref > Js.t) = + make ~ref:(Props ## ref) ~children:(Props ## children) in + Output$Ref_as_prop + end module Onclick_handler_button = struct external makeProps : diff --git a/test/Ref__test.re b/test/Ref__test.re new file mode 100644 index 000000000..223a5f348 --- /dev/null +++ b/test/Ref__test.re @@ -0,0 +1,35 @@ +open Jest; +open Expect; + +module Button_with_ref = { + [@react.component] + let make = (~children, ~ref) => { + ; + }; +}; + +let getByRole = (role, container) => { + ReactTestingLibrary.getByRole(~matcher=`Str(role), container); +}; + +[@mel.get] external innerHTML: Dom.element => string = "innerHTML"; + +describe("ref", () => { + test("can be passed as prop to a component", () => { + let domRef = React.createRef(); + let container = + ReactTestingLibrary.render( + + {React.string("Click me")} + , + ); + let button = getByRole("FancyButton", container); + expect(button->innerHTML)->toBe("Click me"); + let content = + switch (Js.Nullable.toOption(domRef.current)) { + | Some(element) => element->innerHTML + | None => failwith("No element found") + }; + expect(content)->toBe("Click me"); + }) +});