diff --git a/.ocamlformat b/.ocamlformat index 3fe595c05..ed7d4b31d 100644 --- a/.ocamlformat +++ b/.ocamlformat @@ -1 +1 @@ -version = 0.24.0 +version = 0.26.0 diff --git a/dune-project b/dune-project index f18444141..993da5cdc 100644 --- a/dune-project +++ b/dune-project @@ -41,6 +41,10 @@ (reason (>= 3.6.0)) (ocaml-lsp-server :with-test) + (merlin + (and + (= 4.10-414) + :with-test)) (opam-check-npm-deps (and (= 1.0.0) diff --git a/ppx/reason_react_ppx.ml b/ppx/reason_react_ppx.ml index 9a913be74..53ed4048f 100644 --- a/ppx/reason_react_ppx.ml +++ b/ppx/reason_react_ppx.ml @@ -71,9 +71,9 @@ module Binding = struct { loc; txt = Ldot (Lident "ReactDOM", "createElement") }) [ (nolabel, element); (nolabel, children) ] - let domProps ~parentExpLoc ~loc props = - Builder.pexp_apply ~loc:parentExpLoc - (Builder.pexp_ident ~loc:parentExpLoc ~attrs:merlinHideAttrs + let domProps ~applyLoc ~loc props = + Builder.pexp_apply ~loc:applyLoc + (Builder.pexp_ident ~loc:applyLoc ~attrs:merlinHideAttrs { loc; txt = Ldot (Lident "ReactDOM", "domProps") }) props end @@ -507,8 +507,8 @@ let jsxMapper = Builder.pexp_construct ~loc:Location.none { txt = Lident "()"; loc = Location.none } None - in - let transformUppercaseCall3 ~caller modulePath ~ctxt mapper loc attrs _ + and key_var_txt = "Key" in + let transformUppercaseCall3 ~caller ~ctxt modulePath mapper parentExpLoc attrs callArguments = let children, argsWithLabels = extractChildren callArguments in let argsForMake = argsWithLabels in @@ -518,7 +518,7 @@ let jsxMapper = argsForMake in let jsxExpr, key, childrenProp = - reactJsxExprAndChildren ~loc ~ctxt mapper ~keyProps children + reactJsxExprAndChildren ~loc:parentExpLoc ~ctxt ~keyProps mapper children in let propsArg = (match childrenProp with @@ -549,61 +549,94 @@ let jsxMapper = (Invalid_argument "JSX name can't be the result of function applications") in - let props = - Builder.pexp_apply ~loc - (Builder.pexp_ident ~loc { loc; txt = propsIdent }) - propsArg - in - let key_args = - match key with - | Some (label, key) -> - [ (label, mapper#expression ctxt key); (nolabel, unit) ] - | None -> [] + let component = + ( nolabel, + Builder.pexp_ident ~loc:parentExpLoc { txt = ident; loc = parentExpLoc } + ) + and props = + ( nolabel, + Builder.pexp_apply ~loc:parentExpLoc + (Builder.pexp_ident ~loc:parentExpLoc + { loc = parentExpLoc; txt = propsIdent }) + propsArg ) in - Builder.pexp_apply ~loc ~attrs jsxExpr - ([ - (nolabel, Builder.pexp_ident ~loc { txt = ident; loc }); - (nolabel, props); - ] - @ key_args) + match key with + | None -> + Builder.pexp_apply ~loc:parentExpLoc ~attrs jsxExpr [ component; props ] + | Some (label, key) -> + (* We create a let binding with the value of the key to ensure + the inferred type https://github.com/reasonml/reason-react/pull/752 *) + let loc = parentExpLoc in + Builder.pexp_let ~loc Nonrecursive + [ + { + pvb_pat = Builder.ppat_var ~loc { txt = key_var_txt; loc }; + pvb_expr = mapper#expression ctxt key; + pvb_attributes = []; + pvb_loc = loc; + }; + ] + (Builder.pexp_apply ~loc:parentExpLoc ~attrs jsxExpr + [ + ( label, + Builder.pexp_ident ~loc:parentExpLoc + { txt = Lident key_var_txt; loc = parentExpLoc } ); + component; + props; + (nolabel, unit); + ]) in - let transformLowercaseCall3 ~ctxt parentExpLoc mapper loc attrs callArguments - id = + let transformLowercaseCall3 ~ctxt parentExpLoc mapper callerLoc attrs + callArguments id = let children, nonChildrenProps = extractChildren callArguments in - let componentNameExpr = constantString ~loc id in + let componentNameExpr = constantString ~loc:callerLoc id in let keyProps, nonChildrenProps = List.partition (fun (arg_label, _) -> "key" = getLabel arg_label) nonChildrenProps in - let jsxExpr, args = - let jsxExpr, key, childrenProp = - reactDomJsxExprAndChildren ~loc:parentExpLoc ~ctxt mapper ~keyProps - children - in - let props = - (match childrenProp with - | Some childrenProp -> - (labelled "children", childrenProp) :: nonChildrenProps - | None -> nonChildrenProps) - |> List.map (fun (label, expression) -> - (label, mapper#expression ctxt expression)) - in - let key_args = - match key with - | Some (label, key) -> - [ (label, mapper#expression ctxt key); (nolabel, unit) ] - | None -> [] - in - ( jsxExpr, - [ - (nolabel, componentNameExpr); - (nolabel, Binding.ReactDOM.domProps ~parentExpLoc ~loc props); - ] - @ key_args ) + + let jsxExpr, key, childrenProp = + reactDomJsxExprAndChildren ~loc:parentExpLoc ~ctxt mapper ~keyProps + children + in + let props = + (match childrenProp with + | Some childrenProp -> + (labelled "children", childrenProp) :: nonChildrenProps + | None -> nonChildrenProps) + |> List.map (fun (label, expression) -> + (label, mapper#expression ctxt expression)) + in + let component = (nolabel, componentNameExpr) + and props = + ( nolabel, + Binding.ReactDOM.domProps ~applyLoc:parentExpLoc ~loc:callerLoc props ) in - Builder.pexp_apply ~loc:parentExpLoc ~attrs jsxExpr args + let loc = parentExpLoc in + let gloc = { loc with loc_ghost = true } in + match key with + | Some (label, key) -> + (* We create a let binding with the value of the key to ensure + the inferred type https://github.com/reasonml/reason-react/pull/752 *) + Builder.pexp_let ~loc:gloc Nonrecursive + [ + { + pvb_pat = Builder.ppat_var ~loc { txt = key_var_txt; loc }; + pvb_expr = mapper#expression ctxt key; + pvb_attributes = []; + pvb_loc = loc; + }; + ] + (Builder.pexp_apply ~loc ~attrs jsxExpr + [ + (label, Builder.pexp_ident ~loc { txt = Lident key_var_txt; loc }); + component; + props; + (nolabel, unit); + ]) + | None -> Builder.pexp_apply ~loc ~attrs jsxExpr [ component; props ] in let rec recursivelyTransformNamedArgsForMake ~ctxt mapper expr list = @@ -1296,7 +1329,7 @@ let jsxMapper = (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) | { txt = Ldot (modulePath, ("createElement" | "make")) } -> transformUppercaseCall3 ~ctxt ~caller:"make" modulePath mapper - parentExpLoc attrs callExpression callArguments + parentExpLoc attrs callArguments (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) (* turn that into ReactDOM.createElement("div", ~props=ReactDOM.domProps(~props1=foo, @@ -1309,7 +1342,7 @@ let jsxMapper = https://github.com/reasonml/reason/pull/2541 *) | { txt = Ldot (modulePath, anythingNotCreateElementOrMake) } -> transformUppercaseCall3 ~ctxt ~caller:anythingNotCreateElementOrMake - modulePath mapper parentExpLoc attrs callExpression callArguments + modulePath mapper parentExpLoc attrs callArguments | { txt = Lapply _ } -> (* don't think there's ever a case where this is reached *) raise diff --git a/ppx/test/keys.t/run.t b/ppx/test/keys.t/run.t index d46dadc87..3242dd6b0 100644 --- a/ppx/test/keys.t/run.t +++ b/ppx/test/keys.t/run.t @@ -32,7 +32,7 @@ _^ "line": 10, "col": 85 }, - "type": "ReactDOM.domProps", + "type": "string", "tail": "no" } diff --git a/ppx/test/lower.t/run.t b/ppx/test/lower.t/run.t index 222bd48ee..0898fb7b4 100644 --- a/ppx/test/lower.t/run.t +++ b/ppx/test/lower.t/run.t @@ -95,7 +95,9 @@ ~children= examples |> List.map(e => + let Key = e.path; ReactDOM.jsxKeyed( + ~key=Key, "li", ([@merlin.hide] ReactDOM.domProps)( ~children= @@ -116,9 +118,8 @@ ), (), ), - ~key=e.path, (), - ) + ); ) |> React.list, (), diff --git a/ppx/test/output.expected b/ppx/test/output.expected deleted file mode 100644 index a746abb4b..000000000 --- a/ppx/test/output.expected +++ /dev/null @@ -1,419 +0,0 @@ -let lower = ReactDOM.jsx "div" (((ReactDOM.domProps)[@merlin.hide ]) ()) -let lower_empty_attr = - ReactDOM.jsx "div" (((ReactDOM.domProps)[@merlin.hide ]) ~className:"" ()) -let lower_inline_styles = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~style:(ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) ()) -let lower_inner_html = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~dangerouslySetInnerHTML:([%bs.obj { __html = text }]) ()) -let lower_opt_attr = - ReactDOM.jsx "div" (((ReactDOM.domProps)[@merlin.hide ]) ?tabIndex ()) -let lowerWithChildAndProps foo = - ReactDOM.jsx "a" - (((ReactDOM.domProps)[@merlin.hide ]) ~children:foo ~tabIndex:1 - ~href:"https://example.com" ()) -let lower_child_static = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(ReactDOM.jsx "span" - (((ReactDOM.domProps)[@merlin.hide ]) ())) ()) -let lower_child_ident = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ~children:lolaspa ()) -let lower_child_single = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ())) ()) -let lower_children_multiple foo bar = - ReactDOM.jsxs "lower" - (((ReactDOM.domProps)[@merlin.hide ]) ~children:(React.array [|foo;bar|]) - ()) -let lower_child_with_upper_as_children = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(React.jsx App.make (App.makeProps ())) ()) -let lower_children_nested = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(ReactDOM.jsxs "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(React.array - [|(ReactDOM.jsx "h2" - (((ReactDOM.domProps) - [@merlin.hide ]) - ~children:("jsoo-react" |> s) - ~className:"title" ()));( - ReactDOM.jsx "nav" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(ReactDOM.jsx "ul" - (((ReactDOM.domProps) - [@merlin.hide ]) - ~children:( - (examples |> - (List.map - (fun e -> - ReactDOM.jsxKeyed - "li" - (((ReactDOM.domProps) - [@merlin.hide - ]) - ~children:( - ReactDOM.jsx - "a" - (((ReactDOM.domProps) - [@merlin.hide - ]) - ~children:( - e.title - |> s) - ~href:( - e.path) - ~onClick:( - fun event - -> - ReactEvent.Mouse.preventDefault - event; - ReactRouter.push - e.path) - ())) ()) - ~key:( - e.path) - ()))) - |> React.list) - ())) - ~className:"menu" ()))|]) - ~className:"sidebar" ())) ~className:"flex-container" - ()) -let fragment foo = ((ReactDOM.createElement React.jsxFragment [|foo|]) - [@bla ]) -let poly_children_fragment foo bar = - ReactDOM.createElement React.jsxFragment [|foo;bar|] -let nested_fragment foo bar baz = - ReactDOM.createElement React.jsxFragment - [|foo;(ReactDOM.createElement React.jsxFragment [|bar;baz|])|] -let nested_fragment_with_lower foo = - ReactDOM.createElement React.jsxFragment - [|(ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ~children:foo ()))|] -let upper = React.jsx Upper.make (Upper.makeProps ()) -let upper_prop = React.jsx Upper.make (Upper.makeProps ~count ()) -let upper_children_single foo = - React.jsx Upper.make (Upper.makeProps ~children:foo ()) -let upper_children_multiple foo bar = - React.jsxs Upper.make - (Upper.makeProps ~children:(React.array [|foo;bar|]) ()) -let upper_children = - React.jsx Page.make - (Page.makeProps - ~children:(ReactDOM.jsx "h1" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(React.string "Yep") ())) ~moreProps:"hgalo" - ()) -let upper_nested_module = - React.jsx Foo.Bar.make (Foo.Bar.makeProps ~a:1 ~b:"1" ()) -let upper_child_expr = - React.jsx Div.make (Div.makeProps ~children:(React.int 1) ()) -let upper_child_ident = React.jsx Div.make (Div.makeProps ~children:lola ()) -let upper_all_kinds_of_props = - React.jsx MyComponent.make - (MyComponent.makeProps - ~children:(ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ~children:"hello" - ())) ~booleanAttribute:true ~stringAttribute:"string" - ~intAttribute:1 ?forcedOptional:((Some "hello")[@explicit_arity ]) - ~onClick:(send handleClick) ()) -let upper_ref_with_children = - React.jsx FancyButton.make - (FancyButton.makeProps - ~children:(ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ())) ~ref:buttonRef - ()) -let lower_ref_with_children = - ReactDOM.jsx "button" - (((ReactDOM.domProps)[@merlin.hide ]) ~children ~ref - ~className:"FancyButton" ()) -let lower_with_many_props = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(ReactDOM.jsxs "picture" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(React.array - [|(ReactDOM.jsx "img" - (((ReactDOM.domProps) - [@merlin.hide ]) - ~src:"picture/img.png" - ~alt:"test picture/img.png" - ~id:"idimg" ()));(ReactDOM.jsx - "source" - (((ReactDOM.domProps) - [@merlin.hide - ]) - ~type_:"image/webp" - ~src:"picture/img1.webp" - ()));( - ReactDOM.jsx "source" - (((ReactDOM.domProps)[@merlin.hide ]) - ~type_:"image/jpeg" - ~src:"picture/img2.jpg" ()))|]) - ~id:"idpicture" ())) ~translate:"yes" ()) -let some_random_html_element = - ReactDOM.jsx "text" - (((ReactDOM.domProps)[@merlin.hide ]) ~dx:"1 2" ~dy:"3 4" ()) -module React_component_with_props = - struct - external makeProps : - lola:'lola -> ?key:string -> unit -> < lola: 'lola > Js.t = "" - [@@bs.obj ] - let make = - ((fun ~lola -> - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(React.string lola) ())) - [@warning "-16"]) - let make = - let Generated$React_component_with_props - (Props : < lola: 'lola > Js.t) = make ~lola:(Props ## lola) in - Generated$React_component_with_props - end -let react_component_with_props = - React.jsx React_component_with_props.make - (React_component_with_props.makeProps ~lola:"flores" ()) -module Upper_case_with_fragment_as_root = - struct - external makeProps : - ?name:'name -> ?key:string -> unit -> < name: 'name option > Js.t = - ""[@@bs.obj ] - let make = - ((fun ?(name= "") -> - ReactDOM.createElement React.jsxFragment - [|(ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(React.string ("First " ^ name)) ()));( - React.jsx Hello.make - (Hello.makeProps ~children:(React.string ("2nd " ^ name)) - ~one:"1" ()))|]) - [@warning "-16"]) - let make = - let Generated$Upper_case_with_fragment_as_root - (Props : < name: 'name option > Js.t) = make ?name:(Props ## name) in - Generated$Upper_case_with_fragment_as_root - end -module Using_React_memo = - struct - external makeProps : - a:'a -> ?key:string -> unit -> < a: 'a > Js.t = ""[@@bs.obj ] - let make = - ((fun ~a -> - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:((Printf.sprintf "`a` is %s" a) |> React.string) ())) - [@warning "-16"]) - let make = - React.memo - (let Generated$Using_React_memo (Props : < a: 'a > Js.t) = - make ~a:(Props ## a) in - Generated$Using_React_memo) - end -module Using_memo_custom_compare_Props = - struct - external makeProps : - a:'a -> ?key:string -> unit -> < a: 'a > Js.t = ""[@@bs.obj ] - let make = - React.memoCustomCompareProps - (fun ~a -> - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:((Printf.sprintf "`a` is %d" a) |> React.string) ())) - (fun prevPros -> fun nextProps -> false) - let make = - let Generated$Using_memo_custom_compare_Props - (Props : < a: 'a > Js.t) = make ~a:(Props ## a) in - Generated$Using_memo_custom_compare_Props - end -module Forward_Ref = - struct - external makeProps : - children:'children -> - buttonRef:'buttonRef -> - ?key:string -> - unit -> < children: 'children ;buttonRef: 'buttonRef > Js.t = - ""[@@bs.obj ] - let make = - ((fun ~children -> - ((fun ~buttonRef -> - ReactDOM.jsx "button" - (((ReactDOM.domProps)[@merlin.hide ]) ~children - ~ref:buttonRef ~className:"FancyButton" ())) - [@warning "-16"])) - [@warning "-16"]) - let make = - React.forwardRef - (let Generated$Forward_Ref - (Props : < children: 'children ;buttonRef: 'buttonRef > Js.t) - = - make ~buttonRef:(Props ## buttonRef) ~children:(Props ## children) in - Generated$Forward_Ref) - end -module Onclick_handler_button = - struct - external makeProps : - name:'name -> - ?isDisabled:'isDisabled -> - ?key:string -> - unit -> < name: 'name ;isDisabled: 'isDisabled option > Js.t - = ""[@@bs.obj ] - let make = - ((fun ~name -> - ((fun ?isDisabled -> - let onClick event = Js.log event in - ReactDOM.jsx "button" - (((ReactDOM.domProps)[@merlin.hide ]) ~name ~onClick - ~disabled:isDisabled ())) - [@warning "-16"])) - [@warning "-16"]) - let make = - let Generated$Onclick_handler_button - (Props : < name: 'name ;isDisabled: 'isDisabled option > Js.t) = - make ?isDisabled:(Props ## isDisabled) ~name:(Props ## name) in - Generated$Onclick_handler_button - end -module Children_as_string = - struct - external makeProps : - ?name:'name -> ?key:string -> unit -> < name: 'name option > Js.t = - ""[@@bs.obj ] - let make = - ((fun ?(name= "joe") -> - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:((Printf.sprintf "`name` is %s" name) |> - React.string) ())) - [@warning "-16"]) - let make = - let Generated$Children_as_string - (Props : < name: 'name option > Js.t) = make ?name:(Props ## name) in - Generated$Children_as_string - end -let () = Dream.run () -let l = 33 -module Uppercase_with_SSR_components = - struct - external makeProps : - children:'children -> - moreProps:'moreProps -> - ?key:string -> - unit -> < children: 'children ;moreProps: 'moreProps > Js.t = - ""[@@bs.obj ] - let make = - ((fun ~children -> - ((fun ~moreProps -> - ReactDOM.jsxs "html" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(React.array - [|(ReactDOM.jsx "head" - (((ReactDOM.domProps)[@merlin.hide ]) - ~children:(ReactDOM.jsx "title" - (((ReactDOM.domProps) - [@merlin.hide ]) - ~children:(React.string - ("SSR React " - ^ - moreProps)) - ())) ()));(ReactDOM.jsxs - "body" - (((ReactDOM.domProps) - [@merlin.hide - ]) - ~children:( - React.array - [|( - ReactDOM.jsx - "div" - (((ReactDOM.domProps) - [@merlin.hide - ]) - ~children - ~id:"root" - ()));( - ReactDOM.jsx - "script" - (((ReactDOM.domProps) - [@merlin.hide - ]) - ~src:"/static/client.js" - ()))|]) - ()))|]) - ())) - [@warning "-16"])) - [@warning "-16"]) - let make = - let Generated$Uppercase_with_SSR_components - (Props : < children: 'children ;moreProps: 'moreProps > Js.t) = - make ~moreProps:(Props ## moreProps) ~children:(Props ## children) in - Generated$Uppercase_with_SSR_components - end -module Upper_with_aria = - struct - external makeProps : - children:'children -> - ?key:string -> unit -> < children: 'children > Js.t = ""[@@bs.obj - ] - let make = - ((fun ~children -> - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ~children - ~ariaHidden:"true" ())) - [@warning "-16"]) - let make = - let Generated$Upper_with_aria (Props : < children: 'children > Js.t) - = make ~children:(Props ## children) in - Generated$Upper_with_aria - end -let data_attributes_should_transform_to_kebabcase = - ReactDOM.createElement React.jsxFragment - [|(ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ~dataAttribute:"" - ~dataattribute:"" ~className:"md:w-1/3" ()));(ReactDOM.jsx "div" - (((ReactDOM.domProps) - [@merlin.hide - ]) - ~className:"md:w-2/3" - ()))|] -let render_onclickPropsAsString = - ReactDOM.jsx "div" - (((ReactDOM.domProps)[@merlin.hide ]) ~_onclick:"alert('hello')" ()) -module External = - struct - external componentProps : - a:int -> - b:string -> ?key:string -> unit -> < a: int ;b: string > Js.t = - ""[@@bs.obj ] - external component : - (< a: int ;b: string > Js.t, React.element) React.componentLike = - "require(\"my-react-library\").MyReactComponent"[@@otherAttribute - "bla"] - end -module type X_int = sig val x : int end -module Func(M:X_int) = - struct - let x = M.x + 1 - external makeProps : - a:'a -> b:'b -> ?key:string -> unit -> < a: 'a ;b: 'b > Js.t = "" - [@@bs.obj ] - let make = - ((fun ~a -> - ((fun ~b -> - fun _ -> - print_endline "This function should be named `Test$Func`" M.x; - ReactDOM.jsx "div" (((ReactDOM.domProps)[@merlin.hide ]) ())) - [@warning "-16"])) - [@warning "-16"]) - let make = - let Generated$Func (Props : < a: 'a ;b: 'b > Js.t) = - make ~b:(Props ## b) ~a:(Props ## a) () in - Generated$Func - end diff --git a/reason-react.opam b/reason-react.opam index a3b4ba00b..7747e9488 100644 --- a/reason-react.opam +++ b/reason-react.opam @@ -23,6 +23,7 @@ depends: [ "reason-react-ppx" "reason" {>= "3.6.0"} "ocaml-lsp-server" {with-test} + "merlin" {= "4.10-414" & with-test} "opam-check-npm-deps" {= "1.0.0" & with-dev-setup} "ocamlformat" {= "0.24.0" & with-dev-setup} "odoc" {with-doc} diff --git a/test/React__test.re b/test/React__test.re index fed88b4a3..563048f41 100644 --- a/test/React__test.re +++ b/test/React__test.re @@ -8,7 +8,7 @@ module DummyStatefulComponent = { let make = (~initialValue=0, ()) => { let (value, setValue) = React.useState(() => initialValue); - ; }; @@ -172,7 +172,8 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent("div", "Hello world!") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can render null elements", () => { @@ -184,7 +185,8 @@ describe("React", () => { container ->DOM.findBySelectorAndPartialTextContent("div", "") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can render string elements", () => { @@ -198,7 +200,8 @@ describe("React", () => { container ->DOM.findBySelectorAndPartialTextContent("div", "Hello") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can render int elements", () => { @@ -210,7 +213,8 @@ describe("React", () => { container ->DOM.findBySelectorAndPartialTextContent("div", "12345") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can render float elements", () => { @@ -222,7 +226,8 @@ describe("React", () => { container ->DOM.findBySelectorAndPartialTextContent("div", "12.345") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can render array of elements", () => { @@ -237,19 +242,22 @@ describe("React", () => { container ->DOM.findBySelectorAndPartialTextContent("div", "1") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); expect( container ->DOM.findBySelectorAndPartialTextContent("div", "2") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); expect( container ->DOM.findBySelectorAndPartialTextContent("div", "3") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can clone an element", () => { @@ -272,19 +280,21 @@ describe("React", () => { "Hello", ) ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can render react components", () => { let container = getContainer(container); - act(() => {ReactDOM.render(, container)}); + act(() => {ReactDOM.render(, container)}); expect( container ->DOM.findBySelectorAndTextContent("button", "0") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); let button = container->DOM.findBySelector("button"); @@ -299,13 +309,15 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent("button", "0") ->Option.isSome, - )->toBe(false); + ) + ->toBe(false); expect( container ->DOM.findBySelectorAndTextContent("button", "1") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("can render react components with reducers", () => { @@ -317,7 +329,8 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent(".value", "0") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); let button = container->DOM.findBySelectorAndPartialTextContent( @@ -336,13 +349,15 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent(".value", "0") ->Option.isSome, - )->toBe(false); + ) + ->toBe(false); expect( container ->DOM.findBySelectorAndTextContent(".value", "1") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); let button = container->DOM.findBySelectorAndPartialTextContent( @@ -361,13 +376,15 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent(".value", "0") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); expect( container ->DOM.findBySelectorAndTextContent(".value", "1") ->Option.isSome, - )->toBe(false); + ) + ->toBe(false); }); test("can render react components with reducers (map state)", () => { @@ -381,7 +398,8 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent(".value", "1") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); let button = container->DOM.findBySelectorAndPartialTextContent( @@ -400,13 +418,15 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent(".value", "1") ->Option.isSome, - )->toBe(false); + ) + ->toBe(false); expect( container ->DOM.findBySelectorAndTextContent(".value", "2") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); let button = container->DOM.findBySelectorAndPartialTextContent( @@ -425,13 +445,15 @@ describe("React", () => { container ->DOM.findBySelectorAndTextContent(".value", "1") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); expect( container ->DOM.findBySelectorAndTextContent(".value", "2") ->Option.isSome, - )->toBe(false); + ) + ->toBe(false); }); test("can render react components with effects", () => { @@ -457,10 +479,7 @@ describe("React", () => { ) }); - expect(callback->Mock.getMock->Mock.calls)->toEqual([| - [|0|], - [|1|], - |]); + expect(callback->Mock.getMock->Mock.calls)->toEqual([|[|0|], [|1|]|]); }); test("can render react components with layout effects", () => { @@ -486,10 +505,7 @@ describe("React", () => { ) }); - expect(callback->Mock.getMock->Mock.calls)->toEqual([| - [|0|], - [|1|], - |]); + expect(callback->Mock.getMock->Mock.calls)->toEqual([|[|0|], [|1|]|]); }); test("can work with React refs", () => { @@ -510,9 +526,8 @@ describe("React", () => { ReactDOM.render(, container) }); - expect(myRef.contents->Option.map(item => item.current))->toEqual( - Some(2), - ); + expect(myRef.contents->Option.map(item => item.current)) + ->toEqual(Some(2)); }); test("Children", () => { @@ -533,19 +548,22 @@ describe("React", () => { container ->DOM.findBySelectorAndPartialTextContent("div[data-index='0']", "1") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); expect( container ->DOM.findBySelectorAndPartialTextContent("div[data-index='1']", "2") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); expect( container ->DOM.findBySelectorAndPartialTextContent("div[data-index='2']", "3") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("Context", () => { @@ -564,7 +582,8 @@ describe("React", () => { container ->DOM.findBySelectorAndPartialTextContent("div", "10") ->Option.isSome, - )->toBe(true); + ) + ->toBe(true); }); test("Events", () => { @@ -609,30 +628,54 @@ describe("React", () => { ) ->toBe(true); }); - - /* test("ErrorBoundary", () => { + + test("Type inference with keys", () => { let container = getContainer(container); + module Author = { + type t = { + name: string, + imageUrl: string, + }; + }; + + let render = author => + + + ; + act(() => { ReactDOM.render( - { - expect( - info.componentStack->Js.String2.includes("ComponentThatThrows"), - ). - toBe(true); - "An error occured"->React.string ; - }}> - - , + render({name: "Joe", imageUrl: "https://foo.png"}), container, ) }); - expect( - container - ->DOM.findBySelectorAndTextContent("strong", "An error occured") - ->Option.isSome, - )->toBe(true); - }); */ + expect(container->DOM.findBySelector("img")->Option.isSome)->toBe(true); + }); + /* test("ErrorBoundary", () => { + let container = getContainer(container); + + act(() => { + ReactDOM.render( + { + expect( + info.componentStack->Js.String2.includes("ComponentThatThrows"), + ). + toBe(true); + "An error occured"->React.string ; + }}> + + , + container, + ) + }); + + expect( + container + ->DOM.findBySelectorAndTextContent("strong", "An error occured") + ->Option.isSome, + )->toBe(true); + }); */ });