578 lines
21 KiB
Go
578 lines
21 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"git.stormux.org/storm/cthulhu/internal/config"
|
|
"git.stormux.org/storm/cthulhu/internal/input"
|
|
"git.stormux.org/storm/cthulhu/internal/platform/xorg"
|
|
"git.stormux.org/storm/cthulhu/internal/preferences"
|
|
)
|
|
|
|
type titleProbeSource struct{ title atspi.ForegroundTitle }
|
|
|
|
func (source *titleProbeSource) ForegroundTitle() atspi.ForegroundTitle { return source.title }
|
|
func (source *titleProbeSource) TitleChanges() <-chan struct{} { return nil }
|
|
func (source *titleProbeSource) Foreground() atspi.ForegroundState {
|
|
return atspi.ForegroundState{Epoch: source.title.Epoch, Client: xorg.Foreground{Known: true, Window: source.title.Title.Window}}
|
|
}
|
|
func (source *titleProbeSource) ForegroundChanges() <-chan struct{} { return nil }
|
|
|
|
type titleProbeQueue struct {
|
|
requests []speechRequest
|
|
interruptions int
|
|
}
|
|
|
|
func (queue *titleProbeQueue) Enqueue(request speechRequest) {
|
|
queue.requests = append(queue.requests, request)
|
|
}
|
|
func (queue *titleProbeQueue) Interrupt() { queue.interruptions++ }
|
|
|
|
func titleControllerFixture(t *testing.T) (*windowTitleController, *titleProbeQueue, atspi.Event) {
|
|
t.Helper()
|
|
queue := &titleProbeQueue{}
|
|
live := &liveSettings{}
|
|
live.Store(config.DefaultSettings())
|
|
titles := &windowTitleController{state: &eventLoopState{}, live: live, speech: queue}
|
|
activation := ownerEvent(atspi.WindowActivatedEvent, ":1.50", "/window", "/window")
|
|
activation.Source.Name, activation.Source.Role = "Original", "frame"
|
|
titles.state.owner.activate(activation)
|
|
titles.activated(activation)
|
|
t.Cleanup(titles.reset)
|
|
changed := activation
|
|
changed.Type = atspi.AccessibleNameEvent
|
|
return titles, queue, changed
|
|
}
|
|
|
|
func TestAutomaticTitlePolicyCoalescesAndPreservesSpeech(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
for _, text := range []string{"Loading", "Loading 50%", "Loading 100%"} {
|
|
changed.Source.Name = text
|
|
if !titles.accessible(changed) {
|
|
t.Fatal("title event not consumed")
|
|
}
|
|
}
|
|
titles.flush()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "Loading 100%" ||
|
|
!queue.requests[0].Append || queue.interruptions != 0 {
|
|
t.Fatalf("title burst = %+v, interruptions %d", queue.requests, queue.interruptions)
|
|
}
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("duplicate title was announced")
|
|
}
|
|
changed.Source.Name = ""
|
|
titles.accessible(changed)
|
|
changed.Source.Name = "Loading 100%"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(queue.requests) != 2 {
|
|
t.Fatal("restored title was not announced")
|
|
}
|
|
}
|
|
|
|
func TestScheduledTitleInterruptedBeforeSpeechQueue(t *testing.T) {
|
|
for _, name := range []string{"c", "Tab", "Control_L", "Escape"} {
|
|
t.Run(name, func(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
tracker := &input.IntentTracker{}
|
|
titles.inputSource = tracker
|
|
changed.Source.Name = "Scheduled status"
|
|
titles.accessible(changed)
|
|
tracker.Observe(input.NewKeyEvent(0, true, name))
|
|
// A newer sample in the same burst must not undo cancellation.
|
|
changed.Source.Name = "Latest status"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(queue.requests) != 0 || titles.pending != nil {
|
|
t.Fatal("interrupted title reached the speech queue")
|
|
}
|
|
// A genuinely later change still announces normally.
|
|
changed.Source.Name = "New status"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "New status" {
|
|
t.Fatalf("later title = %+v", queue.requests)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScheduledNativeIntroductionPreservesSwitchAndRepeatInput(t *testing.T) {
|
|
titles, queue, _ := titleControllerFixture(t)
|
|
titles.state.owner.clear()
|
|
tracker := &input.IntentTracker{}
|
|
titles.inputSource = tracker
|
|
source := &titleProbeSource{title: atspi.ForegroundTitle{Epoch: 1, Title: xorg.WindowTitle{Window: 20, TitleWindow: 20, Title: "Other window"}}}
|
|
titles.source, titles.foreground = source, source
|
|
// Input initiating a switch precedes discovery of its destination title.
|
|
tracker.Observe(input.NewKeyEvent(0, true, "Tab"))
|
|
titles.nativeChanged()
|
|
repeat := input.NewKeyEvent(0, true, "Tab")
|
|
repeat.Repeat = true
|
|
tracker.Observe(repeat)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "Other window" {
|
|
t.Fatalf("destination title = %+v", queue.requests)
|
|
}
|
|
// Fresh typing after a subsequent switch cancels its delayed introduction.
|
|
source.title.Epoch++
|
|
source.title.Title.Window, source.title.Title.TitleWindow = 21, 21
|
|
source.title.Title.Title = "Next window"
|
|
titles.nativeChanged()
|
|
tracker.Observe(input.NewKeyEvent(0, true, "c"))
|
|
repeat = input.NewKeyEvent(0, true, "c")
|
|
repeat.Repeat = true
|
|
tracker.Observe(repeat)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("typing interruption was hidden by its later repeat")
|
|
}
|
|
// Returning to that window must introduce it again despite cancellation.
|
|
source.title.Epoch++
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 2 || queue.requests[1].Text != "Next window" {
|
|
t.Fatalf("return title = %+v", queue.requests)
|
|
}
|
|
}
|
|
|
|
func TestScheduledTitleCannotReviveWhileWaitingForSettings(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
tracker := &input.IntentTracker{}
|
|
titles.inputSource = tracker
|
|
titles.live.onApplication = func(string) {}
|
|
titles.application = "Example Application"
|
|
changed.Source.Name = "Pending application title"
|
|
titles.accessible(changed)
|
|
titles.flush() // Requeued until the application override is committed.
|
|
tracker.Observe(input.NewKeyEvent(0, true, "c"))
|
|
application := titles.application
|
|
titles.live.application.Store(&application)
|
|
titles.flush()
|
|
if len(queue.requests) != 0 || titles.pending != nil {
|
|
t.Fatal("settings retry revived an interrupted title")
|
|
}
|
|
}
|
|
|
|
func TestAutomaticTitleUsesBrailleMessagePathAndKeepsFocus(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
display := &flashRecorder{}
|
|
titles.speech = outputQueue{speech: queue, braille: display, live: titles.live}
|
|
focused := titles.state.owner.focus
|
|
changed.Source.Name = "Updated title"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(display.messages) != 1 || display.messages[0] != "Updated title" || titles.state.owner.focus != focused {
|
|
t.Fatalf("title braille = %q, focus = %+v", display.messages, titles.state.owner.focus)
|
|
}
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
changed.Source.Name = "Silent title"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(display.messages) != 1 {
|
|
t.Fatal("disabled title still flashed on braille")
|
|
}
|
|
}
|
|
|
|
func TestAutomaticTitlesRejectBackgroundDisabledAndStaleEvents(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
background := changed
|
|
background.Source.Path = "/background"
|
|
background.Source.Name = "Background"
|
|
titles.accessible(background)
|
|
titles.flush()
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
changed.Source.Name = "Disabled"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
settings.Reading.AutomaticallyReadTitleChanges = true
|
|
titles.live.Store(settings)
|
|
changed.Source.Name = "Stale"
|
|
titles.accessible(changed)
|
|
titles.state.owner.clear()
|
|
titles.flush()
|
|
if len(queue.requests) != 0 {
|
|
t.Fatalf("unwanted titles = %+v", queue.requests)
|
|
}
|
|
}
|
|
|
|
func TestAutomaticTitlesCancelEmptyAndLockedUpdates(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
changed.Source.Name = "Transient"
|
|
titles.accessible(changed)
|
|
changed.Source.Name = ""
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
titles.gate = newFocusGate(nil)
|
|
changed.Source.Name = "Locked title"
|
|
titles.accessible(changed)
|
|
titles.gate.secure.Store(true)
|
|
titles.flush()
|
|
titles.gate.secure.Store(false)
|
|
titles.gate.suspended.Store(true)
|
|
changed.Source.Name = "XTerm handoff"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(queue.requests) != 0 {
|
|
t.Fatalf("unwanted titles = %+v", queue.requests)
|
|
}
|
|
}
|
|
|
|
func TestNativeXTermIntroductionDuringHandoff(t *testing.T) {
|
|
for _, accessible := range []bool{false, true} {
|
|
t.Run(map[bool]string{false: "native only", true: "accessible activation"}[accessible], func(t *testing.T) {
|
|
titles, queue, activation := titleControllerFixture(t)
|
|
titles.state.owner.clear()
|
|
titles.gate = newFocusGate(nil)
|
|
titles.gate.SetSuspended(true)
|
|
titles.speech = gatedSpeech{speech: queue, gate: titles.gate}
|
|
source := &titleProbeSource{title: atspi.ForegroundTitle{Epoch: 1, Title: xorg.WindowTitle{Window: 20, TitleWindow: 20, Title: "Shell", Application: "xterm"}}}
|
|
titles.source, titles.foreground = source, source
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
if accessible {
|
|
activation.Source.Name, activation.Source.ApplicationName = "Shell", "xterm"
|
|
activation.Foreground.Epoch = 1
|
|
titles.state.owner.activate(activation)
|
|
titles.activated(activation)
|
|
}
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "Shell" || !titles.gate.Suspended() {
|
|
t.Fatalf("handoff title = %+v; suspended = %t", queue.requests, titles.gate.Suspended())
|
|
}
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
source.title.Title.Title = "Shell updated"
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("handoff announced duplicate or terminal title change")
|
|
}
|
|
// Separate xterms can have the same title; each switch introduces it.
|
|
source.title.Epoch++
|
|
source.title.Title.Window, source.title.Title.TitleWindow = 21, 21
|
|
source.title.Title.Title = "Shell"
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 2 || queue.requests[1].Text != "Shell" {
|
|
t.Fatalf("second terminal introduction = %+v", queue.requests)
|
|
}
|
|
source.title.Epoch++
|
|
source.title.Title.Window, source.title.Title.TitleWindow = 22, 22
|
|
source.title.Title.Title = "Second shell"
|
|
titles.nativeChanged()
|
|
titles.gate.secure.Store(true)
|
|
titles.flush()
|
|
if len(queue.requests) != 2 {
|
|
t.Fatal("locked screen announced terminal title")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNativeTitlesSharePolicyAndActivationDeduplication(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
source := &titleProbeSource{title: atspi.ForegroundTitle{Title: xorg.WindowTitle{Window: 10, TitleWindow: 10, Title: "Original"}}}
|
|
titles.source, titles.foreground = source, source
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
changed.Source.Name = "Changed"
|
|
titles.accessible(changed)
|
|
source.title.Title.Title = "Changed"
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "Changed" {
|
|
t.Fatalf("duplicate source output = %+v", queue.requests)
|
|
}
|
|
source.title.Title.Title = "Changed again"
|
|
titles.nativeChanged()
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("native change ignored disabled preference")
|
|
}
|
|
titles.state.owner.clear()
|
|
source.title.Epoch++
|
|
source.title.Title.Window, source.title.Title.TitleWindow, source.title.Title.Title = 20, 20, "Other window"
|
|
titles.nativeChanged()
|
|
source.title.Title.Title = "Other window ready"
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 2 || queue.requests[1].Text != "Other window ready" {
|
|
t.Fatal("disabled title changes suppressed initial window introduction")
|
|
}
|
|
}
|
|
|
|
func TestNativeTitleDropsOldForegroundAndTracksWineChildSwitch(t *testing.T) {
|
|
titles, queue, _ := titleControllerFixture(t)
|
|
titles.state.owner.clear()
|
|
source := &titleProbeSource{title: atspi.ForegroundTitle{Epoch: 1, Title: xorg.WindowTitle{Window: 10, TitleWindow: 11, Title: "Wine game"}}}
|
|
titles.source, titles.foreground = source, source
|
|
titles.nativeChanged()
|
|
source.title.Epoch++
|
|
titles.flush()
|
|
if len(queue.requests) != 0 {
|
|
t.Fatal("old foreground title survived")
|
|
}
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
source.title.Title.TitleWindow, source.title.Title.Title = 12, "Wine dialog"
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 2 || queue.requests[1].Text != "Wine dialog" {
|
|
t.Fatalf("Wine child switch = %+v", queue.requests)
|
|
}
|
|
}
|
|
|
|
func TestAutomaticTitlesWaitForApplicationOverride(t *testing.T) {
|
|
titles, queue, changed := titleControllerFixture(t)
|
|
titles.application = "Example Application"
|
|
titles.live.onApplication = func(string) {}
|
|
changed.Source.Name = "Changing title"
|
|
titles.accessible(changed)
|
|
titles.flush()
|
|
if len(queue.requests) != 0 || titles.pending == nil {
|
|
t.Fatal("title raced application settings")
|
|
}
|
|
application := titles.application
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
titles.live.application.Store(&application)
|
|
titles.flush()
|
|
if len(queue.requests) != 0 || titles.pending != nil {
|
|
t.Fatal("application override was not honored")
|
|
}
|
|
}
|
|
|
|
func TestLateNativeSampleHonorsDisabledTitleChanges(t *testing.T) {
|
|
titles, queue, _ := titleControllerFixture(t)
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
source := &titleProbeSource{title: atspi.ForegroundTitle{Title: xorg.WindowTitle{Window: 10, TitleWindow: 10, Title: "Changed after activation"}}}
|
|
titles.source, titles.foreground = source, source
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 0 {
|
|
t.Fatal("first native sample bypassed disabled preference")
|
|
}
|
|
}
|
|
|
|
func TestTitlePreferenceAppliesPersistsAndRestoresAcrossApplications(t *testing.T) {
|
|
settings := config.DefaultSettings()
|
|
settings.Path = filepath.Join(t.TempDir(), "settings.toml")
|
|
store, err := config.NewStore(settings.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = store.Save(settings); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
worker := newPreferencesWorker(slog.New(slog.DiscardHandler), settings, nil)
|
|
worker.live = &liveSettings{}
|
|
worker.live.Store(settings)
|
|
ctx := context.Background()
|
|
worker.switchApplication(ctx, "Noisy Application")
|
|
worker.beginEdit(ctx, "\x00")
|
|
values := preferences.FromSettings(worker.editing)
|
|
if !values.Reading.AutomaticallyReadTitleChanges {
|
|
t.Fatal("checkbox defaults off")
|
|
}
|
|
values.Reading.AutomaticallyReadTitleChanges = false
|
|
if response := worker.apply(ctx, preferences.Request{Values: values, Save: true}); !response.Accepted {
|
|
t.Fatal(response.Message)
|
|
}
|
|
worker.editingActive = false
|
|
worker.switchApplication(ctx, "Other Application")
|
|
if !worker.live.Load().Reading.AutomaticallyReadTitleChanges {
|
|
t.Fatal("override leaked")
|
|
}
|
|
worker.switchApplication(ctx, "Noisy Application")
|
|
if worker.live.Load().Reading.AutomaticallyReadTitleChanges {
|
|
t.Fatal("override lost on return")
|
|
}
|
|
reopened, err := config.NewStore(settings.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loaded, found, err := reopened.LoadForApplication("Noisy Application")
|
|
if err != nil || !found || loaded.Reading.AutomaticallyReadTitleChanges {
|
|
t.Fatalf("saved override = %t, %t, %v", loaded.Reading.AutomaticallyReadTitleChanges, found, err)
|
|
}
|
|
global, err := reopened.Load()
|
|
if err != nil || !global.Reading.AutomaticallyReadTitleChanges {
|
|
t.Fatalf("global default changed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWineTitlesUseSharedControllerWhileControlsStayReadable(t *testing.T) {
|
|
titles, queue, _ := titleControllerFixture(t)
|
|
titles.state.owner.clear()
|
|
titles.lastTitle = ""
|
|
control := make(chan string, 2)
|
|
owner := newWineEventPresenter(func(text string) bool { control <- text; return true })
|
|
defer owner.Close()
|
|
connectWineWindowTitles(owner, titles.live, &fakeAdapter{})
|
|
owner.pending = &winePresentation{source: "control", window: "Wine game", message: "Play, button", parts: []string{"Play", "button"}}
|
|
owner.flush()
|
|
if got := <-control; got != "Play, button" {
|
|
t.Fatalf("control included title: %q", got)
|
|
}
|
|
titles.wineChanged(<-titles.live.wineTitles)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "Wine game" {
|
|
t.Fatal("Wine initial title missing")
|
|
}
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
owner.pending = &winePresentation{source: "control", window: "Wine game loading", message: "Play, button", parts: []string{"Play", "button"}}
|
|
owner.flush()
|
|
<-control
|
|
titles.wineChanged(<-titles.live.wineTitles)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("Wine helper bypassed preference")
|
|
}
|
|
owner.pending = &winePresentation{source: "window", window: "Wine game ready", message: "Wine game ready", parts: []string{"Wine game ready"}}
|
|
owner.flush()
|
|
titles.wineChanged(<-titles.live.wineTitles)
|
|
titles.flush()
|
|
select {
|
|
case text := <-control:
|
|
t.Fatalf("title-only helper focus bypassed preference: %q", text)
|
|
default:
|
|
}
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("title-only focus bypassed preference")
|
|
}
|
|
}
|
|
|
|
func TestWineTitleReportsCannotClaimUnrelatedForeground(t *testing.T) {
|
|
titles, queue, _ := titleControllerFixture(t)
|
|
source := &titleProbeSource{title: atspi.ForegroundTitle{Epoch: 1, Title: xorg.WindowTitle{Window: 10, TitleWindow: 10, Title: "Terminal"}}}
|
|
titles.source, titles.foreground = source, source
|
|
titles.wineChanged(wineWindowTitle{text: "Background game", foreground: source.title})
|
|
titles.flush()
|
|
if len(queue.requests) != 0 {
|
|
t.Fatal("background helper title was announced")
|
|
}
|
|
}
|
|
|
|
func TestWineReviewFocusDoesNotSuppressTitleIntroduction(t *testing.T) {
|
|
for _, desktop := range []bool{false, true} {
|
|
for _, reviewFirst := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("desktop=%t/reviewFirst=%t", desktop, reviewFirst), func(t *testing.T) {
|
|
c, registry, source, worker := reviewFocusSetup(t)
|
|
titles, queue, _ := titleControllerFixture(t)
|
|
titles.state = c.state
|
|
titles.source, titles.foreground = source, source
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
if desktop {
|
|
source.title.Title.TitleWindow = 21
|
|
source.title.Title.Client.Window = 21
|
|
}
|
|
introduce := func() {
|
|
if reviewFirst {
|
|
c.changed()
|
|
}
|
|
titles.nativeChanged()
|
|
c.changed() // The event loop syncs review focus again before flushing.
|
|
titles.wineChanged(wineWindowTitle{text: "Same title", foreground: source.title})
|
|
titles.flush()
|
|
}
|
|
introduce()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "Same title" {
|
|
t.Fatalf("Wine introduction = %+v", queue.requests)
|
|
}
|
|
if worker.Focus() != registry.notices[0].ID() || c.state.owner.title != "Same title" {
|
|
t.Fatal("title presentation changed Wine review ownership")
|
|
}
|
|
c.changed()
|
|
titles.nativeChanged()
|
|
titles.wineChanged(wineWindowTitle{text: "Same title", foreground: source.title})
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("duplicate Wine title was spoken")
|
|
}
|
|
registry.notices[0].Title = "Status changed"
|
|
source.title.Title.Title = "Status changed"
|
|
c.changed()
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("Wine title change bypassed disabled preference")
|
|
}
|
|
// Returning to the same Wine window must introduce it again.
|
|
c.retire()
|
|
source.state.Epoch++
|
|
source.title.Epoch = source.state.Epoch
|
|
registry.notices[0].Title = "Same title"
|
|
source.title.Title.Title = "Same title"
|
|
introduce()
|
|
if len(queue.requests) != 2 || queue.requests[1].Text != "Same title" {
|
|
t.Fatalf("return to Wine window = %+v", queue.requests)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWineReviewTitlesUseNativeApplicationPreferences(t *testing.T) {
|
|
c, registry, source, _ := reviewFocusSetup(t)
|
|
titles, queue, _ := titleControllerFixture(t)
|
|
titles.state = c.state
|
|
titles.source, titles.foreground = source, source
|
|
var application string
|
|
titles.live.onApplication = func(name string) { application = name }
|
|
previousApplication := "Other Application"
|
|
titles.live.application.Store(&previousApplication)
|
|
c.changed()
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if application != "fixture.exe" || len(queue.requests) != 0 || titles.pending == nil {
|
|
t.Fatalf("Wine title raced application settings: application=%q requests=%+v", application, queue.requests)
|
|
}
|
|
settings := titles.live.Load()
|
|
settings.Reading.AutomaticallyReadTitleChanges = false
|
|
titles.live.Store(settings)
|
|
titles.live.application.Store(&application)
|
|
titles.flush()
|
|
if len(queue.requests) != 1 || queue.requests[0].Text != "Same title" {
|
|
t.Fatalf("Wine introduction = %+v", queue.requests)
|
|
}
|
|
registry.notices[0].Title = "Changed status"
|
|
source.title.Title.Title = "Changed status"
|
|
c.changed()
|
|
titles.nativeChanged()
|
|
titles.flush()
|
|
if len(queue.requests) != 1 {
|
|
t.Fatal("Wine title ignored its application preference")
|
|
}
|
|
}
|