1package ui
2
3import (
4 "github.com/bnema/nefergui/internal/css"
5 "github.com/bnema/nefergui/internal/edit"
6 "github.com/bnema/nefergui/internal/keyboard"
7 "testing"
8)
9
10type clip struct{ data []byte }
11
12func (c *clip) ReadText(limit int) ([]byte, error) { return c.data, nil }
13func (c *clip) WriteText(v []byte) error { c.data = append([]byte(nil), v...); return nil }
14func TestRadioSliderAndEditingIdentity(t *testing.T) {
15 r := newRuntime()
16 r.styles = css.Compile(css.UA(), css.Parse(`input,radio,slider {height:20px}`))
17 var choice string
18 v := 3.0
19 name := "e\u0301"
20 order := []string{"a", "b"}
21 view := func(f *Frame) {
22 root := f.Root()
23 for _, key := range order {
24 root.Input(key, &name, Key(key))
25 }
26 root.Radio("One", "one", &choice, Key("r1"))
27 root.Radio("Two", "two", &choice, Key("r2"))
28 root.Slider("level", &v, 0, 10, 1, Key("s"))
29 }
30 r.Build(view)
31 r.Queue(inputEvent{Target: r.Target(3), Kind: "activate"})
32 r.Build(view)
33 if choice != "two" {
34 t.Fatal(choice)
35 }
36 r.route(key("Tab"))
37 r.Build(view)
38 for !r.state.focus.same(r.Target(4)) {
39 r.route(key("Tab"))
40 r.Build(view)
41 if r.state.focus.same(r.Target(0)) {
42 t.Fatalf("slider skipped: %v", func() []string {
43 var ids []string
44 for _, e := range r.focusOrder() {
45 ids = append(ids, idKey(e.identity))
46 }
47 return ids
48 }())
49 }
50 }
51 if !r.state.focus.same(r.Target(4)) {
52 t.Fatalf("slider focus: focus=%s order=%v", idKey(r.state.focus), func() []string {
53 var ids []string
54 for _, e := range r.focusOrder() {
55 ids = append(ids, idKey(e.identity))
56 }
57 return ids
58 }())
59 }
60 r.route(key("Right"))
61 r.Build(view)
62 if v != 4 {
63 t.Fatal(v)
64 }
65 r.Queue(inputEvent{Target: r.Target(0), Kind: "edit-key", Text: "backspace"})
66 r.Build(view)
67 if name != "" {
68 t.Fatal("grapheme delete", name)
69 }
70 order = []string{"b", "a"}
71 r.Redraw()
72 r.Build(view)
73 if r.edits[idKey(r.Target(0))] == r.edits[idKey(r.Target(1))] {
74 t.Fatal("state aliased")
75 }
76}
77func TestDisabledEditorRejectsFocusAndText(t *testing.T) {
78 r := newRuntime()
79 r.styles = css.Compile(css.UA(), css.Parse(`input {height:20px}`))
80 value := "keep"
81 view := func(f *Frame) { f.Root().Input("name", &value, Key("name"), Disabled(true)) }
82 r.Build(view)
83 r.route(key("Tab"))
84 r.Build(view)
85 if r.state.focus != nil {
86 t.Fatalf("disabled input focused: %s", idKey(r.state.focus))
87 }
88 r.route(platformInput{Kind: "key", Key: keyboard.Key{Name: "q", Text: "x", Pressed: true}})
89 r.Build(view)
90 if value != "keep" {
91 t.Fatalf("disabled input edited: %q", value)
92 }
93}
94
95func TestEditFocusIMEAndClipboard(t *testing.T) {
96 r := newRuntime()
97 r.styles = css.Compile(css.UA(), css.Parse(`input {height:20px}`))
98 var value string
99 view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }
100 r.Build(view)
101 r.clipboard = &clip{data: []byte("paste")}
102 r.route(key("Tab"))
103 r.Build(view)
104 r.route(platformInput{Kind: "key", Key: keyboard.Key{Name: "q", Text: "x", Pressed: true}})
105 r.Build(view)
106 if value != "x" || r.committed.children[0].text != "•" {
107 t.Fatalf("value=%q display=%q focus=%s", value, r.committed.children[0].text, idKey(r.state.focus))
108 }
109 r.route(platformInput{Kind: "ime-done", IME: edit.IMEBatch{Preedit: "候補"}})
110 r.Build(view)
111 if value != "x" {
112 t.Fatal(value)
113 }
114 r.route(platformInput{Kind: "focus-out"})
115 r.Build(view)
116 if r.edits[idKey(r.Target(0))].Preedit != "" {
117 t.Fatal("preedit survives blur")
118 }
119}