First real commit. Setting up for full CI/CD and testing pipeline

This commit is contained in:
2026-11-04 22:30:59 +01:00
parent cbb843344e
commit e08c997914
26 changed files with 3561 additions and 198 deletions

121
app/app.go Normal file
View File

@@ -0,0 +1,121 @@
package app
import (
"context"
"log"
"os"
"path/filepath"
"sync"
"fast_reader/internal/reader"
"fast_reader/internal/settings"
"fast_reader/internal/storage"
)
type App struct {
ctx context.Context
mu sync.RWMutex
settings settings.Settings
settingsPath string
}
func NewApp() *App {
return &App{
settings: settings.Default(),
}
}
func (a *App) Startup(ctx context.Context) {
a.ctx = ctx
configDir, err := os.UserConfigDir()
if err != nil {
log.Printf("Could not locate user config directory: %v", err)
return
}
a.mu.Lock()
defer a.mu.Unlock()
a.settingsPath = filepath.Join(
configDir,
"fast_reader",
"settings.json",
)
var saved settings.Settings
err = storage.Load(a.settingsPath, &saved)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("Could not load settings: %v", err)
}
a.settings = settings.Default()
return
}
a.settings = saved.Normalize()
log.Println("Settings loaded successfully")
}
// PrepareText converts pasted text into a sequence of words
// ready for RSVP playback.
func (a *App) PrepareText(text string) []reader.Word {
return reader.Tokenize(text)
}
// GetVersion returns the application version.
func (a *App) GetVersion() string {
return "0.1.0"
}
// GetSettings returns the current application settings.
func (a *App) GetSettings() settings.Settings {
a.mu.RLock()
defer a.mu.RUnlock()
return a.settings
}
// SaveSettings validates and persists application settings.
func (a *App) SaveSettings(input settings.Settings) error {
normalized := input.Normalize()
a.mu.Lock()
defer a.mu.Unlock()
if a.settingsPath == "" {
configDir, err := os.UserConfigDir()
if err != nil {
return err
}
a.settingsPath = filepath.Join(
configDir,
"fast_reader",
"settings.json",
)
}
if err := storage.Save(a.settingsPath, normalized); err != nil {
return err
}
a.settings = normalized
return nil
}
// ResetSettings restores and persists the default settings.
func (a *App) ResetSettings() (settings.Settings, error) {
defaults := settings.Default()
if err := a.SaveSettings(defaults); err != nil {
return settings.Settings{}, err
}
return defaults, nil
}

154
app/app_test.go Normal file
View File

@@ -0,0 +1,154 @@
package app
import (
"context"
"testing"
"fast_reader/internal/settings"
)
func TestNewApp(t *testing.T) {
a := NewApp()
if a == nil {
t.Fatal("NewApp() returned nil")
}
if got := a.GetSettings(); got != settings.Default() {
t.Errorf("initial settings = %+v, want %+v",
got, settings.Default())
}
}
func TestStartup(t *testing.T) {
application := NewApp()
ctx := context.Background()
// Startup should store the Wails application context.
application.Startup(ctx)
if application.ctx != ctx {
t.Error("Startup() did not store the application context")
}
}
func TestPrepareText(t *testing.T) {
a := NewApp()
words := a.PrepareText("Hello world")
if len(words) != 2 {
t.Fatalf("PrepareText() returned %d words, want 2", len(words))
}
if words[0].Text != "Hello" || words[1].Text != "world" {
t.Errorf("unexpected words: %+v", words)
}
}
func TestPrepareEmptyText(t *testing.T) {
application := NewApp()
got := application.PrepareText("")
if len(got) != 0 {
t.Errorf("PrepareText() returned %d words for empty input, want 0", len(got))
}
}
func TestGetVersion(t *testing.T) {
a := NewApp()
if got := a.GetVersion(); got != "0.1.0" {
t.Errorf("GetVersion() = %q, want %q", got, "0.1.0")
}
}
func TestSaveAndLoadSettings(t *testing.T) {
configDir := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configDir)
first := NewApp()
first.Startup(context.Background())
expected := settings.Settings{
ReadingSpeed: 500,
FontSize: 64,
Theme: settings.ThemeLight,
}
if err := first.SaveSettings(expected); err != nil {
t.Fatalf("SaveSettings() error = %v", err)
}
if got := first.GetSettings(); got != expected {
t.Errorf("settings = %+v, want %+v", got, expected)
}
// Simulate restarting the application.
second := NewApp()
second.Startup(context.Background())
if got := second.GetSettings(); got != expected {
t.Errorf("reloaded settings = %+v, want %+v", got, expected)
}
}
func TestSaveSettingsNormalizesValues(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
a := NewApp()
a.Startup(context.Background())
input := settings.Settings{
ReadingSpeed: 2000,
FontSize: 10,
Theme: "invalid",
}
if err := a.SaveSettings(input); err != nil {
t.Fatalf("SaveSettings() error = %v", err)
}
expected := settings.Settings{
ReadingSpeed: settings.MaxReadingSpeed,
FontSize: settings.MinFontSize,
Theme: settings.ThemeDark,
}
if got := a.GetSettings(); got != expected {
t.Errorf("settings = %+v, want %+v", got, expected)
}
}
func TestResetSettings(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
a := NewApp()
a.Startup(context.Background())
custom := settings.Settings{
ReadingSpeed: 600,
FontSize: 72,
Theme: settings.ThemeLight,
}
if err := a.SaveSettings(custom); err != nil {
t.Fatalf("SaveSettings() error = %v", err)
}
got, err := a.ResetSettings()
if err != nil {
t.Fatalf("ResetSettings() error = %v", err)
}
if got != settings.Default() {
t.Errorf("ResetSettings() = %+v, want %+v",
got, settings.Default())
}
if current := a.GetSettings(); current != settings.Default() {
t.Errorf("current settings = %+v, want defaults %+v",
current, settings.Default())
}
}