diff --git a/CHANGELOG.md b/CHANGELOG.md
index d42c040..10efa28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## 0.13.0
+
+### Changed
+* `ReactElement` is now a full `Monoid` with `empty` as identity and append
+ creating React.Fragment elements.
+* **Breaking**: module `Elmish.React.DOM` has been removed and its contents
+ moved to `Elmish.React`.
+* **Breaking**: module `Elmish.Trace` has been removed. Its sole export has been
+ part of the standard `debug` library for a while now.
+* Fixed a bug with `readForeign` and nested `Nullable`s: reading `[1,"foo",2]`
+ as `Nullable (Array Int)` would complain that the second element is bogus
+ (which is true) and incorrectly state that the expected type was `Nullable Int`.
+
## 0.12.0
### Changed
diff --git a/docs/react-ffi.md b/docs/react-ffi.md
index a8a0e34..c8f6535 100644
--- a/docs/react-ffi.md
+++ b/docs/react-ffi.md
@@ -6,9 +6,63 @@ nav_order: 6
# Using React components
{:.no_toc}
-> **Under construction**. This page is unfinished. Many headings just have some
-> bullet points sketching the main points that should be discussed.
+Because Elmish is just a thin layer on top of React, it is quite easy to use
+non-PureScript React components from the wider ecosystem.
-1. TOC
-{:toc}
+A typical import of a React component consists of three parts:
+* A row of props, with optional props denoted via `Opt`.
+* Actual FFI-import of the component constructor. This import is weakly
+ typed and shouldn't be exported from the module. Consider it internal
+ implementation detail.
+* Strongly-typed, PureScript-friendly function that constructs the
+ component. The body of such function usually consists of just a call
+ to `createElement` (or `createElement'` for childless components), its
+ only purpose being the type signature. This function is what should be
+ exported for use by consumers.
+
+Classes and type aliases provided in this module, when applied to the
+constructor function, make it possible to pass only partial props to it,
+while still ensuring their correct types and presence of non-optional ones.
+This is facilitated by the
+[undefined-is-not-a-problem](https://github.com/paluh/purescript-undefined-is-not-a-problem/) library.
+
+## Example
+
+### The JSX file with component implementation
+```jsx
+// `world` prop is required, `hello` and `highlight` are optional
+export const MyComponent = ({ hello, world, highlight }) =>
+
+ {hello || "Hello"},
+ {world}
+
+```
+
+### PureScript FFI module
+```haskell
+module MyComponent(Props, myComponent) where
+
+import Data.Undefined.NoProblem (Opt)
+import Elmish.React (createElement)
+import Elmish.React.Import (ImportedReactComponentConstructor, ImportedReactComponent)
+
+type Props = ( world :: String, hello :: Opt String, highlight :: Opt Boolean )
+
+myComponent :: ImportedReactComponentConstructor Props
+myComponent = createElement myComponent_
+
+foreign import myComponent_ :: ImportedReactComponent
+```
+
+### PureScript use site
+```haskell
+import MyComponent (myComponent)
+import Elmish.React (fragment) as H
+
+view :: ...
+view = H.fragment
+ [ myComponent { world: "world" }
+ , myComponent { hello: "Goodbye", world: "cruel world!", highlight: true }
+ ]
+```
diff --git a/packages.dhall b/packages.dhall
index c5f301f..ad17a4a 100644
--- a/packages.dhall
+++ b/packages.dhall
@@ -8,5 +8,5 @@ in upstream
with elmish-html =
{ dependencies = [ "prelude", "record" ]
, repo = "https://github.com/collegevine/purescript-elmish-html.git"
- , version = "v0.8.2"
+ , version = "tweaks"
}
diff --git a/src/Elmish/Component.purs b/src/Elmish/Component.purs
index 8d4016a..44a47ca 100644
--- a/src/Elmish/Component.purs
+++ b/src/Elmish/Component.purs
@@ -28,9 +28,9 @@ import Effect (Effect, foreachE)
import Effect.Aff (Aff, Milliseconds(..), delay, launchAff_)
import Effect.Class (class MonadEffect, liftEffect)
import Elmish.Dispatch (Dispatch)
-import Elmish.React (ReactComponent, ReactComponentInstance, ReactElement, getField, setField)
+import Elmish.React (ReactComponent, ReactComponentInstance, ReactElement)
+import Elmish.React.Internal (Field(..), getField, setField)
import Elmish.State (StateStrategy, dedicatedStorage, localState)
-import Elmish.Trace (traceTime)
-- | A UI component state transition: wraps the new state value together with a
-- | (possibly empty) list of effects that the transition has caused (called
@@ -243,10 +243,10 @@ withTrace :: ∀ m msg state
withTrace def = def { update = tracingUpdate, view = tracingView }
where
tracingUpdate s m =
- let (Transition s cmds) = traceTime "Update" \_ -> def.update s $ Debug.spy "Message" m
+ let (Transition s cmds) = Debug.traceTime "Update" \_ -> def.update s $ Debug.spy "Message" m
in Transition (Debug.spy "State" s) cmds
tracingView s d =
- traceTime "Rendering" \_ -> def.view s d
+ Debug.traceTime "Rendering" \_ -> def.view s d
-- | This function is low level, not intended for a use in typical consumer
-- | code. Use `construct` or `wrapWithLocalState` instead.
@@ -307,13 +307,13 @@ bindComponent cmpt stateStrategy = \def -> -- Explicit lambda to make sure `def`
sequence_ =<< getSubscriptions component
setSubscriptions [] component
- subscriptionsField = "__subscriptions"
- getSubscriptions = getField @(Array (Effect Unit)) subscriptionsField >>> map (fromMaybe [])
- setSubscriptions = setField @(Array (Effect Unit)) subscriptionsField
+ subscriptionsField = Field @"__subscriptions" @(Array (Effect Unit))
+ getSubscriptions = getField subscriptionsField >>> map (fromMaybe [])
+ setSubscriptions = setField subscriptionsField
- unmountedField = "__unmounted"
- getUnmounted = getField @Boolean unmountedField >>> map (fromMaybe false)
- setUnmounted = setField @Boolean unmountedField
+ unmountedField = Field @"__unmounted" @Boolean
+ getUnmounted = getField unmountedField >>> map (fromMaybe false)
+ setUnmounted = setField unmountedField
-- | Given a `ComponentDef'`, binds that def to a freshly created React class,
-- | instantiates that class, and returns a rendering function.
diff --git a/src/Elmish/Foreign.purs b/src/Elmish/Foreign.purs
index 7f85bca..c2fd889 100644
--- a/src/Elmish/Foreign.purs
+++ b/src/Elmish/Foreign.purs
@@ -195,7 +195,7 @@ instance CanReceiveFromJavaScript a => CanReceiveFromJavaScript (Nullable a) whe
| otherwise =
case validateForeignType @a v of
Valid -> Valid
- Invalid err -> Invalid err { expected = "Nullable " <> err.expected }
+ Invalid err -> Invalid err { expected = if err.path == "" then "Nullable " <> err.expected else err.expected }
instance CanPassToJavaScript a => CanPassToJavaScript (Opt a)
instance CanReceiveFromJavaScript a => CanReceiveFromJavaScript (Opt a) where
diff --git a/src/Elmish/React.js b/src/Elmish/React.js
index f471e0e..63d5634 100644
--- a/src/Elmish/React.js
+++ b/src/Elmish/React.js
@@ -19,6 +19,23 @@ export var hydrate_ = ReactDOM.hydrate;
export var renderToString = (ReactDOMServer && ReactDOMServer.renderToString) || (_ => "");
export var unmount_ = ReactDOM.unmountComponentAtNode
+export var fragment_ = React.Fragment;
+
+export var appendElement_ = a => b => {
+ const childrenOf = x => {
+ if (x === false || x === null || typeof x === 'undefined') return []
+ if (x.type === React.Fragment) {
+ const children = x.props?.children
+ if (children instanceof Array) return children
+ if (children === false || children === null || typeof children === 'undefined') return []
+ return [children]
+ }
+ return [x]
+ }
+ const allChildren = [...childrenOf(a), ...childrenOf(b)]
+ return allChildren.length === 0 ? false : React.createElement(React.Fragment, null, allChildren)
+}
+
export function createElement_(component, props, children) {
// The type of `children` is `Array ReactElement`. If we pass that in as
// third parameter of `React.createElement` directly, React complains about
diff --git a/src/Elmish/React.purs b/src/Elmish/React.purs
index cf562d1..2e56a1c 100644
--- a/src/Elmish/React.purs
+++ b/src/Elmish/React.purs
@@ -1,31 +1,31 @@
module Elmish.React
- ( ReactElement
- , ReactComponent
- , ReactComponentInstance
- , class ValidReactProps
- , class ReactChildren, asReactChildren
- , assignState
- , createElement
- , createElement'
- , getField
- , getState
- , hydrate
- , setField
- , setState
- , render
- , renderToString
- , unmount
- , module Ref
- ) where
+ ( ReactElement
+ , ReactComponent
+ , ReactComponentInstance
+ , class ValidReactProps
+ , class ReactChildren, asReactChildren
+ , assignState
+ , createElement
+ , createElement'
+ , empty
+ , fragment
+ , getState
+ , hydrate
+ , setState
+ , render
+ , renderToString
+ , text
+ , unmount
+ , module Ref
+ ) where
import Prelude
import Data.Function.Uncurried (Fn3, runFn3)
-import Data.Maybe (Maybe)
import Data.Nullable (Nullable)
import Effect (Effect)
import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, runEffectFn1, runEffectFn2, runEffectFn3)
-import Elmish.Foreign (class CanPassToJavaScript, class CanReceiveFromJavaScript, Foreign, readForeign)
+import Elmish.Foreign (class CanPassToJavaScript)
import Elmish.React.Ref (Ref, callbackRef) as Ref
import Prim.TypeError (Text, class Fail)
import Unsafe.Coerce (unsafeCoerce)
@@ -34,6 +34,9 @@ import Web.DOM as HTML
-- | Instantiated subtree of React DOM. JSX syntax produces values of this type.
foreign import data ReactElement :: Type
+instance Semigroup ReactElement where append = appendElement_
+instance Monoid ReactElement where mempty = empty
+
-- | This type represents constructor of a React component with a particular
-- | behavior. The type prameter is the record of props (in React lingo) that
-- | this component expects. Such constructors can be "rendered" into
@@ -82,6 +85,20 @@ createElement' :: ∀ props
-> ReactElement
createElement' component props = createElement component props ([] :: Array ReactElement)
+-- | Empty React element.
+empty :: ReactElement
+empty = unsafeCoerce false
+
+-- | Render a plain string as a React element.
+text :: String -> ReactElement
+text = unsafeCoerce
+
+-- | Wraps multiple React elements as a single one (import of React.Fragment)
+fragment :: Array ReactElement -> ReactElement
+fragment = createElement fragment_ {}
+
+foreign import fragment_ :: ReactComponent {}
+foreign import appendElement_ :: ReactElement -> ReactElement -> ReactElement
-- | Asserts that the given type is a valid React props structure. Currently
-- | there are three rules for what is considered "valid":
@@ -112,14 +129,6 @@ setState :: ∀ state. ReactComponentInstance -> state -> (Effect Unit) -> Effec
setState = runEffectFn3 setState_
foreign import setState_ :: ∀ state. EffectFn3 ReactComponentInstance state (Effect Unit) Unit
-getField :: ∀ @a. CanReceiveFromJavaScript a => String -> ReactComponentInstance -> Effect (Maybe a)
-getField field object = runEffectFn2 getField_ field object <#> readForeign @a
-foreign import getField_ :: EffectFn2 String ReactComponentInstance Foreign
-
-setField :: ∀ @a. CanPassToJavaScript a => String -> a -> ReactComponentInstance -> Effect Unit
-setField = runEffectFn3 setField_
-foreign import setField_ :: ∀ a. EffectFn3 String a ReactComponentInstance Unit
-
-- | The equivalent of `this.state = x`, as opposed to `setState`, which is the
-- | equivalent of `this.setState(x)`. This function is used in a component's
-- | constructor to set the initial state.
diff --git a/src/Elmish/React/DOM.js b/src/Elmish/React/DOM.js
deleted file mode 100644
index a593f74..0000000
--- a/src/Elmish/React/DOM.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import { Fragment } from "react/index.js";
-export var fragment_ = Fragment;
diff --git a/src/Elmish/React/DOM.purs b/src/Elmish/React/DOM.purs
deleted file mode 100644
index e310bca..0000000
--- a/src/Elmish/React/DOM.purs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Elmish.React.DOM
- ( empty
- , text
- , fragment
- ) where
-
-import Elmish.React (ReactComponent, ReactElement, createElement)
-import Unsafe.Coerce (unsafeCoerce)
-
--- | Empty React element.
-empty :: ReactElement
-empty = unsafeCoerce false
-
--- | Render a plain string as a React element.
-text :: String -> ReactElement
-text = unsafeCoerce
-
--- | Wraps multiple React elements as a single one (import of React.Fragment)
-fragment :: Array ReactElement -> ReactElement
-fragment = createElement fragment_ {}
-
-foreign import fragment_ :: ReactComponent {}
diff --git a/src/Elmish/React/Import.purs b/src/Elmish/React/Import.purs
index 2aa88c5..d702cb6 100644
--- a/src/Elmish/React/Import.purs
+++ b/src/Elmish/React/Import.purs
@@ -30,7 +30,7 @@
-- |
-- |
-- | -- PureScript
--- | module MyComponent(Props, OptProps, myComponent) where
+-- | module MyComponent(Props, myComponent) where
-- |
-- | import Data.Undefined.NoProblem (Opt)
-- | import Elmish.React (createElement)
@@ -38,7 +38,7 @@
-- |
-- | type Props = ( world :: String, hello :: Opt String, highlight :: Opt Boolean )
-- |
--- | myComponent :: ImportedReactComponentConstructor Props OptProps
+-- | myComponent :: ImportedReactComponentConstructor Props
-- | myComponent = createElement myComponent_
-- |
-- | foreign import myComponent_ :: ImportedReactComponent
@@ -46,7 +46,7 @@
-- |
-- | -- PureScript use site
-- | import MyComponent (myComponent)
--- | import Elmish.React.DOM (fragment)
+-- | import Elmish.React (fragment) as H
-- |
-- | view :: ...
-- | view = H.fragment
diff --git a/src/Elmish/React/Internal.js b/src/Elmish/React/Internal.js
new file mode 100644
index 0000000..206b5b7
--- /dev/null
+++ b/src/Elmish/React/Internal.js
@@ -0,0 +1,2 @@
+export const getField_ = (field, obj) => obj[field]
+export const setField_ = (field, value, obj) => obj[field] = value
diff --git a/src/Elmish/React/Internal.purs b/src/Elmish/React/Internal.purs
new file mode 100644
index 0000000..8ee8d7b
--- /dev/null
+++ b/src/Elmish/React/Internal.purs
@@ -0,0 +1,25 @@
+module Elmish.React.Internal
+ ( Field(..)
+ , getField
+ , setField
+ ) where
+
+import Prelude
+
+import Data.Maybe (Maybe)
+import Data.Symbol (class IsSymbol, reflectSymbol)
+import Effect (Effect)
+import Effect.Uncurried (EffectFn2, EffectFn3, runEffectFn2, runEffectFn3)
+import Elmish.Foreign (class CanPassToJavaScript, class CanReceiveFromJavaScript, Foreign, readForeign)
+import Elmish.React (ReactComponentInstance)
+import Type.Proxy (Proxy(..))
+
+data Field (f :: Symbol) (a :: Type) = Field
+
+getField :: ∀ f a. CanReceiveFromJavaScript a => IsSymbol f => Field f a -> ReactComponentInstance -> Effect (Maybe a)
+getField _ object = runEffectFn2 getField_ (reflectSymbol $ Proxy @f) object <#> readForeign @a
+foreign import getField_ :: EffectFn2 String ReactComponentInstance Foreign
+
+setField :: ∀ f a. CanPassToJavaScript a => IsSymbol f => Field f a -> a -> ReactComponentInstance -> Effect Unit
+setField _ = runEffectFn3 setField_ $ reflectSymbol $ Proxy @f
+foreign import setField_ :: ∀ a. EffectFn3 String a ReactComponentInstance Unit
diff --git a/src/Elmish/React/Ref.purs b/src/Elmish/React/Ref.purs
index c36247f..2b7c41e 100644
--- a/src/Elmish/React/Ref.purs
+++ b/src/Elmish/React/Ref.purs
@@ -31,7 +31,13 @@ instance CanPassToJavaScript (Ref a)
-- | view :: State -> Dispatch Message -> ReactElement
-- | view state dispatch =
-- | H.input_ "" { ref: callbackRef state.inputElement (dispatch <<< RefChanged), … }
+-- |
+-- | update :: State -> Message -> Transition Message State
+-- | update state = case _ of
+-- | RefChanged ref -> pure state { inputElement = ref }
+-- | …
-- | ```
+-- |
callbackRef :: forall el. Maybe el -> (Maybe el -> Effect Unit) -> Ref el
callbackRef ref setRef = mkCallbackRef $ mkEffectFn1 \ref' -> case ref, Nullable.toMaybe ref' of
Nothing, Nothing -> pure unit
diff --git a/src/Elmish/Trace.js b/src/Elmish/Trace.js
deleted file mode 100644
index d7462f1..0000000
--- a/src/Elmish/Trace.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export function traceTime(name) {
- return function(f) {
- const start = new Date()
- const res = f()
- const end = new Date()
- console.log(name + " took " + (end - start) + "ms")
- return res
- }
-}
-
\ No newline at end of file
diff --git a/src/Elmish/Trace.purs b/src/Elmish/Trace.purs
deleted file mode 100644
index 6089c37..0000000
--- a/src/Elmish/Trace.purs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Elmish.Trace
- ( traceTime
- ) where
-
-import Prelude
-
-foreign import traceTime :: forall a. String -> (Unit -> a) -> a
diff --git a/test/Foreign.purs b/test/Foreign.purs
index 4e3336d..cf90bbe 100644
--- a/test/Foreign.purs
+++ b/test/Foreign.purs
@@ -5,7 +5,7 @@ import Prelude
import Data.Array ((!!))
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
-import Data.Nullable (Nullable, notNull, null)
+import Data.Nullable (Nullable, notNull, null, toMaybe)
import Elmish.Foreign (class CanReceiveFromJavaScript, readForeign, readForeign')
import Foreign (unsafeToForeign)
import Foreign.Object (Object)
@@ -56,6 +56,19 @@ spec = describe "Elmish.Foreign" do
(read @{ x :: Nullable { y :: Int } } { x: null :: _ Int })
`shouldEqual` Just { x: null }
+ (read' @(Nullable (Array Int)) null <#> toMaybe) `shouldEqual` Right Nothing
+ (read' @(Array (Nullable Int)) [f 42, f 5, f null] <#> map toMaybe) `shouldEqual` Right [Just 42, Just 5, Nothing]
+
+ (read' @(Nullable { x :: Int }) null <#> toMaybe) `shouldEqual` Right Nothing
+
+ let r = read' @(Nullable { x :: Int, y :: Nullable { z :: Int } }) { x: 42, y: null }
+ (r <#> toMaybe <#> map _.x) `shouldEqual` Right (Just 42)
+ (r <#> toMaybe <#> map _.y <#> map toMaybe) `shouldEqual` Right (Just Nothing)
+
+ let q = read @{ foo :: String, one :: Nullable Int } { foo: "bar", one: null }
+ (q <#> _.foo) `shouldEqual` Just "bar"
+ (q <#> _.one <#> toMaybe) `shouldEqual` Just Nothing
+
it "treats missing record fields as null" do
read { x: "foo" } `shouldEqual` Just { x: "foo", y: null :: _ Int }
@@ -70,6 +83,20 @@ spec = describe "Elmish.Foreign" do
it "nullable" do
(read' @(Nullable Int) "foo") `shouldEqual` Left "Expected Nullable Int but got: \"foo\""
+ it "nullable array" do
+ (read' @(Nullable (Array Int)) [f 42, f "foo"]) `shouldEqual` Left "[1]: expected Int but got: \"foo\""
+ (read' @(Nullable (Array Int)) [f 42, f 5, f "foo"]) `shouldEqual` Left "[2]: expected Int but got: \"foo\""
+
+ it "nullable record" do
+ (read' @(Nullable { x :: Int, y :: Boolean }) { x: 42, y: "foo" })
+ `shouldEqual` Left ".y: expected Boolean but got: \"foo\""
+
+ (read' @(Nullable { x :: Int, y :: { z :: Int } }) { x: 42, y: "foo" })
+ `shouldEqual` Left ".y: expected Object but got: \"foo\""
+
+ (read' @(Nullable { x :: Int, y :: { z :: Nullable Int } }) { x: 42, y: null })
+ `shouldEqual` Left ".y: expected Object but got: "
+
it "nested within array" do
(read' @(Array Int) [f 42, f "foo"]) `shouldEqual` Left "[1]: expected Int but got: \"foo\""
(read' @(Array Int) [f 42, f 5, f "foo"]) `shouldEqual` Left "[2]: expected Int but got: \"foo\""
diff --git a/test/Main.purs b/test/Main.purs
index ac96583..c4c97c0 100644
--- a/test/Main.purs
+++ b/test/Main.purs
@@ -7,6 +7,7 @@ import Effect.Aff (launchAff_)
import Test.Component as Component
import Test.Foreign as Foreign
import Test.LocalState as LocalState
+import Test.ReactElement as ReactElement
import Test.Spec.Reporter (specReporter)
import Test.Spec.Runner (runSpec)
import Test.Subscriptions as Subscriptions
@@ -17,3 +18,4 @@ main = launchAff_ $ runSpec [specReporter] do
Component.spec
LocalState.spec
Subscriptions.spec
+ ReactElement.spec
diff --git a/test/ReactElement.js b/test/ReactElement.js
new file mode 100644
index 0000000..b2afcdc
--- /dev/null
+++ b/test/ReactElement.js
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export const isFragment = e => e.type === React.Fragment
+export const elementChildren = e => e.props.children
+export const elementType = e => typeof e === 'string' ? 'text' : e.type
+export const elementText = e =>
+ typeof e === 'string' ? e :
+ typeof e.props.children === 'string' ? e.props.children :
+ e.props.children.map(elementText).join('|')
diff --git a/test/ReactElement.purs b/test/ReactElement.purs
new file mode 100644
index 0000000..9abf03c
--- /dev/null
+++ b/test/ReactElement.purs
@@ -0,0 +1,56 @@
+module Test.ReactElement (spec) where
+
+import Prelude
+
+import Data.Array (fold)
+import Elmish (ReactElement)
+import Elmish.HTML.Styled as H
+import Test.Spec (Spec, describe, it)
+import Test.Spec.Assertions (shouldEqual)
+
+spec :: Spec Unit
+spec = describe "Elmish.React.ReactElement" do
+ describe "Monoid instance" do
+
+ it "should have `empty` as identity" do
+ (H.div "" "One" <> mempty)
+ `shouldBeFragmentOf` ["div:One"]
+ (mempty <> H.div "" "Two")
+ `shouldBeFragmentOf` ["div:Two"]
+
+ it "should append two elements" do
+ (H.div "" "One" <> H.span "" "Two")
+ `shouldBeFragmentOf` ["div:One", "span:Two"]
+
+ it "should append one fragment and one element" do
+ (H.fragment [ H.text "One", H.p "" "Two" ] <> H.text "Three")
+ `shouldBeFragmentOf` ["text:One", "p:Two", "text:Three"]
+
+ it "should append one element and one fragment" do
+ (H.a "" "One" <> H.fragment [ H.text "Two", H.text "Three" ])
+ `shouldBeFragmentOf` ["a:One", "text:Two", "text:Three"]
+
+ it "should append two fragments" do
+ (H.fragment [ H.b "" "One", H.div "" "Two" ] <> H.fragment [ H.p "" "Three", H.text "Four" ])
+ `shouldBeFragmentOf` ["b:One", "div:Two", "p:Three", "text:Four"]
+
+ it "should not flatten nested elements" do
+ (H.div "" [H.text "One", H.text "Two"] <> H.p "" "Three" <> H.a "" [H.text "Four", H.p "" "Five"])
+ `shouldBeFragmentOf` ["div:One|Two", "p:Three", "a:Four|Five"]
+
+ it "should be foldable" do
+ (fold $ H.text <$> ["One", "Two", "Three"])
+ `shouldBeFragmentOf` ["text:One", "text:Two", "text:Three"]
+
+ where
+ shouldBeFragmentOf r expected = do
+ isFragment r `shouldEqual` true
+ showFragment r `shouldEqual` expected
+
+ showFragment f = showElement <$> elementChildren f
+ showElement e = elementType e <> ":" <> elementText e
+
+foreign import elementChildren :: ReactElement -> Array ReactElement
+foreign import elementType :: ReactElement -> String
+foreign import elementText :: ReactElement -> String
+foreign import isFragment :: ReactElement -> Boolean