From 7ed43baaa6be88a55952a3411575922c8d883a08 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 06 Jul 2026 15:47:42 +0800
Subject: [PATCH] feat: default right modifier hotkey

---
 C1.source/privatevoice.src/internal/config/config_test.go |  150 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 150 insertions(+), 0 deletions(-)

diff --git a/C1.source/privatevoice.src/internal/config/config_test.go b/C1.source/privatevoice.src/internal/config/config_test.go
index c04af51..c6d94aa 100644
--- a/C1.source/privatevoice.src/internal/config/config_test.go
+++ b/C1.source/privatevoice.src/internal/config/config_test.go
@@ -1,9 +1,14 @@
 package config
 
 import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+	"runtime"
 	"testing"
 
 	"voicesnap/internal/model"
+	"voicesnap/internal/paths"
 )
 
 func TestDefaultLanguageConfig(t *testing.T) {
@@ -19,6 +24,117 @@
 	}
 	if cfg.ModelSelectionMode != ModelSelectionModeAuto {
 		t.Fatalf("model selection mode = %q, want %q", cfg.ModelSelectionMode, ModelSelectionModeAuto)
+	}
+}
+
+func TestDefaultHotkeyForNewConfig(t *testing.T) {
+	tests := []struct {
+		goos string
+		want int
+	}{
+		{goos: "darwin", want: RightModifierDefaultVK},
+		{goos: "windows", want: RightModifierDefaultVK},
+		{goos: "linux", want: LegacyDefaultHotkeyVK},
+	}
+
+	for _, tt := range tests {
+		if got := defaultHotkeyVKForNewConfig(tt.goos); got != tt.want {
+			t.Fatalf("defaultHotkeyVKForNewConfig(%q) = 0x%X, want 0x%X", tt.goos, got, tt.want)
+		}
+	}
+}
+
+func TestLoadCreatesFirstConfigWithNewPlatformDefault(t *testing.T) {
+	withIsolatedConfigHome(t)
+
+	cfg, err := Load()
+	if err != nil {
+		t.Fatalf("Load() error = %v", err)
+	}
+	if got, want := cfg.HotkeyVK, defaultHotkeyVKForNewConfig(runtime.GOOS); got != want {
+		t.Fatalf("HotkeyVK = 0x%X, want 0x%X", got, want)
+	}
+	if cfg.HotkeyMode != HotkeyModeHold {
+		t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeHold)
+	}
+
+	raw := readConfigJSON(t)
+	if got, ok := raw["HotkeyVK"].(float64); !ok || int(got) != cfg.HotkeyVK {
+		t.Fatalf("saved HotkeyVK = %#v, want %d", raw["HotkeyVK"], cfg.HotkeyVK)
+	}
+	if got := raw["HotkeyMode"]; got != HotkeyModeHold {
+		t.Fatalf("saved HotkeyMode = %#v, want %q", got, HotkeyModeHold)
+	}
+}
+
+func TestLoadExistingConfigPreservesHotkeyAndMode(t *testing.T) {
+	withIsolatedConfigHome(t)
+	writeConfigJSON(t, map[string]interface{}{
+		"HotkeyVK":   0x41,
+		"HotkeyMode": HotkeyModeTap,
+	})
+
+	cfg, err := Load()
+	if err != nil {
+		t.Fatalf("Load() error = %v", err)
+	}
+	if cfg.HotkeyVK != 0x41 {
+		t.Fatalf("HotkeyVK = 0x%X, want 0x41", cfg.HotkeyVK)
+	}
+	if cfg.HotkeyMode != HotkeyModeTap {
+		t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeTap)
+	}
+}
+
+func TestLoadExistingConfigMissingHotkeyUsesLegacyFallback(t *testing.T) {
+	withIsolatedConfigHome(t)
+	writeConfigJSON(t, map[string]interface{}{
+		"HotkeyMode": HotkeyModeTap,
+	})
+
+	cfg, err := Load()
+	if err != nil {
+		t.Fatalf("Load() error = %v", err)
+	}
+	if cfg.HotkeyVK != LegacyDefaultHotkeyVK {
+		t.Fatalf("HotkeyVK = 0x%X, want legacy 0x%X", cfg.HotkeyVK, LegacyDefaultHotkeyVK)
+	}
+	if cfg.HotkeyVK == RightModifierDefaultVK {
+		t.Fatalf("existing config missing HotkeyVK must not inherit new default 0x%X", RightModifierDefaultVK)
+	}
+	if cfg.HotkeyMode != HotkeyModeTap {
+		t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeTap)
+	}
+}
+
+func TestLoadExistingConfigMissingModeKeepsLegacyHoldFallback(t *testing.T) {
+	withIsolatedConfigHome(t)
+	writeConfigJSON(t, map[string]interface{}{
+		"HotkeyVK": 0x41,
+	})
+
+	cfg, err := Load()
+	if err != nil {
+		t.Fatalf("Load() error = %v", err)
+	}
+	if cfg.HotkeyVK != 0x41 {
+		t.Fatalf("HotkeyVK = 0x%X, want 0x41", cfg.HotkeyVK)
+	}
+	if cfg.HotkeyMode != HotkeyModeHold {
+		t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeHold)
+	}
+}
+
+func TestExistingConfigFallbackUsesLegacyHotkey(t *testing.T) {
+	cfg := ExistingConfigFallback()
+	if cfg.HotkeyVK != LegacyDefaultHotkeyVK {
+		t.Fatalf("HotkeyVK = 0x%X, want legacy 0x%X", cfg.HotkeyVK, LegacyDefaultHotkeyVK)
+	}
+	if cfg.HotkeyVK == RightModifierDefaultVK {
+		t.Fatalf("existing config fallback must not inherit new default 0x%X", RightModifierDefaultVK)
+	}
+	if cfg.HotkeyMode != HotkeyModeHold {
+		t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeHold)
 	}
 }
 
@@ -39,3 +155,37 @@
 		t.Fatalf("bad mode normalized to %q, want %q", got, ModelSelectionModeAuto)
 	}
 }
+
+func withIsolatedConfigHome(t *testing.T) {
+	t.Helper()
+	home := t.TempDir()
+	t.Setenv("HOME", home)
+	t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
+}
+
+func writeConfigJSON(t *testing.T, value map[string]interface{}) {
+	t.Helper()
+	if err := paths.Ensure(); err != nil {
+		t.Fatalf("paths.Ensure() error = %v", err)
+	}
+	data, err := json.Marshal(value)
+	if err != nil {
+		t.Fatalf("json.Marshal() error = %v", err)
+	}
+	if err := os.WriteFile(paths.File("config.json"), data, 0644); err != nil {
+		t.Fatalf("write config error = %v", err)
+	}
+}
+
+func readConfigJSON(t *testing.T) map[string]interface{} {
+	t.Helper()
+	data, err := os.ReadFile(paths.File("config.json"))
+	if err != nil {
+		t.Fatalf("read config error = %v", err)
+	}
+	var raw map[string]interface{}
+	if err := json.Unmarshal(data, &raw); err != nil {
+		t.Fatalf("json.Unmarshal() error = %v", err)
+	}
+	return raw
+}

--
Gitblit v1.9.3