1package ui
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/nefergui/internal/css"
8 "github.com/bnema/nefergui/internal/text"
9)
10
11func TestPointerTextSelection(t *testing.T) {
12 catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
13 if err != nil {
14 t.Fatal(err)
15 }
16 r := newRuntime()
17 r.setTextEngine(text.NewEngine(catalog))
18 r.styles = css.Compile(css.UA(), css.Parse(`input, textarea { width: 240px; height: 32px; font-size: 16px; }`))
19 value := "aé word"
20 view := func(f *Frame) { f.Root().Input("edit", &value, Key("edit")) }
21 r.Build(view)
22 n := r.output.Tree.Children[0]
23 id := n.ID
24 bs := textBoundaries(value, n, r.output.Display)
25 if len(bs) == 0 || len(bs[0]) < 3 {
26 t.Fatalf("no text clusters: %+v", bs)
27 }
28 x := func(b int) float64 {
29 for _, v := range bs[0] {
30 if v.byteOffset == b {
31 return v.x
32 }
33 }
34 t.Fatalf("no boundary %d: %+v", b, bs)
35 return 0
36 }
37 y := n.Content.Y + 1
38 at := time.Unix(20, 0)
39 press := func(px float64, when time.Time, shift bool) {
40 r.route(platformInput{Kind: "press", Button: pointerPrimary, X: px, Y: y, Time: when, Shift: shift})
41 r.Build(view)
42 }
43 release := func() { r.route(platformInput{Kind: "release", Button: pointerPrimary, X: 0, Y: y}); r.Build(view) }
44 press(x(3), at, false)
45 if got := r.edits[id].Cursor; got != 3 {
46 t.Fatalf("UTF-8 boundary got %d want 3: %+v content=%+v", got, bs, n.Content)
47 }
48 r.route(platformInput{Kind: "motion", X: x(0), Y: y})
49 r.Build(view)
50 if a, b := r.edits[id].Range(); a != 0 || b != 3 {
51 t.Fatalf("drag selection %d:%d", a, b)
52 }
53 release()
54 press(x(8)-0.01, at.Add(time.Second), true)
55 if a, b := r.edits[id].Range(); a != 3 || b != 8 {
56 t.Fatalf("shift selection %d:%d", a, b)
57 }
58 release()
59 press(x(5), at.Add(2*time.Second), false)
60 release()
61 press(x(5), at.Add(2*time.Second+300*time.Millisecond), false)
62 if a, b := r.edits[id].Range(); a != 4 || b != 8 {
63 t.Fatalf("double word selection %d:%d", a, b)
64 }
65 release()
66 // A word start used to include the previous segment too.
67 press(x(4), at.Add(3*time.Second), false)
68 release()
69 press(x(4), at.Add(3*time.Second+200*time.Millisecond), false)
70 if a, b := r.edits[id].Range(); a != 4 || b != 8 {
71 t.Fatalf("boundary word selection %d:%d", a, b)
72 }
73}