package app import ( "strings" "testing" "kjol/vdom" ) // The two-runtime demo.s whole claim is that its two panes are ONE function: the live // component on the left, and the HTML string the server sends on the right. If they could // drift, the page would be a lie told in the most embarrassing possible place. // // It lives on /wasm now, not on the front page — it is the Wasm Web engine.s argument, // and the front page is kjøl.s. The test followed it. // // So: render it, click the button the way the browser would, render again, and check // that BOTH panes moved. A pane rendered from a stale copy of the tree — or from a // second, hand-written one — fails here. func TestTwoRuntimePanesShareOneTree(t *testing.T) { page := DocsPage(Deps{Path: func() string { return "/wasm" }}) html := vdom.RenderHTML(page()) if !strings.Contains(html, "clicked 0 times") { t.Fatalf("the live pane did not render its initial state:\n%s", html) } // The right-hand pane is the ESCAPED HTML of the same tree, so the markup it shows // appears in the page's own markup double-escaped: <div ... if !strings.Contains(html, "<div class=") { t.Fatal("the right-hand pane is not showing rendered HTML at all") } clickButton(t, page(), "Click me") html = vdom.RenderHTML(page()) if strings.Count(html, "clicked 1 times") < 2 { t.Errorf("after one click, %d panes say \"clicked 1 times\" — both should:\n%s", strings.Count(html, "clicked 1 times"), html) } } // The byte count under the right-hand pane is the length of the string actually shown, // not a number typed in by hand — so it has to move when the markup does. func TestTwoRuntimeByteCountIsReal(t *testing.T) { page := DocsPage(Deps{Path: func() string { return "/wasm" }}) before := byteCountLabel(t, vdom.RenderHTML(page())) clickButton(t, page(), "Click me") // "clicked 0 times" -> "clicked 1 times" is the same length, so click into double // digits, where the markup genuinely grows by one byte. for i := 0; i < 10; i++ { clickButton(t, page(), "Click me") } after := byteCountLabel(t, vdom.RenderHTML(page())) if before == after { t.Errorf("the markup grew by a digit but the byte count did not move (%s) — it is not measuring the string", before) } } // byteCountLabel pulls the "N bytes of HTML" caption out of the rendered page. func byteCountLabel(t *testing.T, html string) string { t.Helper() i := strings.Index(html, " bytes of HTML") if i < 0 { t.Fatal("no byte-count caption on the /wasm overview") } start := strings.LastIndexByte(html[:i], '>') + 1 return html[start : i+len(" bytes of HTML")] } // clickButton finds a button by its label and fires its click handler. func clickButton(t *testing.T, n *vdom.VNode, label string) { t.Helper() if !findAndClickButton(n, label) { t.Fatalf("no clickable button labelled %q on the page", label) } } func findAndClickButton(n *vdom.VNode, label string) bool { if n == nil { return false } if n.Tag == "button" && strings.Contains(textOf(n), label) { if h := n.Events[vdom.EVENT_CLICK]; h != nil { h(clickEvent{}) return true } } for _, c := range n.Children { if findAndClickButton(c, label) { return true } } return false } func textOf(n *vdom.VNode) string { if n.Tag == "" { return n.Text } var b strings.Builder for _, c := range n.Children { b.WriteString(textOf(c)) } return b.String() } // clickEvent is a vdom.Event with no DOM behind it — enough to invoke a handler. type clickEvent struct{} func (clickEvent) PreventDefault() {} func (clickEvent) StopPropagation() {} func (clickEvent) Value() string { return "" } func (clickEvent) Checked() bool { return false } func (clickEvent) Key() string { return "" } func (clickEvent) ClientX() int { return 0 } func (clickEvent) ClientY() int { return 0 } func (clickEvent) Target() any { return nil } func (clickEvent) SetData(_, _ string) {} func (clickEvent) GetData(string) string { return "" } var _ vdom.Event = clickEvent{}