~eliasnaur/gio

23406645704747400cd259659a7fc3d16a304c6a — Chris Waldon 11 months ago dee3cc4
widget: test and document seekPosition

This commit adds a test for the seekPosition helper, a function which can
be used to move a combinedPos forward through a body of text until it approaches
a position.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2 files changed, 88 insertions(+), 0 deletions(-)

M widget/editor.go
M widget/text_test.go
M widget/editor.go => widget/editor.go +2 -0
@@ 1093,6 1093,8 @@ func (e *Editor) closestPosition(pos combinedPos) combinedPos {

// seekPosition seeks to the position closest to needle, starting at start and returns true.
// If limit is non-zero, seekPosition stops seeks after limit runes and returns false.
// Start must have all fields valid, and needle must have at least one of runes, lineCol,
// or x+y valid. Start must be known to be before needle.
func seekPosition(lines []text.Line, alignment text.Alignment, width int, start, needle combinedPos, limit int) (combinedPos, bool) {
	count := 0
	// Advance until start is greater than or equal to needle.

M widget/text_test.go => widget/text_test.go +86 -0
@@ 1,6 1,7 @@
package widget

import (
	"math"
	"strconv"
	"testing"



@@ 440,3 441,88 @@ func TestPositionGreaterOrEqual(t *testing.T) {
		}
	}
}

func TestSeekPosition(t *testing.T) {
	fontSize := 16
	lineWidth := fontSize * 10
	// Be careful tuning the runeLimit here. This test case's complexity
	// is O(N^2) where N=runeLimit. It's easy to make this test take a stupid
	// amount of time accidentally.
	ltrText, rtlText := makeTestText(fontSize, lineWidth, 15)
	type testcase struct {
		name  string
		lines []text.Line
		align text.Alignment
		width int
	}
	for _, tc := range []testcase{
		{
			name:  "ltr",
			lines: ltrText,
			align: text.Start,
			width: lineWidth,
		},
		{
			name:  "rtl",
			lines: rtlText,
			align: text.Start,
			width: lineWidth,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			finalLineRunes := tc.lines[len(tc.lines)-1].Layout.Runes
			// Statically generate all valid positions.
			positions := make([]combinedPos, 1, finalLineRunes.Offset+finalLineRunes.Count+1)
			positions[0] = firstPos(tc.lines[0], tc.align, tc.width)
			for i := 1; ; i++ {
				pos, eof := incrementPosition(tc.lines, tc.align, tc.width, positions[i-1])
				positions = append(positions, pos)
				if eof {
					break
				}
			}
			for i, start := range positions {
				for k, needle := range positions {
					if k < i {
						continue
					}
					t.Run(tc.name+" "+strconv.Itoa(i)+"->"+strconv.Itoa(k), func(t *testing.T) {
						for kind := 0; kind < 3; kind++ {
							p2 := needle
							transform := ""
							switch kind {
							case 0: // only runes populated
								transform = "runes only"
								p2.lineCol = screenPos{}
								p2.x = 0
								p2.y = 0
							case 1: // only lineCol populated
								transform = "lineCol only"
								p2.runes = 0
								p2.x = 0
								p2.y = 0
							case 2: // only x and y populated
								transform = "x,y only"
								p2.runes = 0
								p2.lineCol = screenPos{}
							}
							for limit := 0; limit <= 10; limit += 5 {
								result, found := seekPosition(tc.lines, tc.align, tc.width, start, p2, limit)
								if (found && result != needle) || (!found && needle.runes-start.runes < limit) {
									t.Errorf("unexpected result seeking p[%d] -> p[%d](%s) (limit %d, found %v) = %#+v\np1: %#+v\np2:%#+v", i, k, transform, limit, found, result, start, p2)
								}
							}
						}
					})
				}
			}
			result, found := seekPosition(tc.lines, tc.align, tc.width, positions[0], combinedPos{runes: math.MaxInt}, 0)
			if !found {
				t.Errorf("reported hit limit on max int")
			}
			if expected := positions[len(positions)-1]; result != expected {
				t.Errorf("expected maximum int position to equal final position.\nexpected %#+v\nactual   %#+v", expected, result)
			}
		})
	}
}