Elmish.Test
- Package
- purescript-elmish-testing-library
- Repository
- collegevine/purescript-elmish-testing-library
Elmish Testing Library tries to roughly follow the general API shape of libraries like JS Selenium or Ruby/Rails Capybara:
testComponent
ortestElement
to mount the component and start a test.find "button.some-class"
- to find a single element (will crash when not found or found more than one).findAll "div.main-content"
- to find zero or multiple elements.- Query elements via
text
,html
,attr
,tagName
, and so on. - Simulate events via
fireEvent
or convenience facades likeclick
,clickOn
, orchange
- Chain operations via
>>
, likefind "button" >> text
orfind "div" >> find "a" >> attr "href"
within
to zoom into an element and run some code in its context.forEach
andmapEach
to run computations in the context of multiple elements in sequence.
An illustrating example:
spec :: Spec Unit
spec =
describe "My component" $
it "illustrates how to write a test" $
testComponent { init, view, update } do
find "h1" >> text >>= shouldEqual "Hello"
find "input[type=text]" >> change "Some text in the textbox"
find "button" >> click
within "div.wrapper" do
text >>= (_ `shouldContain` "Some text inside a wapper")
findAll "a" >> forEach do
attr "href" >>= shouldEqual "http://foo.bar"
Every DOM-manipulating operation (e.g. find
, text
, fireEvent
, and so
on) doesn't take a DOM node as a parameter, but instead runs in a monad
that always has a "current" (or "focused") node in context. All operations
always apply to that "current" node.
The current node can be changed (or "refocused", or "zoomed") via within
or within'
. These functions find (or take) another node and run a
computation with that node as "current", thus making all operations inside
a within
apply to it.
In operator form >>
, these functions allow "chaining" operations
together, as in:
find "button" >> click
-- equivalent to:
b <- find "button"
within' b click
The difference is admittedly only cosmetic, but it allows for nice looking code, like:
-- click a button within the 6th subnode of the root <div>
find "div" >> childAt 5 >> find "button" >> click
If you have an Element
value on your hands, you can apply a
DOM-manipulating operation to it via $$
(prefix) or ##
(postfix), for
example:
button <- find "button"
click $$ button -- equivalent to: within' button click
button ## click -- equivalent to: within' button click
These operators are necessary, because operations can't be applied as
functions (e.g. click button
), since they don't take a node as parameter.
Finally, if you have an array of elements (e.g. obtained via findAll
),
you could iterate over them with traverse
, like:
findAll "button" >>= traverse_ \button -> button ## click
findAll "a" >>= traverse_ \a ->
within' a do
attr "href" >>= shouldEqual "http://foo"
text >>= shouldEqual "Click me"
But it's more convenient to use special helper functions forEach
and
mapEach
instead. These functions run the given computation in the context
of every element in the array:
findAll "button" >> forEach click
findAll "a" >> forEach do
attr "href" >>= shouldEqual "http://foo"
text >>= shouldEqual "Click me"
Or even:
findAll "a" >> mapEach text >>= shouldEqual ["Click me", "Click me"]
Re-exports from Elmish.Test.Bootstrap
#testElement Source
testElement :: forall m a. MonadAff m => ReactElement -> ReaderT TestState m a -> m a
A convenience version of testComponent
for "pure" components - i.e.
components that consist only of view
, no init
or update
.
#testComponent Source
testComponent :: forall m a msg state. MonadAff m => ComponentDef msg state -> ReaderT TestState m a -> m a
Mount the given component to a DOM element, run the given computation in the context of that element, return the computation's result.
Example:
describe "My component" $
it "should work" $
testComponent { init, view, update } do
find "h1" >> text >>= shouldEqual "Hello"
Re-exports from Elmish.Test.Combinators
#within Source
within :: forall m a. Testable m => String -> m a -> m a
Finds an element by the given selector and runs the given computation in the context of that element.
Example:
describe "My component" $
it "should work" $
testComponent { init, view, update } do
within "section:nth-child(1)" do
find "h2" >> text >>= shouldEqual "First Section"
clickOn "button"
within "section:nth-child(2)" do
find "h2" >> text >>= shouldEqual "Second Section"
find "input[type=text]" >> change "Some Text"
#chainM Source
chainM :: forall m a. Testable m => m Element -> m a -> m a
Used in its operator form >>
, this function chains two DOM operations
together, taking the output of the first operation and making it context of
the second one. For example:
buttonInsideDiv <- find "div" >> find "button"
inputValue <- find "input" >> attr "value"
Re-exports from Elmish.Test.Discover
Re-exports from Elmish.Test.Events
#fireEvent Source
fireEvent :: forall m r. Testable m => CanPassToJavaScript (Record r) => String -> Record r -> m Unit
Simulates a React-friendly event with the given name and given args, firing it on the current-context element. The second argument is a record of values that gets merged into the event object.
For example:
find "button" >> fireEvent "click" {}
find "input" >> fireEvent "change" { target: { value: "some text" } }
find "input" >> fireEvent "keyDown" { key: "Enter", keyCode: 13, which: 13 }
This function uses the Simulate
function from react-dom/test-utils
. See
https://reactjs.org/docs/test-utils.html#simulate
If the arguments record contains a field target
, which in turn contains a
field value
, the value of that field is assigned to the value
property
of the current-context element before firing the event. This special case
is intended to support events targeted at input fields, such as change
or
input
, where the event handler usually tries to access the
event.target.value
field.
Re-exports from Elmish.Test.Query
#prop Source
prop :: forall m a. Testable m => DomPropType a => DomProp a -> m a
Returns the given property of the current-context element.
import Elmish.Test.DomProps as P
find "input" >> prop P.value >>= shouldEqual "hello"
#nearestEnclosingReactComponentName Source
nearestEnclosingReactComponentName :: forall m. Testable m => m String
Re-exports from Elmish.Test.SpinWait
#waitWhile' Source
waitWhile' :: forall m. Testable m => Milliseconds -> m Boolean -> m Unit
Performs active wait while the given condition is true. Times out with a crash after given time period.
#waitUntil' Source
waitUntil' :: forall m. Testable m => Milliseconds -> m Boolean -> m Unit
Performs active wait while the given condition is true. Times out with a crash after given time period.