Simulating input
Make the UI reachable from a test
A test needs to get at the widgets it taps and reads. The simplest way is to
build the UI in a function that returns them, and call that function from both
main and the test:
// counter holds the widgets a test needs to reach.
type counter struct {
name *widget.Entry
add *widget.Button
output *widget.Label
count int
}
func newCounter() *counter {
c := &counter{
name: widget.NewEntry(),
output: widget.NewLabel("Nobody has clicked yet"),
}
c.add = widget.NewButton("Click", func() {
c.count++
c.output.SetText(c.name.Text + " clicked " + strconv.Itoa(c.count) + " times")
})
return c
}
func (c *counter) content() core.CanvasObject {
return container.NewVBox(c.name, c.add, c.output)
}
func main() {
a := app.New()
w := a.NewWindow("Counter")
w.SetContent(newCounter().content())
bellui.ShowAndRun(w)
}Tap, type, check
Put the test next to the code, in a _test.go file of the same package:
func TestCounter_TypeAndTap(t *testing.T) {
test.NewTempApp(t) // an in-memory app, torn down when the test ends
c := newCounter()
w := test.NewTempWindow(t, c.content())
w.Resize(w.Content().MinSize())
test.Type(c.name, "Ada")
test.Tap(c.add)
test.Tap(c.add)
if got, want := c.output.Text, "Ada clicked 2 times"; got != want {
t.Errorf("label = %q, want %q", got, want)
}
}Run it with go test ./.... No window opens.
test.Typefocuses the widget and then sends the text one character at a time, as typing would.test.Taptaps the widget. It runs the widget’s tap handling, including moving focus, so the button’sOnTappedfunction runs.- After that, read the widgets’ fields, such as
Label.Text, and compare them with what you expect.
Resizing the window to its content’s minimum size, as above, gives every widget a real size and position. Do this when the test depends on layout, or when it taps a position on the canvas rather than a widget.
Other input
| Function | Simulates |
|---|---|
Tap, TapAt(obj, pos) | A primary click or tap on a widget, optionally at a position inside it. |
DoubleTap | A double click. |
TapSecondary, TapSecondaryAt | A right click. |
LongPress, LongPressAt | A long press on a touch screen. |
TapCanvas(c, pos) | A tap at an absolute position on a canvas, delivered to whatever is there. |
Type(obj, text) | Typing into a focusable widget. |
TypeOnCanvas(c, text) | Typing to the canvas’s own key handler. |
Drag(c, pos, dx, dy) | A drag that starts at pos and moves by dx, dy. DragSteps does it in several steps. |
Scroll(c, pos, dx, dy) | A scroll wheel or trackpad scroll at a position. |
MoveMouse(c, pos) | Moving the pointer, so hover effects run. |
FocusNext(c), FocusPrevious(c) | Moving focus to the next or previous widget, as Tab and Shift-Tab do. |
Compose, ComposeCommit, ComposeCancel | Text entry through an input method, as used for Chinese, Japanese and Korean. |
All of these are in the test
package. The canvas c is usually w.Canvas() of your test window.
Assertion helpers
The package has a few assertions for common checks. They report a failure
through t, and most also return false, so you can stop the test early if
you need to.
func TestCounter_Layout(t *testing.T) {
test.NewTempApp(t)
c := newCounter()
w := test.NewTempWindow(t, c.content())
w.Resize(w.Content().MinSize())
test.AssertVisible(t, c.add)
test.AssertEnabled(t, c.add)
test.AssertMinSizeAtLeast(t, c.add, 40, 20)
c.add.Disable()
test.AssertDisabled(t, c.add)
}| Function | Checks |
|---|---|
AssertVisible, AssertHidden | The object’s Visible() state. |
AssertEnabled, AssertDisabled | A widget that can be disabled. |
AssertWidgetSize(t, obj, w, h) | The current size, to within 1.5 units. |
AssertWidgetPosition(t, obj, x, y) | The current position, to within 1.5 units. |
AssertMinSizeAtLeast(t, obj, w, h) | The minimum size is at least this big. |
AssertFitsWithin(t, obj, w, h) | The current size is no bigger than this. |
AssertCanvasTappableAt(t, c, pos) | Something tappable is at this position. |
AssertNotificationSent(t, n, f) | Running f sends the notification n. |
Themes in tests
The test app starts with a test theme whose colours are deliberately plain.
To check how your UI looks with another theme,
test.ApplyTheme(t, th)
switches the theme and switches it back when the test ends.
The complete test
package main
import (
"testing"
"github.com/belluikit/bellui/test"
)
func TestCounter_TypeAndTap(t *testing.T) {
test.NewTempApp(t) // an in-memory app, torn down when the test ends
c := newCounter()
w := test.NewTempWindow(t, c.content())
w.Resize(w.Content().MinSize())
test.Type(c.name, "Ada")
test.Tap(c.add)
test.Tap(c.add)
if got, want := c.output.Text, "Ada clicked 2 times"; got != want {
t.Errorf("label = %q, want %q", got, want)
}
}
func TestCounter_Layout(t *testing.T) {
test.NewTempApp(t)
c := newCounter()
w := test.NewTempWindow(t, c.content())
w.Resize(w.Content().MinSize())
test.AssertVisible(t, c.add)
test.AssertEnabled(t, c.add)
test.AssertMinSizeAtLeast(t, c.add, 40, 20)
c.add.Disable()
test.AssertDisabled(t, c.add)
}