Files
cthulhu/internal/app/emoji_menu_test.go

137 lines
4.0 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"errors"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/input"
"git.stormux.org/storm/cthulhu/internal/interaction"
)
type emojiTestSource struct {
keys *uiTestKeys
calls chan string
err error
}
func (source *emojiTestSource) TypeText(_ context.Context, text string) error {
if source.keys.InternalUISession() != 0 {
source.calls <- "capture retained"
} else {
source.calls <- text
}
return source.err
}
func TestEmojiHostCancelCheckboxMemoryAndClipboard(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
keys := &uiTestKeys{}
source := &emojiTestSource{keys: keys, calls: make(chan string, 4)}
copier := &ocrTestCopier{copied: make(chan string, 4)}
output := &ocrTestOutput{speech: make(chan speechRequest, 64)}
ui := &internalUI{keys: keys, emojiSource: source, emojiCopier: copier, speech: output, gate: &focusGate{}, events: make(chan input.CommandEvent, 32), done: make(chan struct{})}
go ui.run(ctx)
defer func() { cancel(); <-ui.done }()
wait := func(prefix string) {
t.Helper()
for {
select {
case message := <-output.speech:
if len(message.Text) >= len(prefix) && message.Text[:len(prefix)] == prefix {
return
}
case <-ctx.Done():
t.Fatalf("missing %q", prefix)
}
}
}
open := func() {
t.Helper()
ui.Handle(input.CommandEvent{Command: input.EmojiMenu})
wait("Emoji Menu. All tab")
}
key := func(symbol uint32, text string) {
ui.Handle(input.CommandEvent{Command: input.InternalUIKey, UISession: 1, Chord: input.CommandChord{Symbol: symbol}, Text: text})
}
open()
key(0xff09, "")
key(0, "goat")
key(0xff0d, "")
wait("Inserted Goat")
if got := <-source.calls; got != "🐐" {
t.Fatal(got)
}
select {
case got := <-copier.copied:
t.Fatalf("default copied: %q", got)
default:
}
// Toggle copying on, cancel, and verify the next invocation retains the choice.
open()
key(0xff09, "")
key(0xff09, "")
key(0, " ")
key(0xff1b, "")
for keys.InternalUISession() != 0 {
if ctx.Err() != nil {
t.Fatal("cancel not processed")
}
time.Sleep(time.Millisecond)
}
open()
key(0xff09, "")
key(0, "astronaut")
key(0xff0d, "")
wait("Copied Astronaut to clipboard")
if got := <-copier.copied; got != "🧑‍🚀" {
t.Fatal(got)
}
select {
case got := <-source.calls:
t.Fatalf("copy mode typed: %q", got)
default:
}
}
func TestEmojiTypingFailure(t *testing.T) {
source := &emojiTestSource{keys: &uiTestKeys{}, calls: make(chan string, 1), err: errors.New("unsupported")}
output := &ocrTestOutput{speech: make(chan speechRequest, 1)}
ui := &internalUI{keys: source.keys, emojiSource: source, speech: output, gate: &focusGate{}}
ui.select_emoji(context.Background(), interaction.Value{Selected: "🐐", Text: "Goat"}, false)
if got := (<-output.speech).Text; got != "Emoji insertion unavailable" {
t.Fatal(got)
}
}
type emojiBrowseKeys struct{ uiTestKeys }
func (*emojiBrowseKeys) BrowseNavigationEnabled() bool { return true }
func TestEmojiBrowseModeBlocksTypingAndAllowsCopy(t *testing.T) {
keys := &emojiBrowseKeys{}
source := &emojiTestSource{keys: &keys.uiTestKeys, calls: make(chan string, 1)}
copier := &ocrTestCopier{copied: make(chan string, 1)}
output := &ocrTestOutput{speech: make(chan speechRequest, 2)}
ui := &internalUI{keys: keys, emojiSource: source, emojiCopier: copier, speech: output, gate: &focusGate{}}
ui.select_emoji(context.Background(), interaction.Value{Selected: "🐐", Text: "Goat"}, false)
if got := (<-output.speech).Text; got != "Emoji insertion unavailable in browse mode" {
t.Fatal(got)
}
ui.select_emoji(context.Background(), interaction.Value{Selected: "🐐", Text: "Goat"}, true)
if got := (<-output.speech).Text; got != "Copied Goat to clipboard" {
t.Fatal(got)
}
select {
case got := <-source.calls:
t.Fatalf("typed in browse mode: %q", got)
default:
}
if got := <-copier.copied; got != "🐐" {
t.Fatal(got)
}
}