github.com/utopiagio/gio@v0.0.8/app/ime_test.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 //go:build go1.18 4 // +build go1.18 5 6 package app 7 8 import ( 9 "testing" 10 "unicode/utf8" 11 12 "github.com/utopiagio/gio/font" 13 "github.com/utopiagio/gio/font/gofont" 14 "github.com/utopiagio/gio/io/input" 15 "github.com/utopiagio/gio/io/key" 16 "github.com/utopiagio/gio/layout" 17 "github.com/utopiagio/gio/op" 18 "github.com/utopiagio/gio/text" 19 "github.com/utopiagio/gio/unit" 20 "github.com/utopiagio/gio/widget" 21 22 ) 23 24 func FuzzIME(f *testing.F) { 25 runes := []rune("Hello, 世界! 🤬 علي،الحسنب北查爾斯頓工廠的安全漏洞已") 26 f.Add([]byte("20\x0010")) 27 f.Add([]byte("80000")) 28 f.Add([]byte("2008\"80\r00")) 29 f.Add([]byte("20007900002\x02000")) 30 f.Add([]byte("20007800002\x02000")) 31 f.Add([]byte("200A02000990\x19002\x17\x0200")) 32 f.Fuzz(func(t *testing.T, cmds []byte) { 33 cache := text.NewShaper(text.WithCollection(gofont.Collection())) 34 e := new(widget.Editor) 35 36 var r input.Router 37 gtx := layout.Context{Ops: new(op.Ops), Source: r.Source()} 38 gtx.Execute(key.FocusCmd{Tag: e}) 39 // Layout once to register focus. 40 e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{}) 41 r.Frame(gtx.Ops) 42 43 var state editorState 44 const ( 45 cmdReplace = iota 46 cmdSelect 47 cmdSnip 48 maxCmd 49 ) 50 const cmdLen = 5 51 for len(cmds) >= cmdLen { 52 n := e.Len() 53 rng := key.Range{ 54 Start: int(cmds[1]) % (n + 1), 55 End: int(cmds[2]) % (n + 1), 56 } 57 switch cmds[0] % cmdLen { 58 case cmdReplace: 59 rstart := int(cmds[3]) % len(runes) 60 rend := int(cmds[4]) % len(runes) 61 if rstart > rend { 62 rstart, rend = rend, rstart 63 } 64 replacement := string(runes[rstart:rend]) 65 state.Replace(rng, replacement) 66 r.Queue(key.EditEvent{Range: rng, Text: replacement}) 67 r.Queue(key.SnippetEvent(state.Snippet.Range)) 68 case cmdSelect: 69 r.Queue(key.SelectionEvent(rng)) 70 runes := []rune(e.Text()) 71 if rng.Start < 0 { 72 rng.Start = 0 73 } 74 if rng.End < 0 { 75 rng.End = 0 76 } 77 if rng.Start > len(runes) { 78 rng.Start = len(runes) 79 } 80 if rng.End > len(runes) { 81 rng.End = len(runes) 82 } 83 state.Selection.Range = rng 84 case cmdSnip: 85 r.Queue(key.SnippetEvent(rng)) 86 runes := []rune(e.Text()) 87 if rng.Start > rng.End { 88 rng.Start, rng.End = rng.End, rng.Start 89 } 90 if rng.Start < 0 { 91 rng.Start = 0 92 } 93 if rng.End < 0 { 94 rng.End = 0 95 } 96 if rng.Start > len(runes) { 97 rng.Start = len(runes) 98 } 99 if rng.End > len(runes) { 100 rng.End = len(runes) 101 } 102 state.Snippet = key.Snippet{ 103 Range: rng, 104 Text: string(runes[rng.Start:rng.End]), 105 } 106 } 107 cmds = cmds[cmdLen:] 108 e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{}) 109 r.Frame(gtx.Ops) 110 newState := r.EditorState() 111 // We don't track caret position. 112 state.Selection.Caret = newState.Selection.Caret 113 // Expanded snippets are ok. 114 their, our := newState.Snippet, state.EditorState.Snippet 115 beforeLen := 0 116 for before := our.Start - their.Start; before > 0; before-- { 117 _, n := utf8.DecodeRuneInString(their.Text[beforeLen:]) 118 beforeLen += n 119 } 120 afterLen := 0 121 for after := their.End - our.End; after > 0; after-- { 122 _, n := utf8.DecodeLastRuneInString(their.Text[:len(their.Text)-afterLen]) 123 afterLen += n 124 } 125 if beforeLen > 0 { 126 our.Text = their.Text[:beforeLen] + our.Text 127 our.Start = their.Start 128 } 129 if afterLen > 0 { 130 our.Text = our.Text + their.Text[len(their.Text)-afterLen:] 131 our.End = their.End 132 } 133 state.EditorState.Snippet = our 134 if newState != state.EditorState { 135 t.Errorf("IME state: %+v\neditor state: %+v", state.EditorState, newState) 136 } 137 } 138 }) 139 } 140 141 func TestEditorIndices(t *testing.T) { 142 var s editorState 143 const str = "Hello, 😀" 144 s.Snippet = key.Snippet{ 145 Text: str, 146 Range: key.Range{ 147 Start: 10, 148 End: utf8.RuneCountInString(str), 149 }, 150 } 151 utf16Indices := [...]struct { 152 Runes, UTF16 int 153 }{ 154 {0, 0}, {10, 10}, {17, 17}, {18, 19}, {30, 31}, 155 } 156 for _, p := range utf16Indices { 157 if want, got := p.UTF16, s.UTF16Index(p.Runes); want != got { 158 t.Errorf("UTF16Index(%d) = %d, wanted %d", p.Runes, got, want) 159 } 160 if want, got := p.Runes, s.RunesIndex(p.UTF16); want != got { 161 t.Errorf("RunesIndex(%d) = %d, wanted %d", p.UTF16, got, want) 162 } 163 } 164 }