package sway import ( "os" "path/filepath" "testing" ) func TestAutogenerateComment(t *testing.T) { tests := []struct { command string expected string }{ {"exec kitty", "kitty"}, {"exec --no-startup-id firefox", "firefox"}, {"kill", "Close window"}, {"exit", "Exit Sway"}, {"reload", "Reload configuration"}, {"fullscreen toggle", "Toggle fullscreen"}, {"floating toggle", "Float/unfloat window"}, {"focus mode_toggle", "Toggle focus mode"}, {"focus parent", "Focus parent container"}, {"focus left", "Focus left"}, {"focus right", "Focus right"}, {"focus up", "Focus up"}, {"focus down", "Focus down"}, {"focus output left", "Focus monitor"}, {"move left", "Move window left"}, {"move right", "Move window right"}, {"move up", "Move window up"}, {"move down", "Move window down"}, {"move container to workspace number 1", "Move to workspace 1"}, {"move container to workspace prev", "Move to previous workspace"}, {"move container to workspace next", "Move to next workspace"}, {"move workspace to output left", "Move workspace to monitor"}, {"workspace number 1", "Workspace 1"}, {"workspace prev", "Previous workspace"}, {"workspace next", "Next workspace"}, {"layout tabbed", "Layout tabbed"}, {"layout stacking", "Layout stacking"}, {"splith", "Split horizontal"}, {"splitv", "Split vertical"}, {"resize grow width 10 ppt", "Resize window"}, {"move scratchpad", "Toggle scratchpad"}, } for _, tt := range tests { t.Run(tt.command, func(t *testing.T) { result := autogenerateComment(tt.command) if result != tt.expected { t.Errorf("autogenerateComment(%q) = %q, want %q", tt.command, result, tt.expected) } }) } } func TestGetKeybindAtLine(t *testing.T) { tests := []struct { name string line string expected *KeyBinding }{ { name: "basic_keybind", line: "bindsym Mod4+q kill", expected: &KeyBinding{ Mods: []string{"Mod4"}, Key: "q", Command: "kill", Comment: "Close window", }, }, { name: "keybind_with_exec", line: "bindsym Mod4+t exec kitty", expected: &KeyBinding{ Mods: []string{"Mod4"}, Key: "t", Command: "exec kitty", Comment: "kitty", }, }, { name: "keybind_with_comment", line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher", expected: &KeyBinding{ Mods: []string{"Mod4"}, Key: "Space", Command: "exec dms ipc call spotlight toggle", Comment: "Open launcher", }, }, { name: "keybind_hidden", line: "bindsym Mod4+h exec secret # [hidden]", expected: nil, }, { name: "keybind_multiple_mods", line: "bindsym Mod4+Shift+e exit", expected: &KeyBinding{ Mods: []string{"Mod4", "Shift"}, Key: "e", Command: "exit", Comment: "Exit Sway", }, }, { name: "keybind_no_mods", line: "bindsym Print exec grim screenshot.png", expected: &KeyBinding{ Mods: []string{}, Key: "Print", Command: "exec grim screenshot.png", Comment: "grim screenshot.png", }, }, { name: "keybind_with_flags", line: "bindsym --release Mod4+x exec notify-send released", expected: &KeyBinding{ Mods: []string{"Mod4"}, Key: "x", Command: "exec notify-send released", Comment: "notify-send released", }, }, { name: "keybind_focus_direction", line: "bindsym Mod4+Left focus left", expected: &KeyBinding{ Mods: []string{"Mod4"}, Key: "Left", Command: "focus left", Comment: "Focus left", }, }, { name: "keybind_workspace", line: "bindsym Mod4+1 workspace number 1", expected: &KeyBinding{ Mods: []string{"Mod4"}, Key: "1", Command: "workspace number 1", Comment: "Workspace 1", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := NewParser() parser.contentLines = []string{tt.line} result := parser.getKeybindAtLine(0) if tt.expected == nil { if result != nil { t.Errorf("expected nil, got %+v", result) } return } if result == nil { t.Errorf("expected %+v, got nil", tt.expected) return } if result.Key != tt.expected.Key { t.Errorf("Key = %q, want %q", result.Key, tt.expected.Key) } if result.Command != tt.expected.Command { t.Errorf("Command = %q, want %q", result.Command, tt.expected.Command) } if result.Comment != tt.expected.Comment { t.Errorf("Comment = %q, want %q", result.Comment, tt.expected.Comment) } if len(result.Mods) != len(tt.expected.Mods) { t.Errorf("Mods length = %d, want %d", len(result.Mods), len(tt.expected.Mods)) } else { for i := range result.Mods { if result.Mods[i] != tt.expected.Mods[i] { t.Errorf("Mods[%d] = %q, want %q", i, result.Mods[i], tt.expected.Mods[i]) } } } }) } } func TestVariableExpansion(t *testing.T) { tmpDir := t.TempDir() configFile := filepath.Join(tmpDir, "config") content := `set $mod Mod4 set $term kitty set $menu rofi bindsym $mod+t exec $term bindsym $mod+d exec $menu ` if err := os.WriteFile(configFile, []byte(content), 0644); err != nil { t.Fatalf("Failed to write test config: %v", err) } section, err := ParseKeys(configFile) if err != nil { t.Fatalf("ParseKeys failed: %v", err) } if len(section.Keybinds) != 2 { t.Errorf("Expected 2 keybinds, got %d", len(section.Keybinds)) } if len(section.Keybinds) > 0 { if section.Keybinds[0].Mods[0] != "Mod4" { t.Errorf("Expected Mod4, got %q", section.Keybinds[0].Mods[0]) } if section.Keybinds[0].Command != "exec kitty" { t.Errorf("Expected 'exec kitty', got %q", section.Keybinds[0].Command) } } if len(section.Keybinds) > 1 { if section.Keybinds[1].Command != "exec rofi" { t.Errorf("Expected 'exec rofi', got %q", section.Keybinds[1].Command) } } } func TestParseKeysWithSections(t *testing.T) { tmpDir := t.TempDir() configFile := filepath.Join(tmpDir, "config") content := `set $mod Mod4 ##! Window Management bindsym $mod+q kill bindsym $mod+f fullscreen toggle ###! Focus bindsym $mod+Left focus left bindsym $mod+Right focus right ##! Applications bindsym $mod+t exec kitty # Terminal ` if err := os.WriteFile(configFile, []byte(content), 0644); err != nil { t.Fatalf("Failed to write test config: %v", err) } section, err := ParseKeys(tmpDir) if err != nil { t.Fatalf("ParseKeys failed: %v", err) } if len(section.Children) != 2 { t.Errorf("Expected 2 top-level sections, got %d", len(section.Children)) } if len(section.Children) >= 1 { windowMgmt := section.Children[0] if windowMgmt.Name != "Window Management" { t.Errorf("First section name = %q, want %q", windowMgmt.Name, "Window Management") } if len(windowMgmt.Keybinds) != 2 { t.Errorf("Window Management keybinds = %d, want 2", len(windowMgmt.Keybinds)) } if len(windowMgmt.Children) != 1 { t.Errorf("Window Management children = %d, want 1", len(windowMgmt.Children)) } else { focus := windowMgmt.Children[0] if focus.Name != "Focus" { t.Errorf("Focus section name = %q, want %q", focus.Name, "Focus") } if len(focus.Keybinds) != 2 { t.Errorf("Focus keybinds = %d, want 2", len(focus.Keybinds)) } } } if len(section.Children) >= 2 { apps := section.Children[1] if apps.Name != "Applications" { t.Errorf("Second section name = %q, want %q", apps.Name, "Applications") } if len(apps.Keybinds) != 1 { t.Errorf("Applications keybinds = %d, want 1", len(apps.Keybinds)) } if len(apps.Keybinds) > 0 && apps.Keybinds[0].Comment != "Terminal" { t.Errorf("Applications keybind comment = %q, want %q", apps.Keybinds[0].Comment, "Terminal") } } } func TestReadContentErrors(t *testing.T) { tests := []struct { name string path string }{ { name: "nonexistent_directory", path: "/nonexistent/path/that/does/not/exist", }, { name: "empty_directory", path: t.TempDir(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := ParseKeys(tt.path) if err == nil { t.Error("Expected error, got nil") } }) } } func TestReadContentWithTildeExpansion(t *testing.T) { homeDir, err := os.UserHomeDir() if err != nil { t.Skip("Cannot get home directory") } tmpSubdir := filepath.Join(homeDir, ".config", "test-sway-"+t.Name()) if err := os.MkdirAll(tmpSubdir, 0755); err != nil { t.Skip("Cannot create test directory in home") } defer os.RemoveAll(tmpSubdir) configFile := filepath.Join(tmpSubdir, "config") if err := os.WriteFile(configFile, []byte("bindsym Mod4+q kill\n"), 0644); err != nil { t.Fatalf("Failed to write test config: %v", err) } relPath, err := filepath.Rel(homeDir, tmpSubdir) if err != nil { t.Skip("Cannot create relative path") } parser := NewParser() tildePathMatch := "~/" + relPath err = parser.ReadContent(tildePathMatch) if err != nil { t.Errorf("ReadContent with tilde path failed: %v", err) } } func TestEmptyAndCommentLines(t *testing.T) { tmpDir := t.TempDir() configFile := filepath.Join(tmpDir, "config") content := ` # This is a comment bindsym Mod4+q kill # Another comment bindsym Mod4+t exec kitty ` if err := os.WriteFile(configFile, []byte(content), 0644); err != nil { t.Fatalf("Failed to write test config: %v", err) } section, err := ParseKeys(configFile) if err != nil { t.Fatalf("ParseKeys failed: %v", err) } if len(section.Keybinds) != 2 { t.Errorf("Expected 2 keybinds (comments ignored), got %d", len(section.Keybinds)) } } func TestRealWorldConfig(t *testing.T) { tmpDir := t.TempDir() configFile := filepath.Join(tmpDir, "config") content := `set $mod Mod4 set $term kitty ## Application Launchers bindsym $mod+t exec $term bindsym $mod+Space exec rofi ## Window Management bindsym $mod+q kill bindsym $mod+f fullscreen toggle ## Focus Navigation bindsym $mod+Left focus left bindsym $mod+Right focus right ## Workspace Navigation bindsym $mod+1 workspace number 1 bindsym $mod+2 workspace number 2 bindsym $mod+Shift+1 move container to workspace number 1 ` if err := os.WriteFile(configFile, []byte(content), 0644); err != nil { t.Fatalf("Failed to write test config: %v", err) } section, err := ParseKeys(configFile) if err != nil { t.Fatalf("ParseKeys failed: %v", err) } if len(section.Keybinds) < 9 { t.Errorf("Expected at least 9 keybinds, got %d", len(section.Keybinds)) } foundExec := false foundKill := false foundWorkspace := false for _, kb := range section.Keybinds { if kb.Command == "exec kitty" { foundExec = true } if kb.Command == "kill" { foundKill = true } if kb.Command == "workspace number 1" { foundWorkspace = true } } if !foundExec { t.Error("Did not find exec kitty keybind") } if !foundKill { t.Error("Did not find kill keybind") } if !foundWorkspace { t.Error("Did not find workspace 1 keybind") } } func TestIsMod(t *testing.T) { tests := []struct { input string expected bool }{ {"Mod4", true}, {"Shift", true}, {"Control", true}, {"Alt", true}, {"Super", true}, {"$mod", true}, {"Left", false}, {"q", false}, {"1", false}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := isMod(tt.input) if result != tt.expected { t.Errorf("isMod(%q) = %v, want %v", tt.input, result, tt.expected) } }) } }