1(window.webpackJsonp=window.webpackJsonp||[]).push([[6],{398:function(t,a,s){t.exports=s.p+"assets/img/HomeAutomationExample.06c64829.svg"},409:function(t,a,s){"use strict";s.r(a);var e=s(56),n=Object(e.a)({},(function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"testing-programs-with-cmds"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#testing-programs-with-cmds"}},[t._v("#")]),t._v(" Testing programs with Cmds")]),t._v(" "),e("p",[e("code",[t._v("elm-program-test")]),t._v(" allows you to test and simulate responses to\n"),e("code",[t._v("Cmd")]),t._v("s produced by your Elm program.\n(The rest of this page walks through an example of how to do that.)\nTesting your program in this way gives a few notable benefits:")]),t._v(" "),e("p",[t._v("Your tests will\n"),e("strong",[t._v("more clearly describe how your app interacts with other systems")]),t._v('.\nSteps in your test will read like\n"then the pending HTTP request to '),e("code",[t._v("example.com")]),t._v(' fails with a network error"\ninstead of\n"then the update function gets called with the '),e("code",[t._v("FetchResponse (Err NetworkError)")]),t._v(' message".')]),t._v(" "),e("p",[t._v("Your tests will be "),e("strong",[t._v("more robust to refactoring of your implementation code")]),t._v(".\nBecause your tests don't need to directly refer to the internals of your program,\nyour tests will still be valid even if you make drastic changes to the architecture of your code\n(like extracting new modules, renaming types or variants,\nor entirely reworking the flow of messages within your program).")]),t._v(" "),e("div",{staticClass:"custom-block warning"},[e("p",{staticClass:"custom-block-title"},[t._v("NOTE")]),t._v(" "),e("p",[e("code",[t._v("elm-program-test")]),t._v("'s features for testing effects depend on\nbeing able to know what a given effect value represents.\nIn the "),e("a",{attrs:{href:"https://guide.elm-lang.org/effects/",target:"_blank",rel:"noopener noreferrer"}},[t._v("Elm architecture"),e("OutboundLink")],1),t._v(",\neffects are normally represented by the "),e("code",[t._v("Cmd msg")]),t._v(" type.\nUnfortunately, Elm does not currently provide a way to\ninspect "),e("code",[t._v("Cmd")]),t._v(" values to see which commands they represent.\nThere's some extra work required to get around this current limitation,\nwhich the rest of this page will walk you through.")]),t._v(" "),e("p",[t._v("Hopefully you'll agree that the benefits (listed above) of testing your program in this way\nwill be worth the extra effort!\nBut be aware of this trade-off when considering whether this type of testing is right for your program.")])]),t._v(" "),e("h2",{attrs:{id:"introducing-the-example-program"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#introducing-the-example-program"}},[t._v("#")]),t._v(" Introducing the example program")]),t._v(" "),e("p",[t._v("The rest of this page will be working with an example program\nthat interacts with a fictional API for controlling the lights in your house.")]),t._v(" "),e("p",[t._v("This diagram shows the architecture of the example application.\nThe Elm code makes HTTP requests to two different endpoints:\n"),e("code",[t._v("/v1/devices")]),t._v(" to get a list of all the lights that can be controlled,\nand "),e("code",[t._v("/v1/devices/:id")]),t._v(" to change the setting of a specific light.")]),t._v(" "),e("p",[e("img",{attrs:{src:s(398),alt:"architecture diagram described in the preceding paragraph"}})]),t._v(" "),e("h2",{attrs:{id:"goal-the-ideal-test"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#goal-the-ideal-test"}},[t._v("#")]),t._v(" Goal: the ideal test")]),t._v(" "),e("p",[t._v("The test we want to write represents the following scenario:")]),t._v(" "),e("ol",[e("li",[t._v("The user loads the main page")]),t._v(" "),e("li",[t._v("The page requests the list of lights")]),t._v(" "),e("li",[t._v("The server responds with the list of lights and their current states")]),t._v(" "),e("li",[t._v('The user clicks "Turn on" for the kitchen lights')]),t._v(" "),e("li",[t._v("We expect the page to send a request to the server with the correct values")])]),t._v(" "),e("p",[t._v("Here's what that test will look like in code:")]),t._v(" "),e("div",{staticClass:"language-elm extra-class"},[e("pre",{pre:!0,attrs:{class:"language-elm"}},[e("code",[e("span",{pre:!0,attrs:{class:"token import-statement"}},[e("span",{pre:!0,attrs:{class:"token keyword"}}
1,[t._v("import")]),t._v(" Expect")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token import-statement"}},[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" ProgramTest")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token import-statement"}},[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" Test "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("exposing")]),t._v(" ")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("test")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"controlling a light"')]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("<|")]),t._v("\n \\"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("start")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|>
1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("ProgramTest.simulateHttpOk")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"GET"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"http://localhost:8003/lighting_service/v1/devices"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"""[{"id":"K001", "name":"Kitchen", "dimmable":false, "value":0}]"""')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|>")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("ProgramTest.clickButton")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Turn on"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|>")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("ProgramTest.expectHttpRequest")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"POST"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"http://localhost:8003/lighting_service/v1/devices/K001"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(">>")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Expect.equal")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"""{"value":1}"""')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),e("p",[t._v("However, running this test produces the following failure:")]),t._v(" "),e("div",{staticClass:"language- extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("â HomeAutomationExampleTest\nâ controlling a light\n\n TEST SETUP ERROR: In order to use simulateHttpResponse,\n you MUST use ProgramTest.withSimulatedEffects before calling ProgramTest.start\n\n\nTEST RUN FAILED\n")])])]),e("p",[t._v("As the error explains, we must use "),e("code",[t._v("ProgramTest.withSimulatedEffects")]),t._v("\nbefore the test will work.\nBut using it requires us to provide a function of type "),e("code",[t._v("effect -> ProgramTest.SimulatedEffect")]),t._v(".\nBecause the effect type of our program\n(the type that "),e("code",[t._v("init")]),t._v(" and "),e("code",[t._v("update")]),t._v(" return as the second item in the tuple)\nis currently "),e("code",[t._v("Cmd Msg")]),t._v(" (which Elm does not currently allow us to inspect the values of),\nwe'll need to do the following before the test will work:")]),t._v(" "),e("ol",[e("li",[t._v("Make a new type which can represent all the effects our program can produce.")]),t._v(" "),e("li",[t._v("Implement the required "),e("code",[t._v("effect -> ProgramTest.SimulatedEffect")]),t._v(" function.")]),t._v(" "),e("li",[t._v("Use "),e("code",[t._v("withSimulatedEffects")]),t._v(" to set up the test.")]),t._v(" "),e("li",[t._v("â
Watch the test pass!")])]),t._v(" "),e("h2",{attrs:{id:"making-it-work"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#making-it-work"}},[t._v("#")]),t._v(" Making it work")]),t._v(" "),e("h3",{attrs:{id:"planning-the-change"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#planning-the-change"}},[t._v("#")]),t._v(" Planning the change")]),t._v(" "),e("p",[t._v("Since Elm does not currently allow us to inspect "),e("code",[t._v("Cmd")]),t._v(" values,\nthe first thing we need to do is define a new type that we "),e("em",[t._v("can")]),t._v(" inspect\nthat can represent all the effects that our "),e("code",[t._v("update")]),t._v(" and "),e("code",[t._v("init")]),t._v(" functions\nwant to produce.\nOur "),e("code",[t._v("init")]),t._v(" and "),e("code",[t._v("update")]),t._v(" functions will change to use this new type,\nand we'll also need to define a function that can turn this new type\ninto "),e("code",[t._v("Cmd")]),t._v("s for when our program is compiled for production.")]),t._v(" "),e("p",[t._v("So in our existing main module:")]),t._v(" "),e("div",{staticClass:"language-elm extra-class"},[e("pre",{pre:!0,attrs:{class:"language-elm"}},[e("code",[e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("init")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Flags")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Model")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Cmd")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("update")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Model")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Model")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Cmd")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),e("p",[t._v("will change to:")]),t._v(" "),e("div",{staticClass:"language-elm extra-class"},[e("pre",{pre:!0,attrs:{class:"language-elm"}},[e("code",[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("type")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Effect")]),t._v("\n\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("init")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Flags")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Model")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("update")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Model")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Model")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("perform")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Cmd")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v("\n")])])]),e("h3",{attrs:{id:"defining-an-effect-type"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#defining-an-effect-type"}},[t._v("#")]),t._v(' Defining an "Effect" type')]),t._v(" "),e("p",[t._v("Taking a look through the "),e("code",[t._v("init")]),t._v(" and "),e("code",[t._v("update")]),t._v(" functions,\n"),t._v("\nthe following "),e("code",[t._v("Cmd")]),t._v("s are being produced â\nthe new type we make will need to have a variant for each one:")]),t._v(" "),e("ul",[e("li",[e("code",[t._v("Cmd.none")])]),t._v(" "),e("li",[e("code",[t._v("loadDeviceList")]),t._v(", which itself uses "),e("code",[t._v("Http.get")])]),t._v(" "),e("li",[e("code",[t._v("changeLight")]),t._v(", which itself uses "),e("code",[t._v("Http.post")])])]),t._v(" "),e("p",[t._v("Each variant we create should contain just enough information to\ncall the functions necessary to create the corresponding "),e("code",[t._v("Cmd")]),t._v(".\nFollowing that guideline, we'll end up with an "),e("code",[t._v("Effect")]),t._v(" type like this\nand a corresponding "),e("code",[t._v("perform")]),t._v(" function:")]),t._v(" "),e("div",{staticClass:"language-elm extra-class"},[e("pre",{pre:!0,attrs:{class:"language-elm"}},[e("code",[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("type")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Effect")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("NoEffect")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("GetDeviceList")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("String")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Json.Decode.Decoder")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("List")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Light")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Result")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Http.Error")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("List")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Light")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}
1")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("ChangeLight")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("String")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Json.Encode.Value")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Json.Decode.Decoder")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Light")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Result")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Http.Error")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Light")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("perform")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Cmd")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("perform")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("case")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("of")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("NoEffect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Cmd.none")]),t._v("\n\n "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("GetDeviceList")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Http.get")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("expect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Http.expectJson")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}
1")]),t._v("\n\n "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("ChangeLight")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Http.post")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Http.jsonBody")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("expect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Http.expectJson")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])])]),e("p",[t._v("After changing "),e("code",[t._v("update")]),t._v(" and "),e("code",[t._v("init")]),t._v(" to return the corresponding "),e("code",[t._v("Effect")]),t._v(" instead of a "),e("code",[t._v("Cmd")]),t._v(",\nthe final change to make the program compile again is to update our "),e("code",[t._v("main")]),t._v("\nto make use of the "),e("code",[t._v("perform")]),t._v(" function:")]),t._v(" "),e("div",{staticClass:"language-elm extra-class"},[e("div",{staticClass:"highlight-lines"},[e("br"),e("br"),e("br"),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("br"),e("br"),e("br"),e("br")]),e("pre",{pre:!0,attrs:{class:"language-elm"}},[e("code",[e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("main")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Program")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Flags")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Model")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Msg")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("main")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Browser.document")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("init")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("\n \\"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("flags")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("init")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("flags")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|>
1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Tuple.mapSecond")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("perform")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("update")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("\n \\"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("msg")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("model")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("update")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("msg")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("model")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|>")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Tuple.mapSecond")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("perform")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("subscriptions")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("subscriptions")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("view")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("view")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])])]),e("h3",{attrs:{id:"simulating-effects"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#simulating-effects"}},[t._v("#")]),t._v(" Simulating effects")]),t._v(" "),e("p",[t._v("Now that our "),e("code",[t._v("update")]),t._v(" and "),e("code",[t._v("init")]),t._v(" functions use an effect type that\nwe can inspect the values of, we can write a function for our test suite\nthat can convert our new "),e("code",[t._v("Effect")]),t._v(" type into simulated effects that\n"),e("code",[t._v("elm-program-test")]),t._v(" can understand:")]),t._v(" "),e("div",{staticClass:"language-elm extra-class"},[e("pre",{pre:!0,attrs:{class:"language-elm"}},[e("code",[e("span",{pre:!0,attrs:{class:"token import-statement"}},[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" SimulatedEffect.Cmd")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token import-statement"}},[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" SimulatedEffect.Http")]),t._v("\n\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("simulateEffects")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.Effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("ProgramTest.SimulatedEffect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.Msg")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("simulateEffects")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("case")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("effect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("of")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.NoEffect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("SimulatedEffect.Cmd.none")]),t._v("\n\n "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.GetDeviceList")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}
1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("SimulatedEffect.Http.get")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("expect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("SimulatedEffect.Http.expectJson")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.ChangeLight")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("->")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("SimulatedEffect.Http.post")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("url")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("SimulatedEffect.Http.jsonBody")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("body")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("expect")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("SimulatedEffect.Http.expectJson")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("onResult")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("decoder")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])])]),e("p",[t._v("You may notice that this looks "),e("em",[t._v("extremely")]),t._v(" similar to the "),e("code",[t._v("perform")]),t._v(" function we wrote earlier.\nThe "),e("code",[t._v("SimulatedEffect.*")]),t._v(" modules are designed to parallel the core Elm modules that produce "),e("code",[t._v("Cmd")]),t._v("s\nso that writing your "),e("code",[t._v("simulateEffects")]),t._v(" function is as straightforward as possible.")]),t._v(" "),e("p",[t._v("One last step! Now that we have a function of the required type,\nwe can use it to enable effect simulation in our tests:")]),t._v(" "),e("div",{staticClass:"language-elm extra-class"},[e("div",{staticClass:"highlight-lines"},[e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("div",{staticClass:"highlighted"},[t._v("Â ")]),e("br"),e("br")]),e("pre",{pre:!0,attrs:{class:"language-elm"}},[e("code",[e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("start")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}}
1,[t._v("ProgramTest")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.Model")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.Msg")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token constant"}},[t._v("Main.Effect")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("start")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("ProgramTest.createDocument")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("init")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Main.init")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("update")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Main.update")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("view")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("Main.view")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|>
1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("ProgramTest.withSimulatedEffects")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("simulateEffects")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|>")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token hvariable"}},[t._v("ProgramTest.start")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),e("p",[t._v("Now that we've enabled effects simulation, "),e("a",{attrs:{href:"#goal-the-ideal-test"}},[t._v("our original test")]),t._v("\nwill successfully run!")]),t._v(" "),e("p",[t._v("You may have noticed that the test itself never directly refers\nto the effect values ("),e("code",[t._v("GetDeviceList")]),t._v(" and "),e("code",[t._v("ChangeLight")]),t._v(") that we defined.\n"),e("code",[t._v("elm-program-test")]),t._v(" handles that for you â\nwhen a "),e("code",[t._v("ProgramTest")]),t._v(" receives an effect from your "),e("code",[t._v("init")]),t._v(" or "),e("code",[t._v("update")]),t._v(" functions,\nit will use the "),e("code",[t._v("simulateEffects")]),t._v(" function we provided to understand what the effect means\nand update the state of the test accordingly.\nYou don't need to worry about the details of how this works â\njust know that as long as you provide a "),e("code",[t._v("simulateEffects")]),t._v(" function\nyou'll be able to use the full API provided by "),e("code",[t._v("ProgramTest")]),t._v(" for testing HTTP requests.")]),t._v(" "),e("h2",{attrs:{id:"try-it-out"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#try-it-out"}},[t._v("#")]),t._v(" Try it out")]),t._v(" "),e("p",[t._v("You can play around with the example here:\n"),e("a",{attrs:{href:"https://ellie-app.com/6wsbBjZ9z2Ma1",target:"_blank",rel:"noopener noreferrer"}},[t._v("https://ellie-app.com/6wsbBjZ9z2Ma1"),e("OutboundLink")],1),t._v("\nTry editing the tests or the implementation to make the tests fail\nand see what kind of failure messages you get.")])])}),[],!1,null,null,null);a.default=n.exports}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.