Running it
What CI runs and the output it checks, byte for byte.
dawn run examples/projects/tea_dom_counter
with this on stdin:
{"op":"init"}
{"op":"event","model":"0","path":[2,1],"event":"click"}
{"op":"event","model":"1","path":[2,0],"event":"click"}
{"op":"event","model":"0","path":[2,0],"event":"click"}
{"op":"event","model":"-1","path":[2,2],"event":"click"}
{"op":"event","model":"0","path":[1],"event":"click"}
not json at all
{"ok":true,"model":"0","patches":[{"path":[],"op":"replace","node":{"t":"elem","tag":"div","props":[["class","counter"]],"on":[],"kids":[{"t":"elem","tag":"h1","props":[],"on":[],"kids":[{"t":"text","s":"dawn counter"}]},{"t":"elem","tag":"p","props":[["class","count"]],"on":[],"kids":[{"t":"text","s":"0"}]},{"t":"elem","tag":"div","props":[["class","row"]],"on":[],"kids":[{"t":"elem","tag":"button","props":[],"on":["click"],"kids":[{"t":"text","s":"-"}]},{"t":"elem","tag":"button","props":[],"on":["click"],"kids":[{"t":"text","s":"+"}]},{"t":"elem","tag":"button","props":[],"on":["click"],"kids":[{"t":"text","s":"reset"}]}]}]}}]}
{"ok":true,"model":"1","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"1"}}]}
{"ok":true,"model":"0","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"0"}}]}
{"ok":true,"model":"-1","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"-1"}}]}
{"ok":true,"model":"0","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"0"}}]}
{"ok":false,"kind":"no-handler","error":"nothing at [1] listens for `click`"}
{"ok":false,"kind":"bad-request","error":"not JSON: invalid literal at offset 0"}
examples/projects/tea_dom_counter/src/main.dawn
# The whole io surface of the demo: one call. The same binary answers a
# browser (built with `dawnc build --target wasm --reactor`) and a shell:
#
# $ echo '{"op":"init"}' | dawn run examples/projects/tea_dom_counter
use tea_core/app.{update, view}
use tea_dom/reactor.{serve}
use counter.{init, encode, decode}
pub fn main() -> Unit !io = serve(init(), encode, decode, update, view)
examples/projects/tea_dom_counter/src/counter.dawn
# The whole application: a model, three messages, `update` and `view`, plus
# the text form of the model the wire carries between turns. No io and no
# host; `main.dawn` is the one call that connects it to one.
#
# `update` and `view` are an `impl App`, and `main` hands the trait's two
# methods to `serve` by name. The gates' fixtures live in
# examples/projects/tea_dom_counter_contract, not here.
use tea_core/app.{App, view}
use tea_core/cmd.{Cmd, NoCmd}
use tea_dom/node.{Node}
use tea_dom/dsl.{button, div, el, text}
## The whole model: how many times the user has pressed something, net.
pub type Counter = { n: Int }
## What the three buttons mean.
pub type Msg =
| Inc
| Dec
| Reset
## The model a fresh page starts from.
pub fn init() -> Counter = Counter { n: 0 }
impl App[Counter] {
type Msg = Msg
type View = Node[Msg]
fn update(m: Counter, msg: Msg) -> (Counter, Cmd[Msg]) =
match msg {
Inc -> (Counter { n: m.n + 1 }, NoCmd)
Dec -> (Counter { n: m.n - 1 }, NoCmd)
Reset -> (init(), NoCmd)
}
fn view(m: Counter) -> Node[Msg] =
div(class: "counter", kids: [
el("h1", kids: [text("dawn counter")]),
el("p", class: "count", kids: [text(to_string(m.n))]),
div(class: "row", kids: [button("-", Dec), button("+", Inc), button("reset", Reset)]),
])
}
## A model as text, and back. The host carries it untouched between turns.
pub fn encode(m: Counter) -> String = to_string(m.n)
## Total, because a host may hand back anything: unreadable text reads as the
## initial model rather than as a panic.
pub fn decode(s: String) -> Counter =
match parse_int(s) {
Some(n) -> Counter { n: n }
None -> init()
}
test "the view is a value, so a frame is one assertion" {
let want: Node[Msg] =
div(class: "counter", kids: [
el("h1", kids: [text("dawn counter")]),
el("p", class: "count", kids: [text("0")]),
div(class: "row", kids: [button("-", Dec), button("+", Inc), button("reset", Reset)]),
])
assert init().view() == want
}