Add fonts, autotable, autotable examples
This commit is contained in:
198
go/webui/position_test.go
Normal file
198
go/webui/position_test.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"kjol/wasmruntime"
|
||||
)
|
||||
|
||||
// A 1024x768 viewport with a 100x40 trigger in the middle, and a 200x100 panel.
|
||||
var (
|
||||
vp = wasmruntime.Size{Width: 1024, Height: 768}
|
||||
trigger = wasmruntime.Rect{X: 400, Y: 300, Width: 100, Height: 40}
|
||||
panel = wasmruntime.Rect{X: 0, Y: 0, Width: 200, Height: 100}
|
||||
)
|
||||
|
||||
func opts(placement string) PositionOptions {
|
||||
o := DefaultPositionOptions()
|
||||
o.Placement = placement
|
||||
return o
|
||||
}
|
||||
|
||||
func TestPlacements(t *testing.T) {
|
||||
// trigger: left=400 right=500 centerX=450, top=300 bottom=340 centerY=320
|
||||
// panel: 200x100. offset 4.
|
||||
cases := []struct {
|
||||
placement string
|
||||
top, left float64
|
||||
}{
|
||||
{PlacementBottomStart, 344, 400}, // below, left edges aligned
|
||||
{PlacementBottom, 344, 350}, // below, centred: 450 - 100
|
||||
{PlacementBottomEnd, 344, 300}, // below, right edges aligned: 500 - 200
|
||||
{PlacementTopStart, 196, 400}, // above: 300 - 100 - 4
|
||||
{PlacementTop, 196, 350},
|
||||
{PlacementTopEnd, 196, 300},
|
||||
{PlacementRightStart, 300, 504}, // right of: 500 + 4
|
||||
{PlacementRight, 270, 504}, // right, centred: 320 - 50
|
||||
{PlacementRightEnd, 240, 504}, // right, bottom-aligned: 340 - 100
|
||||
{PlacementLeftStart, 300, 196}, // left of: 400 - 200 - 4
|
||||
{PlacementLeft, 270, 196},
|
||||
{PlacementLeftEnd, 240, 196},
|
||||
}
|
||||
for _, c := range cases {
|
||||
got := ComputePosition(trigger, panel, vp, opts(c.placement))
|
||||
if got.Top != c.top || got.Left != c.left {
|
||||
t.Errorf("%s: got top=%v left=%v, want top=%v left=%v", c.placement, got.Top, got.Left, c.top, c.left)
|
||||
}
|
||||
if got.Placement != c.placement {
|
||||
t.Errorf("%s: resolved placement changed to %s with room to spare", c.placement, got.Placement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlipWhenItDoesNotFit(t *testing.T) {
|
||||
// Trigger near the bottom: a bottom-placed panel would run off, so flip up.
|
||||
low := wasmruntime.Rect{X: 400, Y: 700, Width: 100, Height: 40}
|
||||
got := ComputePosition(low, panel, vp, opts(PlacementBottomStart))
|
||||
if got.Placement != PlacementTopStart {
|
||||
t.Fatalf("placement = %s, want top-start (should have flipped)", got.Placement)
|
||||
}
|
||||
if got.Top != 596 { // 700 - 100 - 4
|
||||
t.Errorf("top = %v, want 596", got.Top)
|
||||
}
|
||||
|
||||
// The alignment suffix must survive the flip.
|
||||
if p := ComputePosition(low, panel, vp, opts(PlacementBottomEnd)).Placement; p != PlacementTopEnd {
|
||||
t.Errorf("bottom-end flipped to %s, want top-end", p)
|
||||
}
|
||||
|
||||
// Trigger near the right edge: right-placed flips to left.
|
||||
right := wasmruntime.Rect{X: 950, Y: 300, Width: 60, Height: 40}
|
||||
if p := ComputePosition(right, panel, vp, opts(PlacementRightStart)).Placement; p != PlacementLeftStart {
|
||||
t.Errorf("placement = %s, want left-start", p)
|
||||
}
|
||||
}
|
||||
|
||||
// If neither side fits, keep the requested one rather than flipping into an
|
||||
// equally bad spot.
|
||||
func TestNoFlipWhenNeitherSideFits(t *testing.T) {
|
||||
tall := wasmruntime.Rect{X: 0, Y: 0, Width: 200, Height: 700}
|
||||
mid := wasmruntime.Rect{X: 400, Y: 350, Width: 100, Height: 40}
|
||||
got := ComputePosition(mid, tall, vp, opts(PlacementBottomStart))
|
||||
if got.Placement != PlacementBottomStart {
|
||||
t.Errorf("placement = %s, want bottom-start (no better side available)", got.Placement)
|
||||
}
|
||||
}
|
||||
|
||||
// The bug we are deliberately fixing: the TSX clamped BOTH axes, so a panel that
|
||||
// did not fit below got pushed up over the trigger it belonged to. Shift must
|
||||
// leave the main axis alone.
|
||||
func TestShiftDoesNotCoverTheTrigger(t *testing.T) {
|
||||
tall := wasmruntime.Rect{X: 0, Y: 0, Width: 200, Height: 700}
|
||||
low := wasmruntime.Rect{X: 400, Y: 700, Width: 100, Height: 40}
|
||||
got := ComputePosition(low, tall, vp, opts(PlacementBottomStart))
|
||||
|
||||
if got.Top < low.Bottom() {
|
||||
t.Errorf("panel top %v is above the trigger's bottom %v — it is covering its own trigger", got.Top, low.Bottom())
|
||||
}
|
||||
}
|
||||
|
||||
func TestShiftClampsTheCrossAxis(t *testing.T) {
|
||||
// Trigger hard against the right edge; a bottom-start panel would overflow.
|
||||
edge := wasmruntime.Rect{X: 980, Y: 300, Width: 40, Height: 40}
|
||||
got := ComputePosition(edge, panel, vp, opts(PlacementBottomStart))
|
||||
|
||||
wantLeft := vp.Width - panel.Width - 8 // 1024 - 200 - padding
|
||||
if got.Left != wantLeft {
|
||||
t.Errorf("left = %v, want %v (clamped to the viewport)", got.Left, wantLeft)
|
||||
}
|
||||
|
||||
// And against the left edge.
|
||||
edge = wasmruntime.Rect{X: 2, Y: 300, Width: 40, Height: 40}
|
||||
if got := ComputePosition(edge, panel, vp, opts(PlacementBottomEnd)); got.Left != 8 {
|
||||
t.Errorf("left = %v, want 8 (clamped to the padding)", got.Left)
|
||||
}
|
||||
}
|
||||
|
||||
// The second flaw we fix: after a shift, the arrow must still point at the trigger.
|
||||
func TestArrowTracksTheTriggerAfterShift(t *testing.T) {
|
||||
o := opts(PlacementBottom)
|
||||
o.ArrowSize = 8
|
||||
|
||||
// Unshifted, centred: the arrow sits at the panel's middle.
|
||||
got := ComputePosition(trigger, panel, vp, o)
|
||||
if got.ArrowOffset != 100 { // panel is 200 wide
|
||||
t.Errorf("centred arrow offset = %v, want 100", got.ArrowOffset)
|
||||
}
|
||||
|
||||
// Hard against the right edge, the panel gets shifted left but the trigger did
|
||||
// not move — so the arrow must slide right to keep pointing at it.
|
||||
edge := wasmruntime.Rect{X: 980, Y: 300, Width: 40, Height: 40} // centre x = 1000
|
||||
got = ComputePosition(edge, panel, vp, o)
|
||||
wantOffset := edge.CenterX() - got.Left // 1000 - 816 = 184
|
||||
if got.ArrowOffset != wantOffset {
|
||||
t.Errorf("arrow offset = %v, want %v (pointing at the trigger centre)", got.ArrowOffset, wantOffset)
|
||||
}
|
||||
if got.ArrowOffset <= 100 {
|
||||
t.Error("arrow did not move toward the trigger after the panel was shifted")
|
||||
}
|
||||
}
|
||||
|
||||
// The arrow must never leave the panel, however far the trigger is.
|
||||
func TestArrowIsClampedToThePanel(t *testing.T) {
|
||||
o := opts(PlacementBottomStart)
|
||||
o.ArrowSize = 8
|
||||
o.ArrowPadding = 4
|
||||
|
||||
far := wasmruntime.Rect{X: 1000, Y: 300, Width: 20, Height: 40}
|
||||
got := ComputePosition(far, panel, vp, o)
|
||||
|
||||
lo, hi := 8.0, panel.Width-8 // padding + half the arrow, at both ends
|
||||
if got.ArrowOffset < lo || got.ArrowOffset > hi {
|
||||
t.Errorf("arrow offset %v escaped the panel [%v, %v]", got.ArrowOffset, lo, hi)
|
||||
}
|
||||
}
|
||||
|
||||
// The third fix: tell the caller how much room there is, so a long menu can scroll
|
||||
// instead of running off the screen.
|
||||
func TestAvailableSpace(t *testing.T) {
|
||||
got := ComputePosition(trigger, panel, vp, opts(PlacementBottomStart))
|
||||
want := vp.Height - trigger.Bottom() - 4 - 8 // viewport - trigger bottom - offset - padding
|
||||
if got.MaxHeight != want {
|
||||
t.Errorf("MaxHeight = %v, want %v", got.MaxHeight, want)
|
||||
}
|
||||
if got.MaxWidth != 0 {
|
||||
t.Errorf("MaxWidth = %v, want 0 (unconstrained on the cross axis)", got.MaxWidth)
|
||||
}
|
||||
|
||||
got = ComputePosition(trigger, panel, vp, opts(PlacementRightStart))
|
||||
if got.MaxWidth != vp.Width-trigger.Right()-12 {
|
||||
t.Errorf("MaxWidth = %v, want %v", got.MaxWidth, vp.Width-trigger.Right()-12)
|
||||
}
|
||||
if got.MaxHeight != 0 {
|
||||
t.Errorf("MaxHeight = %v, want 0", got.MaxHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOppositeSide(t *testing.T) {
|
||||
for placement, want := range map[string]string{
|
||||
PlacementTopStart: "bottom",
|
||||
PlacementBottom: "top",
|
||||
PlacementLeftEnd: "right",
|
||||
PlacementRight: "left",
|
||||
} {
|
||||
if got := OppositeSide(placement); got != want {
|
||||
t.Errorf("OppositeSide(%s) = %s, want %s", placement, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownPlacementFallsBackToBottom(t *testing.T) {
|
||||
got := ComputePosition(trigger, panel, vp, opts("nonsense"))
|
||||
if got.Placement != "bottom-center" && got.Placement != "bottom" {
|
||||
t.Errorf("placement = %s, want a bottom variant", got.Placement)
|
||||
}
|
||||
if got.Top != 344 {
|
||||
t.Errorf("top = %v, want 344 (positioned below)", got.Top)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user