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:
testComponentortestElementto 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
fireEventor convenience facades likeclick,clickOn, orchange - Chain operations via
>>, likefind "button" >> textorfind "div" >> find "a" >> attr "href" withinto zoom into an element and run some code in its context.forEachandmapEachto 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 aA 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 aMount 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 aFinds 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 aUsed 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 UnitSimulates 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 aReturns the given property of the current-context element.
import Elmish.Test.DomProps as P
find "input" >> prop P.value >>= shouldEqual "hello"
Re-exports from Elmish.Test.SpinWait
#waitWhile' Source
waitWhile' :: forall m. Testable m => Milliseconds -> m Boolean -> m UnitPerforms 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 UnitPerforms active wait while the given condition is true. Times out with a crash after given time period.