//go:build js && wasm package wasmruntime import ( "strings" "syscall/js" "testing" "kjol/vdom" ) // buildServerDOM fakes what the HTML parser hands us: a real DOM tree, built from // the SERVER's VNode tree, which hydration then adopts. func buildServerDOM(server *vdom.VNode) js.Value { return createDOM(server, "") } // Hydration ADOPTS the server's DOM. If the server's HTML and the client's render // disagree, the disagreement has to be resolved in the CLIENT's favour — it is the // one that is running, and it is the only one that can still change. // // Left unreconciled, the divergence is permanent and invisible: the DOM keeps the // server's value, and no later re-render corrects it either, because updateAttrs // compares one client VNode against the next — which agree with each other and // disagree with the DOM. The symptom is a page that is right when you navigate to it // and wrong when you refresh onto it. func TestHydrationReconcilesAttributes(t *testing.T) { root := document.Call("createElement", "div") // What the server rendered (say, from a stale binary). server := vdom.El("main", vdom.Attr("class", "mx-auto max-w-5xl px-4")) root.Call("appendChild", buildServerDOM(server)) // What the client actually renders now. client := vdom.El("main", vdom.Attr("class", "mx-auto max-w-[100rem] px-4")) hydrateNode(root.Get("firstChild"), client) got := root.Get("firstChild").Call("getAttribute", "class").String() if got != "mx-auto max-w-[100rem] px-4" { t.Errorf("after hydration class = %q, want the client's value — the server's stale class was kept", got) } } // And the ordinary case must still work: matching markup is adopted, not rebuilt. func TestHydrationAdoptsMatchingMarkup(t *testing.T) { root := document.Call("createElement", "div") server := vdom.El("div", vdom.Attr("class", "a"), vdom.El("span", vdom.Text("hello")), ) root.Call("appendChild", buildServerDOM(server)) adopted := root.Get("firstChild") client := vdom.El("div", vdom.Attr("class", "a"), vdom.El("span", vdom.Text("hello")), ) hydrateNode(root.Get("firstChild"), client) // Same node — hydration adopted it rather than replacing it. if !rt(client).dom.Equal(adopted) { t.Error("hydration replaced a matching node instead of adopting it") } if got := root.Get("innerHTML").String(); !strings.Contains(got, "hello") { t.Errorf("content lost: %s", got) } } // A text node that disagrees is corrected too (this already worked; pin it). func TestHydrationCorrectsText(t *testing.T) { root := document.Call("createElement", "div") server := vdom.El("p", vdom.Text("stale")) root.Call("appendChild", buildServerDOM(server)) client := vdom.El("p", vdom.Text("fresh")) hydrateNode(root.Get("firstChild"), client) if got := root.Get("innerHTML").String(); !strings.Contains(got, "fresh") { t.Errorf("stale server text survived hydration: %s", got) } }