Files
kjol/go/cmd/kjol-web/app/tworuntimes_test.go

124 lines
4.0 KiB
Go

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{}