Skip to content

Commit

Permalink
Migrate to JSDOM (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
fsoikin committed Sep 16, 2022
1 parent b859ccf commit 0f5e389
Show file tree
Hide file tree
Showing 11 changed files with 993 additions and 494 deletions.
1,256 changes: 869 additions & 387 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
},
"license": "MIT",
"dependencies": {
"@happy-dom/global-registrator": "^6.0.2",
"global-jsdom": "^8.0.0",
"jsdom": "^16.5.1",
"bower": "^1.8.14",
"esbuild": "^0.14.48",
"pulp": "^16.0.2",
Expand Down
4 changes: 3 additions & 1 deletion src/Elmish/Test.purs
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@
module Elmish.Test
( module Elmish.Test.Bootstrap
, module Elmish.Test.Combinators
, module Elmish.Test.Discover
, module Elmish.Test.Events
, module Elmish.Test.Query
, module Elmish.Test.SpinWait
) where

import Elmish.Test.Bootstrap (testComponent, testElement)
import Elmish.Test.Combinators (chain, chainM, forEach, mapEach, within, within', (##), ($$), (>>))
import Elmish.Test.Discover (childAt, children, find, findAll, findFirst, findNth)
import Elmish.Test.Events (change, click, clickOn, fireEvent)
import Elmish.Test.Query (attr, exists, find, findAll, findFirst, findNth, html, prop, tagName, text)
import Elmish.Test.Query (attr, count, exists, html, prop, tagName, text)
import Elmish.Test.SpinWait (waitUntil, waitUntil', waitWhile, waitWhile')
4 changes: 2 additions & 2 deletions src/Elmish/Test/Bootstrap.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GlobalRegistrator } from '@happy-dom/global-registrator';
import registerDom from 'global-jsdom'

export const ensureDom_ = () => {
if (typeof window === "undefined") {
GlobalRegistrator.register()
registerDom()
}
}
2 changes: 1 addition & 1 deletion src/Elmish/Test/Combinators.purs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Prelude

import Control.Monad.Reader (local)
import Data.Traversable (traverse, traverse_)
import Elmish.Test.Query (find)
import Elmish.Test.Discover (find)
import Elmish.Test.State (class Testable, TestState(..))
import Web.DOM (Element)

Expand Down
102 changes: 102 additions & 0 deletions src/Elmish/Test/Discover.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module Elmish.Test.Discover
( childAt
, children
, find
, findAll
, findFirst
, findNth
)
where

import Prelude

import Data.Array (fold, length, mapMaybe, (!!))
import Data.Maybe (Maybe(..))
import Effect.Class (liftEffect)
import Elmish.Test.State (class Testable, crash, currentNode)
import Web.DOM (Element)
import Web.DOM.Element as DOM
import Web.DOM.Node (childNodes)
import Web.DOM.NodeList as NodeList
import Web.DOM.ParentNode (QuerySelector(..), querySelectorAll)

-- | Finds exactly one element by CSS selector. If the selector matches zero
-- | elements or more than one, this function will throw an exception.
-- |
-- | find "button" >> click
-- |
find :: m. Testable m => String -> m Element
find selector =
findAll selector >>= case _ of
[el] -> pure el
els -> crash $ "Expected to find one element matching '" <> selector <> "', but found " <> show (length els)

-- | Finds the first element out of possibly many matching the given selector.
-- | If there are no elements matching the selector, throws an exception.
findFirst :: m. Testable m => String -> m Element
findFirst = findNth 0


-- | Finds the n-th (zero-based) element out of possibly many matching the given
-- | selector. If there are no elements matching the selector, throws an
-- | exception.
findNth :: m. Testable m => Int -> String -> m Element
findNth idx selector =
findAll selector >>= \all -> case all !! idx of
Just el -> pure el
Nothing -> crash $ fold
[ "Expected to find "
, show idx
, "th element matching '"
, selector
, "', but there are only "
, show (length all)
, " elements"
]

-- | Finds zero or more elements by CSS selector.
-- |
-- | findAll "button" >>= traverse_ \b -> click $$ b
-- |
-- | divs <- find "div"
-- | length divs `shouldEqual` 10
-- |
findAll :: m. Testable m => String -> m (Array Element)
findAll selector = do
current <- currentNode
liftEffect $
querySelectorAll (QuerySelector selector) (DOM.toParentNode current)
>>= NodeList.toArray
<#> mapMaybe DOM.fromNode

-- | Returns all immediate child elements of the current-context element.
-- |
-- | find "div" >> children >>= traverse_ \child ->
-- | tag <- tagName
-- | when (tag == "BUTTON")
-- | click
-- |
children :: m. Testable m => m (Array Element)
children = do
current <- currentNode
liftEffect $
childNodes (DOM.toNode current)
>>= NodeList.toArray
<#> mapMaybe DOM.fromNode

-- | Within the current-context element, finds a child element at the given
-- | index. Crashes if the is no child with the given index.
childAt :: m. Testable m => Int -> m Element
childAt idx = do
cs <- children
case cs !! idx of
Just e ->
pure e
Nothing ->
crash $ fold
[ "Expected to find a child element at index "
, show idx
, ", but there are only "
, show (length cs)
, " children"
]
2 changes: 0 additions & 2 deletions src/Elmish/Test/DomProps.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ newtype DomProp (a :: Type) = DomProp String

value = DomProp "value" :: DomProp String

href = DomProp "href" :: DomProp String

disabled = DomProp "disabled" :: DomProp Boolean

checked = DomProp "checked" :: DomProp Boolean
2 changes: 1 addition & 1 deletion src/Elmish/Test/Events.purs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Effect.Class (liftEffect)
import Effect.Uncurried (EffectFn3, runEffectFn3)
import Elmish.Foreign (class CanPassToJavaScript)
import Elmish.Test.Combinators ((>>))
import Elmish.Test.Query (find)
import Elmish.Test.Discover (find)
import Elmish.Test.State (class Testable, currentNode)
import Web.DOM (Element)

Expand Down
1 change: 0 additions & 1 deletion src/Elmish/Test/Query.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export const innerText_ = e => e.innerText || ""
export const outerHTML_ = e => e.outerHTML || ""
export const prop_ = (name, e) => e[name] || null // converting `undefined` to `null` so it can be handled via `Nullable`
108 changes: 12 additions & 96 deletions src/Elmish/Test/Query.purs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
module Elmish.Test.Query
( attr
, childAt
, children
, count
, exists
, find
, findAll
, findFirst
, findNth
, html
, prop
, tagName
Expand All @@ -16,108 +11,31 @@ module Elmish.Test.Query

import Prelude

import Data.Array (fold, length, mapMaybe, null, (!!))
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Array (length, null)
import Data.Maybe (fromMaybe)
import Data.Nullable as N
import Effect.Class (liftEffect)
import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)
import Elmish.Test.Discover (findAll)
import Elmish.Test.DomProps (class DomPropType, DomProp, defaultValue)
import Elmish.Test.State (class Testable, crash, currentNode)
import Elmish.Test.State (class Testable, currentNode)
import Web.DOM (Element)
import Web.DOM.Element as DOM
import Web.DOM.Node (childNodes)
import Web.DOM.NodeList as NodeList
import Web.DOM.ParentNode (QuerySelector(..), querySelectorAll)

-- | Finds exactly one element by CSS selector. If the selector matches zero
-- | elements or more than one, this function will throw an exception.
-- |
-- | find "button" >> click
-- |
find :: m. Testable m => String -> m Element
find selector =
findAll selector >>= case _ of
[el] -> pure el
els -> crash $ "Expected to find one element matching '" <> selector <> "', but found " <> show (length els)

-- | Finds the first element out of possibly many matching the given selector.
-- | If there are no elements matching the selector, throws an exception.
findFirst :: m. Testable m => String -> m Element
findFirst = findNth 0


-- | Finds the n-th (zero-based) element out of possibly many matching the given
-- | selector. If there are no elements matching the selector, throws an
-- | exception.
findNth :: m. Testable m => Int -> String -> m Element
findNth idx selector =
findAll selector >>= \all -> case all !! idx of
Just el -> pure el
Nothing -> crash $ fold
[ "Expected to find "
, show idx
, "th element matching '"
, selector
, "', but there are only "
, show (length all)
, " elements"
]

-- | Finds zero or more elements by CSS selector.
-- |
-- | findAll "button" >>= traverse_ \b -> click $$ b
-- |
-- | divs <- find "div"
-- | length divs `shouldEqual` 10
-- |
findAll :: m. Testable m => String -> m (Array Element)
findAll selector = do
current <- currentNode
liftEffect $
querySelectorAll (QuerySelector selector) (DOM.toParentNode current)
>>= NodeList.toArray
<#> mapMaybe DOM.fromNode

-- | Returns all immediate child elements of the current-context element.
-- |
-- | find "div" >> children >>= traverse_ \child ->
-- | tag <- tagName
-- | when (tag == "BUTTON")
-- | click
-- |
children :: m. Testable m => m (Array Element)
children = do
current <- currentNode
liftEffect $
childNodes (DOM.toNode current)
>>= NodeList.toArray
<#> mapMaybe DOM.fromNode

-- | Within the current-context element, finds a child element at the given
-- | index. Crashes if the is no child with the given index.
childAt :: m. Testable m => Int -> m Element
childAt idx = do
cs <- children
case cs !! idx of
Just e ->
pure e
Nothing ->
crash $ fold
[ "Expected to find a child element at index "
, show idx
, ", but there are only "
, show (length cs)
, " children"
]
import Web.DOM.Node (textContent)

-- | Returns `true` if at least one element exists matching the given CSS
-- | selector.
exists :: m. Testable m => String -> m Boolean
exists selector = not null <$> findAll selector

-- | Returns the number of elements within the current context that match the
-- | given selector.
count :: m. Testable m => String -> m Int
count selector = length <$> findAll selector

-- | Returns full inner text of the current-context element.
text :: m. Testable m => m String
text = currentNode >>= (liftEffect <<< runEffectFn1 innerText_)
text = currentNode >>= \el -> liftEffect $ textContent (DOM.toNode el)

-- | Returns HTML representation of the current-context element.
html :: m. Testable m => m String
Expand All @@ -141,8 +59,6 @@ prop :: ∀ m a. Testable m => DomPropType a => DomProp a -> m a
prop name = currentNode >>= \e -> liftEffect $
runEffectFn2 prop_ name e <#> N.toMaybe <#> fromMaybe defaultValue

foreign import innerText_ :: EffectFn1 Element String

foreign import outerHTML_ :: EffectFn1 Element String

foreign import prop_ :: a. EffectFn2 (DomProp a) Element (N.Nullable a)
3 changes: 1 addition & 2 deletions test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import Elmish.Test.DomProps as P
import Elmish.Test.Events (change, click, clickOn)
import Test.Spec (Spec, describe, it)
import Test.Spec.Assertions (shouldEqual)
import Test.Spec.Assertions.String (shouldContain)
import Test.Spec.Reporter (consoleReporter)
import Test.Spec.Runner (runSpec)

Expand Down Expand Up @@ -51,7 +50,7 @@ spec =
change "Frodo"
prop P.value >>= shouldEqual "Frodo"

text >>= (_ `shouldContain` "Hello, Frodo")
text >>= shouldEqual "1IncDecHello, Frodo"

-- findAll
buttons <- findAll "button"
Expand Down

0 comments on commit 0f5e389

Please sign in to comment.