Files
cthulhu/internal/app/laptop_layout_test.go

140 lines
5.4 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"bytes"
"errors"
"log/slog"
"os"
"path/filepath"
"reflect"
"testing"
"git.stormux.org/storm/cthulhu/internal/config"
"git.stormux.org/storm/cthulhu/internal/input"
"git.stormux.org/storm/cthulhu/internal/preferences"
)
func TestLaptopLayoutSessionAndExplicitPreferences(t *testing.T) {
global := config.DefaultSettings()
global.Path = filepath.Join(t.TempDir(), "settings.toml")
store, err := config.NewStore(global.Path)
if err != nil {
t.Fatal(err)
}
if err := store.Save(global); err != nil {
t.Fatal(err)
}
app := global
app.CthulhuModifierKeys = []string{"Insert"}
app.TimeFormat = "24-hour"
for _, name := range []string{"Cached Application", "New Application"} {
if err := store.SaveForApplication(name, app); err != nil {
t.Fatal(err)
}
}
original, err := os.ReadFile(global.Path)
if err != nil {
t.Fatal(err)
}
worker := newPreferencesWorker(slog.New(slog.DiscardHandler), global, acceptingRemoteSpeechSettings{})
worker.laptopLayout = true
worker.settings = worker.sessionKeyboard(global)
worker.live = &liveSettings{}
worker.live.Store(worker.settings)
recorder := &keyboardRecorder{}
worker.bindings = recorder
checkLaptop := func(settings config.Settings) {
t.Helper()
if settings.KeyboardLayout != int(input.LaptopKeyboardLayout) ||
!reflect.DeepEqual(settings.CthulhuModifierKeys, []string{"Caps_Lock"}) {
t.Fatalf("laptop keyboard lost: layout=%d keys=%v", settings.KeyboardLayout, settings.CthulhuModifierKeys)
}
}
checkLaptop(worker.settings)
worker.cycle_progress_output()
if worker.globalSettings.KeyboardLayout != global.KeyboardLayout || !reflect.DeepEqual(worker.globalSettings.CthulhuModifierKeys, global.CthulhuModifierKeys) {
t.Fatal("progress shortcut cached the startup keyboard in global settings")
}
for _, name := range []string{"Cached Application", "Other Application", "Cached Application", "New Application"} {
worker.switchApplication(t.Context(), name)
if worker.activeApp != name {
t.Fatal("application switch failed")
}
checkLaptop(worker.settings)
checkLaptop(worker.live.Load())
if name != "Other Application" && worker.settings.TimeFormat != "24-hour" {
t.Fatal("startup keyboard override discarded application preferences")
}
}
current, err := os.ReadFile(global.Path)
if err != nil || !bytes.Equal(current, original) {
t.Fatalf("switching applications saved the override: %v", err)
}
// A remote session change and the speech-save shortcut must not promote
// the startup keyboard override into global or cached profile settings.
next := worker.settings
next.EnableKeyEcho = true
if response := worker.applyNext(t.Context(), next, false, false); !response.Accepted {
t.Fatal(response.Message)
}
worker.cycle_progress_output()
cached := worker.appSettings[worker.activeApp]
if cached.KeyboardLayout != app.KeyboardLayout || !reflect.DeepEqual(cached.CthulhuModifierKeys, app.CthulhuModifierKeys) {
t.Fatal("session changes cached the startup keyboard in application settings")
}
worker.handleSpeechCommand(t.Context(), input.IncreaseSpeechSetting)
worker.handleSpeechCommand(t.Context(), input.SaveSpeechSettings)
loaded, err := store.Load()
if err != nil || loaded.KeyboardLayout != global.KeyboardLayout || !reflect.DeepEqual(loaded.CthulhuModifierKeys, global.CthulhuModifierKeys) {
t.Fatalf("speech save changed the saved keyboard: %v", err)
}
worker.switchApplication(t.Context(), "Other Application")
worker.switchApplication(t.Context(), "New Application")
checkLaptop(worker.settings)
if !worker.settings.EnableKeyEcho {
t.Fatal("remote application change was lost")
}
if !worker.beginEdit(t.Context(), "") {
t.Fatal("could not open global preferences")
}
checkLaptop(worker.editing)
values := preferences.FromSettings(worker.editing)
values.KeyboardLayout = global.KeyboardLayout
values.CthulhuModifierKeys = global.CthulhuModifierKeys
recorder.err = errors.New("grab conflict")
if response := worker.apply(t.Context(), preferences.Request{Values: values}); response.Accepted || !worker.laptopLayout {
t.Fatal("failed preferences apply discarded the startup override")
}
checkLaptop(worker.settings)
// Saving the displayed laptop choice explicitly makes it permanent.
recorder.err = nil
values = preferences.FromSettings(worker.editing)
if response := worker.apply(t.Context(), preferences.Request{Values: values, Save: true}); !response.Accepted || worker.laptopLayout {
t.Fatalf("explicit save did not replace the startup override: %+v", response)
}
loaded, err = store.Load()
if err != nil {
t.Fatal(err)
}
checkLaptop(loaded)
worker.editingActive = false
if !worker.beginEdit(t.Context(), "") {
t.Fatal("could not reopen preferences")
}
values = preferences.FromSettings(worker.editing)
values.KeyboardLayout = global.KeyboardLayout
values.CthulhuModifierKeys = global.CthulhuModifierKeys
if response := worker.apply(t.Context(), preferences.Request{Values: values}); !response.Accepted {
t.Fatal(response.Message)
}
worker.editingActive = false
worker.switchApplication(t.Context(), "Another Application")
if worker.settings.KeyboardLayout != global.KeyboardLayout || !reflect.DeepEqual(worker.settings.CthulhuModifierKeys, global.CthulhuModifierKeys) {
t.Fatal("preferences could not replace the laptop keyboard for the session")
}
}