121 lines
4.1 KiB
Go
121 lines
4.1 KiB
Go
//go:build js && wasm
|
|
|
|
package wasmruntime
|
|
|
|
import (
|
|
"syscall/js"
|
|
"testing"
|
|
|
|
"kjol/vdom"
|
|
)
|
|
|
|
// Run from kjol/go:
|
|
//
|
|
// GOOS=js GOARCH=wasm go test -exec="node testdata/domexec.js" ./wasmruntime
|
|
|
|
// Props travel through the vdom as strings, but `checked` is a BOOLEAN property, and in
|
|
// JavaScript every non-empty string is truthy — so `el.checked = "false"` ticks the box.
|
|
// A multi-select rendered that way shows every option as selected, forever.
|
|
//
|
|
// The reconciler must write a real boolean.
|
|
func TestBoolPropIsWrittenAsABoolean(t *testing.T) {
|
|
root := document.Call("createElement", "div")
|
|
box := vdom.NewRef()
|
|
|
|
unticked := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", false))
|
|
patchChildren(root, nil, one(unticked))
|
|
|
|
el, _ := box.Node().(js.Value)
|
|
if got := el.Get("checked"); got.Type() != js.TypeBoolean {
|
|
t.Fatalf(`checked is a %v, not a boolean — the string "false" is truthy in JS`, got.Type())
|
|
}
|
|
if el.Get("checked").Bool() {
|
|
t.Error("BoolProp(false) produced a TICKED checkbox")
|
|
}
|
|
}
|
|
|
|
// ...and it must keep tracking the signal. A tick that renders once and then never
|
|
// changes is the same bug wearing a different hat: the row would look right until you
|
|
// clicked it.
|
|
func TestBoolPropUpdatesOnRerender(t *testing.T) {
|
|
root := document.Call("createElement", "div")
|
|
box := vdom.NewRef()
|
|
|
|
off := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", false))
|
|
patchChildren(root, nil, one(off))
|
|
el, _ := box.Node().(js.Value)
|
|
|
|
on := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", true))
|
|
patchChildren(root, one(off), one(on))
|
|
if !el.Get("checked").Bool() {
|
|
t.Fatal("selecting an option did not tick its checkbox")
|
|
}
|
|
|
|
back := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", false))
|
|
patchChildren(root, one(on), one(back))
|
|
if el.Get("checked").Bool() {
|
|
t.Error("deselecting an option did not UNTICK its checkbox")
|
|
}
|
|
}
|
|
|
|
// A string prop is still a string — the boolean handling must not swallow an input's
|
|
// value, least of all the literal value "false".
|
|
func TestStringPropIsUntouched(t *testing.T) {
|
|
root := document.Call("createElement", "div")
|
|
r := vdom.NewRef()
|
|
|
|
patchChildren(root, nil, one(vdom.Input(vdom.WithRef(r), vdom.Prop("value", "false"))))
|
|
el, _ := r.Node().(js.Value)
|
|
if got := el.Get("value").String(); got != "false" {
|
|
t.Errorf("value = %q, want the string \"false\"", got)
|
|
}
|
|
}
|
|
|
|
// OverflowsX is how the multi-select decides its pills no longer fit. It compares
|
|
// scrollWidth (all the content) against clientWidth (what is visible) — NOT
|
|
// getBoundingClientRect, which reports the clipped box and so can never reveal an
|
|
// overflow.
|
|
func TestOverflowsX(t *testing.T) {
|
|
root := document.Call("createElement", "div")
|
|
|
|
cases := []struct {
|
|
name string
|
|
client, scroll, want float64
|
|
overflows bool
|
|
}{
|
|
{name: "content fits exactly", client: 200, scroll: 200},
|
|
{name: "content is wider than the box", client: 200, scroll: 340, overflows: true},
|
|
// Sub-pixel layout puts scrollWidth a hair over clientWidth on content that
|
|
// visually fits; believing that would collapse a field showing two pills.
|
|
{name: "sub-pixel noise is not an overflow", client: 200, scroll: 200.6},
|
|
// A hidden element has no layout at all. That is not an overflow — and reading it
|
|
// as one is exactly how a collapsed field would get stuck collapsed.
|
|
{name: "display:none reports nothing", client: 0, scroll: 0},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := vdom.NewRef()
|
|
n := vdom.Div(vdom.WithRef(r))
|
|
patchChildren(root, nil, one(n))
|
|
|
|
el, _ := r.Node().(js.Value)
|
|
el.Set("clientWidth", tc.client)
|
|
el.Set("scrollWidth", tc.scroll)
|
|
|
|
if got := OverflowsX(r); got != tc.overflows {
|
|
t.Errorf("OverflowsX(client=%v scroll=%v) = %v, want %v", tc.client, tc.scroll, got, tc.overflows)
|
|
}
|
|
patchChildren(root, one(n), nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
// An unmounted ref measures nothing rather than panicking — components call the host
|
|
// API unconditionally, including on the render before their element exists.
|
|
func TestOverflowsXOnUnmountedRef(t *testing.T) {
|
|
if OverflowsX(vdom.NewRef()) {
|
|
t.Error("an unmounted ref should not report an overflow")
|
|
}
|
|
}
|