// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "testing" "git.stormux.org/storm/cthulhu/internal/config" "git.stormux.org/storm/cthulhu/internal/remote" ) func TestWineEditFieldsOnlyTypingAndVoices(t *testing.T) { settings := config.DefaultSettings() settings.KeyEchoInEditFieldsOnly = true settings.UseCustomEchoVoice = true settings.UseCustomEchoForKey = true settings.UseCustomEchoForCharacter = false live := &liveSettings{} live.Store(settings) speech := &remoteSpeechQueue{} braille := &remoteBrailleQueue{running: true} presenter := remoteSpeech{speech: speech, braille: braille, live: live} owner := newWineEventPresenter(func(string) bool { return true }) defer owner.Close() presenter.wine = owner for _, tc := range []struct { role, states, description string want bool }{ {"edit", "", "", true}, {"read only edit", "", "", false}, {"edit", "read only", "", false}, {"button", "", "", false}, {"text", "", "", false}, {"edit", "", "password", false}, } { presenter.PresentAccessibleEvent(remote.AccessibleEvent{SourceID: "control", EventType: "focus", Role: tc.role, States: tc.states, Description: tc.description}) owner.flush() before := len(speech.requests) accepted := presenter.PresentAccessibleEvent(remote.AccessibleEvent{SourceID: "control", EventType: "text", Name: "A", States: "kind=8"}) if accepted != tc.want || (len(speech.requests) > before) != tc.want { t.Fatalf("role=%s states=%s protected=%s accepted=%v", tc.role, tc.states, tc.description, accepted) } if tc.want && (!speech.requests[before].CustomEcho || !speech.requests[before].Character || braille.text[len(braille.text)-1] != "A") { t.Fatal("Wine key voice or braille lost") } } presenter.PresentAccessibleEvent(remote.AccessibleEvent{SourceID: "edit", EventType: "focus", Role: "edit"}) owner.flush() settings.EnableEchoByCharacter = true live.Store(settings) presenter.PresentAccessibleEvent(remote.AccessibleEvent{SourceID: "edit", EventType: "text", Name: "a", States: "kind=8"}) if speech.requests[len(speech.requests)-1].CustomEcho { t.Fatal("character routing used key-only custom voice") } // Moving focus to a menu invalidates the preceding editor's text actions. presenter.PresentAccessibleEvent(remote.AccessibleEvent{SourceID: "menu", EventType: "focus", Role: "menu item"}) owner.flush() if presenter.PresentAccessibleEvent(remote.AccessibleEvent{SourceID: "edit", EventType: "text", Name: "a", States: "kind=8"}) { t.Fatal("old editor still echoes") } } func TestWineKeyEchoRequiresCurrentDesktopFocus(t *testing.T) { _, _, monitor, _ := reviewFocusSetup(t) owner := newWineEventPresenter(func(string) bool { return true }) defer owner.Close() owner.foreground, owner.titles = monitor, monitor focus := remote.AccessibleEvent{SourceID: "edit", EventType: "focus", Role: "edit", WindowTitle: monitor.title.Title.Title} owner.Present(focus) owner.flush() if !owner.EditFieldFocused() { t.Fatal("current Wine editor rejected") } monitor.state.Epoch++ if owner.EditFieldFocused() { t.Fatal("stale Wine editor survived foreground switch") } monitor.title.Epoch = monitor.state.Epoch owner.Present(focus) owner.flush() if !owner.EditFieldFocused() { t.Fatal("new focus did not restore editor") } monitor.title.Title.Wine = false if owner.EditFieldFocused() { t.Fatal("native window reused Wine focus") } monitor.title.Title.Wine = true focus.Role = "menu item" owner.Present(focus) owner.flush() if owner.EditFieldFocused() { t.Fatal("menu retained editor echo") } owner.Close() if owner.EditFieldFocused() { t.Fatal("closed helper retained focus") } }