package config import ( "os" "path/filepath" "strings" "testing" ) func writeConfig(t *testing.T, content string) string { t.Helper() path := filepath.Join(t.TempDir(), "config.yaml") if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } return path } // Regression: a new key's index must be written as an int scalar. A !!str key // ("25":) makes config.Load fail — yaml.v3 refuses string keys for map[int] — // which bricked the config on the daemon's next hot reload. func TestApplyKeyEditNewSlotLoadsBack(t *testing.T) { path := writeConfig(t, "icons_dir: ./icons\nbrightness: 70\nkeys: {}\n") err := ApplyKeyEdit(path, 25, KeyEdit{Set: map[string]any{ "icon": "test.png", "command": "echo hi", }}) if err != nil { t.Fatalf("ApplyKeyEdit: %v", err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load after edit: %v", err) } key, ok := cfg.Keys[25] if !ok { t.Fatalf("key 25 missing after edit; keys=%v", cfg.Keys) } if key.Icon != "test.png" || key.Command != "echo hi" { t.Fatalf("key 25 = %+v", key) } raw, _ := os.ReadFile(path) if strings.Contains(string(raw), `"25"`) { t.Fatalf("index written as a quoted string:\n%s", raw) } } // Editing an existing key must merge, and comments/blank lines must survive. func TestApplyKeyEditMergePreservesFormatting(t *testing.T) { path := writeConfig(t, `icons_dir: ./icons # the deck keys: 0: icon: a.png # keep me command: run-a `) if err := ApplyKeyEdit(path, 0, KeyEdit{Set: map[string]any{"text": "Hi"}}); err != nil { t.Fatalf("ApplyKeyEdit: %v", err) } raw, _ := os.ReadFile(path) text := string(raw) for _, want := range []string{"# the deck", "# keep me", "command: run-a", "text: Hi", "\n\n"} { if !strings.Contains(text, want) { t.Fatalf("output missing %q:\n%s", want, text) } } cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Keys[0].Icon != "a.png" || cfg.Keys[0].Text != "Hi" { t.Fatalf("merge lost fields: %+v", cfg.Keys[0]) } } // Unset is applied before Set, so a field named in both ends up set. func TestApplyKeyEditSetWinsOverUnset(t *testing.T) { path := writeConfig(t, "keys:\n 0:\n command: old\n") err := ApplyKeyEdit(path, 0, KeyEdit{ Set: map[string]any{"command": "new"}, Unset: []string{"command"}, }) if err != nil { t.Fatalf("ApplyKeyEdit: %v", err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Keys[0].Command != "new" { t.Fatalf("command = %q, want %q", cfg.Keys[0].Command, "new") } } func TestClearKeyAndSetBrightness(t *testing.T) { path := writeConfig(t, "brightness: 70\nkeys:\n 3:\n command: x\n") if err := ClearKey(path, 3); err != nil { t.Fatalf("ClearKey: %v", err) } if err := ClearKey(path, 99); err != nil { // absent key is not an error t.Fatalf("ClearKey absent: %v", err) } if err := SetBrightness(path, 140); err != nil { // clamped t.Fatalf("SetBrightness: %v", err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if len(cfg.Keys) != 0 { t.Fatalf("keys not cleared: %v", cfg.Keys) } if cfg.Brightness != 100 { t.Fatalf("brightness = %d, want 100", cfg.Brightness) } }