~eliasnaur/gio

ffec83a001be70c169726b56cc293a839366c51f — Elias Naur 3 years ago dcbbcbb
widget: add Editor tests

Signed-off-by: Elias Naur <mail@eliasnaur.com>
1 files changed, 139 insertions(+), 0 deletions(-)

A widget/editor_test.go
A widget/editor_test.go => widget/editor_test.go +139 -0
@@ 0,0 1,139 @@
// SPDX-License-Identifier: Unlicense OR MIT

package widget

import (
	"fmt"
	"image"
	"math/rand"
	"reflect"
	"testing"
	"testing/quick"

	"gioui.org/f32"
	"gioui.org/font/gofont"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/text"
	"gioui.org/unit"
)

func TestEditor(t *testing.T) {
	e := new(Editor)
	gtx := layout.Context{
		Ops:         new(op.Ops),
		Constraints: layout.Exact(image.Pt(100, 100)),
	}
	cache := text.NewCache(gofont.Collection())
	fontSize := unit.Px(10)
	font := text.Font{}

	e.SetText("æbc\naøå•")
	e.Layout(gtx, cache, font, fontSize)
	assertCaret(t, e, 0, 0, 0)
	e.moveEnd()
	assertCaret(t, e, 0, 3, len("æbc"))
	e.Move(+1)
	assertCaret(t, e, 1, 0, len("æbc\n"))
	e.Move(-1)
	assertCaret(t, e, 0, 3, len("æbc"))
	e.moveLines(+1)
	assertCaret(t, e, 1, 3, len("æbc\naøå"))
	e.moveEnd()
	assertCaret(t, e, 1, 4, len("æbc\naøå•"))
	e.Move(+1)
	assertCaret(t, e, 1, 4, len("æbc\naøå•"))
}

// assertCaret asserts that the editor caret is at a particular line
// and column, and that the byte position matches as well.
func assertCaret(t *testing.T, e *Editor, line, col, bytes int) {
	t.Helper()
	gotLine, gotCol := e.CaretPos()
	if gotLine != line || gotCol != col {
		t.Errorf("caret at (%d, %d), expected (%d, %d)", gotLine, gotCol, line, col)
	}
	if bytes != e.rr.caret {
		t.Errorf("caret at buffer position %d, expected %d", e.rr.caret, bytes)
	}
}

type editMutation int

const (
	setText editMutation = iota
	moveRune
	moveLine
	movePage
	moveStart
	moveEnd
	moveCoord
	moveLast // Mark end; never generated.
)

func TestEditorCaretConsistency(t *testing.T) {
	gtx := layout.Context{
		Ops:         new(op.Ops),
		Constraints: layout.Exact(image.Pt(100, 100)),
	}
	cache := text.NewCache(gofont.Collection())
	fontSize := unit.Px(10)
	font := text.Font{}
	for _, a := range []text.Alignment{text.Start, text.Middle, text.End} {
		e := &Editor{
			Alignment: a,
		}
		e.Layout(gtx, cache, font, fontSize)

		consistent := func() error {
			t.Helper()
			gotLine, gotCol := e.CaretPos()
			gotCoords := e.CaretCoords()
			wantLine, wantCol, wantX, wantY := e.layoutCaret()
			wantCoords := f32.Pt(float32(wantX)/64, float32(wantY))
			if wantLine == gotLine && wantCol == gotCol && gotCoords == wantCoords {
				return nil
			}
			return fmt.Errorf("caret (%d,%d) pos %s, want (%d,%d) pos %s", gotLine, gotCol, gotCoords, wantLine, wantCol, wantCoords)
		}
		if err := consistent(); err != nil {
			t.Errorf("initial editor inconsistency (alignment %s): %v", a, err)
		}

		move := func(mutation editMutation, str string, distance int8, x, y uint16) bool {
			switch mutation {
			case setText:
				e.SetText(str)
				e.Layout(gtx, cache, font, fontSize)
			case moveRune:
				e.Move(int(distance))
			case moveLine:
				e.moveLines(int(distance))
			case movePage:
				e.movePages(int(distance))
			case moveStart:
				e.moveStart()
			case moveEnd:
				e.moveEnd()
			case moveCoord:
				e.moveCoord(image.Pt(int(x), int(y)))
			default:
				return false
			}
			if err := consistent(); err != nil {
				t.Error(err)
				return false
			}
			return true
		}
		if err := quick.Check(move, nil); err != nil {
			t.Errorf("editor inconsistency (alignment %s): %v", a, err)
		}
	}
}

// Generate generates a value of itself, for testing/quick.
func (editMutation) Generate(rand *rand.Rand, size int) reflect.Value {
	t := editMutation(rand.Intn(int(moveLast)))
	return reflect.ValueOf(t)
}