Module

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 or testElement 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 like click, clickOn, or change
  • Chain operations via >>, like find "button" >> text or find "div" >> find "a" >> attr "href"
  • within to zoom into an element and run some code in its context.
  • forEach and mapEach 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 => Element -> m a -> m a

A more general version of within that accepts an Element instead of a CSS selector.

In its operator form ## this function can be used similarly to postfix function application, for example:

button <- find "button"
button ## click

#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"

#mapEach Source

mapEach :: forall m a. Testable m => m a -> Array Element -> m (Array a)

Runs the given computation multiple times, in the context of each of the given Elements, and returns their results as an array.

values <- findAll "input" >> mapEach (prop P.value)

#forEach Source

forEach :: forall m. Testable m => m Unit -> Array Element -> m Unit

Runs the given computation multiple times, in the context of each of the given Elements.

findAll "button" >> forEach click

#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"

#chain Source

chain :: forall m a. Testable m => m a -> Element -> m a

A flipped version of within'. In its operator form $$ this function can be used similarly to function application, for example:

button <- find "button"
click $$ button

#(>>) Source

Operator alias for Elmish.Test.Combinators.chainM (left-associative / precedence 8)

#($$) Source

Operator alias for Elmish.Test.Combinators.chain (left-associative / precedence 8)

#(##) Source

Operator alias for Elmish.Test.Combinators.within' (left-associative / precedence 8)

Re-exports from Elmish.Test.Discover

#findNth Source

findNth :: forall m. Testable m => Int -> String -> m Element

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.

#findFirst Source

findFirst :: forall m. Testable m => String -> m Element

Finds the first element out of possibly many matching the given selector. If there are no elements matching the selector, throws an exception.

#findAll Source

findAll :: forall m. Testable m => String -> m (Array Element)

Finds zero or more elements by CSS selector.

findAll "button" >>= traverse_ \b -> click $$ b

divs <- find "div" length divs shouldEqual 10

#find Source

find :: forall m. Testable m => String -> m Element

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

#children Source

children :: forall m. Testable m => m (Array Element)

Returns all immediate child elements of the current-context element.

find "div" >> children >>= traverse_ \child ->
  tag <- tagName
  when (tag == "BUTTON")
    click

#childAt Source

childAt :: forall m. Testable m => Int -> m Element

Within the current-context element, finds a child element at the given index. Crashes if the is no child with the given index.

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.

#clickOn Source

clickOn :: forall m. Testable m => String -> m Unit

A convenience function, finding an element by CSS selector and simulating the click event on it.

clickOn "button.t--my-button"

#click Source

click :: forall m. Testable m => m Unit

A convenience specialization of fireEvent, simulating the click event.

find "button" >> click

#change Source

change :: forall m. Testable m => String -> m Unit

A convenience specialization of fireEvent, simulating the change event with the given value.

find "input" >> change "some text"

Re-exports from Elmish.Test.Query

#text Source

text :: forall m. Testable m => m String

Returns full inner text of the current-context element.

#tagName Source

tagName :: forall m. Testable m => m String

Returns the tag name of the current-context element.

#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

#html Source

html :: forall m. Testable m => m String

Returns HTML representation of the current-context element.

#exists Source

exists :: forall m. Testable m => String -> m Boolean

Returns true if at least one element exists matching the given CSS selector.

#count Source

count :: forall m. Testable m => String -> m Int

Returns the number of elements within the current context that match the given selector.

#attr Source

attr :: forall m. Testable m => String -> m String

Returns the given attribute of the current-context element.

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.

#waitWhile Source

waitWhile :: forall m. Testable m => m Boolean -> m Unit

Performs active wait while the given condition is true. Times out with a crash after a second.

#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.

#waitUntil Source

waitUntil :: forall m. Testable m => m Boolean -> m Unit

Performs active wait while the given condition is false. Times out with a crash after a second.