Fenrir's emoji ported to cthulhu.
This commit is contained in:
1 parent
3e2429b8a5
commit
01f50c17e8
24 files changed
+1505
-9
No files matched your search
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
"git.stormux.org/storm/cthulhu/internal/input"
|
||||
"git.stormux.org/storm/cthulhu/internal/platform/xorg"
|
||||
)
|
||||
|
||||
func TestNativeBrowserEmoji(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_BROWSER") != "1" {
|
||||
t.Skip("requires private browser fixture")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
adapter := atspi.New()
|
||||
keys := xorg.NewCommands([]string{"Insert"}, input.DesktopKeyboardLayout)
|
||||
for _, service := range []interface {
|
||||
Start(context.Context) error
|
||||
Stop(context.Context) error
|
||||
}{adapter, keys} {
|
||||
if err := service.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer service.Stop(context.Background())
|
||||
}
|
||||
output := &ocrTestOutput{speech: make(chan speechRequest, 256)}
|
||||
ui := &internalUI{keys: keys, emojiSource: adapter, emojiCopier: xclipOCRCopier{}, speech: output, gate: newFocusGate(keys), events: make(chan input.CommandEvent, 32), done: make(chan struct{})}
|
||||
var focus atomic.Pointer[atspi.Snapshot]
|
||||
go ui.run(ctx)
|
||||
relayDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(relayDone)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case event := <-keys.Commands():
|
||||
ui.Handle(event)
|
||||
case event := <-adapter.Events():
|
||||
if isFocusGainEvent(event) {
|
||||
focus.Store(&event.Source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() { cancel(); <-ui.done; <-relayDone }()
|
||||
key := func(names ...string) {
|
||||
t.Helper()
|
||||
if data, err := exec.CommandContext(ctx, "xdotool", append([]string{"key"}, names...)...).CombinedOutput(); err != nil {
|
||||
t.Fatalf("keys: %v %s", err, data)
|
||||
}
|
||||
}
|
||||
waitSpeech := func(prefix string) {
|
||||
t.Helper()
|
||||
for {
|
||||
select {
|
||||
case message := <-output.speech:
|
||||
t.Logf("UI: %s", message.Text)
|
||||
if strings.HasPrefix(message.Text, prefix) {
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("missing %q", prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"Emoji input", "Emoji textarea", "Emoji editor"} {
|
||||
for attempt := 0; attempt < 12; attempt++ {
|
||||
key("Tab")
|
||||
deadline := time.Now().Add(200 * time.Millisecond)
|
||||
for time.Now().Before(deadline) {
|
||||
if current := focus.Load(); current != nil && current.Name == name {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
if current := focus.Load(); current != nil && current.Name == name {
|
||||
break
|
||||
}
|
||||
}
|
||||
current := focus.Load()
|
||||
if current == nil || current.Name != name {
|
||||
t.Fatalf("field %s not focused: %+v", name, current)
|
||||
}
|
||||
key("ctrl+a", "Insert+ctrl+e")
|
||||
waitSpeech("Emoji Menu. All tab")
|
||||
key("Tab", "a", "s", "t", "r", "o", "n", "a", "u", "t")
|
||||
key("Return")
|
||||
waitSpeech("Inserted Astronaut")
|
||||
|
||||
// Read back from the start, independently of the insertion's caret placement.
|
||||
key("ctrl+Home")
|
||||
var text string
|
||||
for {
|
||||
content, err := adapter.ReadFocusedText(ctx, current.ID())
|
||||
text = content.Text
|
||||
want := "🧑🚀"
|
||||
if err == nil && text == want {
|
||||
break
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
t.Fatalf("%s replacement: %q %v", name, text, err)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user