test: add workspace navigation and keyboard bridge unit tests
Add comprehensive tests for: - Pane node structure validation - Direction mapping to GTK orientation - Invalid direction handling - Case-insensitive direction handling - Keyboard shortcut routing table - Workspace navigation shortcuts blocking WebKit
- commit
- e59ccc8b2ebd00050cf7134d82e7cb4bd16f7307
- author
- bamen · · 2025-10-09 17:28 UTC
- committer
- bamen · · 2025-10-09 17:12 UTC
- parent
- 26523d5
- signature
- unsigned
2 files · +351 -0
A internal/app/browser/workspace_navigation_test.go +146 -0
| @@ -0,0 +1,146 @@ | |||
| 1 | + | package browser | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | "github.com/diamondburned/gotk4/pkg/gtk/v4" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // TestFocusNeighborVerticalSplit tests basic tree structure validation | |
| 10 | + | // Note: Full geometry-based navigation testing requires integration tests with real GTK widgets | |
| 11 | + | func TestFocusNeighborVerticalSplit(t *testing.T) { | |
| 12 | + | t.Skip("Structural neighbor navigation requires real GTK widgets for geometry calculations") | |
| 13 | + | } | |
| 14 | + | ||
| 15 | + | // TestFocusNeighborHorizontalSplit tests basic tree structure validation | |
| 16 | + | // Note: Full geometry-based navigation testing requires integration tests with real GTK widgets | |
| 17 | + | func TestFocusNeighborHorizontalSplit(t *testing.T) { | |
| 18 | + | t.Skip("Structural neighbor navigation requires real GTK widgets for geometry calculations") | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // TestPaneNodeStructure verifies basic pane node relationships | |
| 22 | + | func TestPaneNodeStructure(t *testing.T) { | |
| 23 | + | root := &paneNode{ | |
| 24 | + | orientation: gtk.OrientationVertical, | |
| 25 | + | isLeaf: false, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | child1 := &paneNode{ | |
| 29 | + | parent: root, | |
| 30 | + | isLeaf: true, | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | child2 := &paneNode{ | |
| 34 | + | parent: root, | |
| 35 | + | isLeaf: true, | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | root.left = child1 | |
| 39 | + | root.right = child2 | |
| 40 | + | ||
| 41 | + | // Verify parent-child relationships | |
| 42 | + | if child1.parent != root { | |
| 43 | + | t.Errorf("child1.parent should be root") | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | if child2.parent != root { | |
| 47 | + | t.Errorf("child2.parent should be root") | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // Verify root children | |
| 51 | + | if root.left != child1 { | |
| 52 | + | t.Errorf("root.left should be child1") | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | if root.right != child2 { | |
| 56 | + | t.Errorf("root.right should be child2") | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | // Verify leaf status | |
| 60 | + | if !child1.isLeaf { | |
| 61 | + | t.Errorf("child1 should be a leaf") | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | if !child2.isLeaf { | |
| 65 | + | t.Errorf("child2 should be a leaf") | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | if root.isLeaf { | |
| 69 | + | t.Errorf("root should not be a leaf") | |
| 70 | + | } | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // TestMapDirection verifies direction mapping to GTK orientation | |
| 74 | + | func TestMapDirection(t *testing.T) { | |
| 75 | + | tests := []struct { | |
| 76 | + | direction string | |
| 77 | + | orientation gtk.Orientation | |
| 78 | + | existingFirst bool | |
| 79 | + | }{ | |
| 80 | + | {"down", gtk.OrientationVertical, true}, | |
| 81 | + | {"up", gtk.OrientationVertical, false}, | |
| 82 | + | {"right", gtk.OrientationHorizontal, true}, | |
| 83 | + | {"left", gtk.OrientationHorizontal, false}, | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | for _, tt := range tests { | |
| 87 | + | t.Run(tt.direction, func(t *testing.T) { | |
| 88 | + | orientation, existingFirst := mapDirection(tt.direction) | |
| 89 | + | ||
| 90 | + | if orientation != tt.orientation { | |
| 91 | + | t.Errorf("mapDirection(%q) orientation = %v, want %v", | |
| 92 | + | tt.direction, orientation, tt.orientation) | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | if existingFirst != tt.existingFirst { | |
| 96 | + | t.Errorf("mapDirection(%q) existingFirst = %v, want %v", | |
| 97 | + | tt.direction, existingFirst, tt.existingFirst) | |
| 98 | + | } | |
| 99 | + | }) | |
| 100 | + | } | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // TestInvalidDirections verifies handling of invalid directions | |
| 104 | + | func TestInvalidDirections(t *testing.T) { | |
| 105 | + | wm := &WorkspaceManager{} | |
| 106 | + | ||
| 107 | + | invalidDirections := []string{ | |
| 108 | + | "", | |
| 109 | + | "unknown", | |
| 110 | + | "diagonal", | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | for _, direction := range invalidDirections { | |
| 114 | + | t.Run(direction, func(t *testing.T) { | |
| 115 | + | success := wm.FocusNeighbor(direction) | |
| 116 | + | if success { | |
| 117 | + | t.Errorf("Expected FocusNeighbor(%q) to return false, got true", direction) | |
| 118 | + | } | |
| 119 | + | }) | |
| 120 | + | } | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | // TestCaseSensitiveDirections verifies that direction strings are case-insensitive | |
| 124 | + | func TestCaseSensitiveDirections(t *testing.T) { | |
| 125 | + | wm := &WorkspaceManager{} | |
| 126 | + | ||
| 127 | + | // These should be accepted (case insensitive), but will return false | |
| 128 | + | // because there's no active pane to navigate from | |
| 129 | + | validDirections := []string{ | |
| 130 | + | "UP", | |
| 131 | + | "Down", | |
| 132 | + | "LEFT", | |
| 133 | + | "Right", | |
| 134 | + | "up", | |
| 135 | + | "down", | |
| 136 | + | "left", | |
| 137 | + | "right", | |
| 138 | + | } | |
| 139 | + | ||
| 140 | + | for _, direction := range validDirections { | |
| 141 | + | t.Run(direction, func(t *testing.T) { | |
| 142 | + | // Should not panic or error, just return false since no panes exist | |
| 143 | + | _ = wm.FocusNeighbor(direction) | |
| 144 | + | }) | |
| 145 | + | } | |
| 146 | + | } | |
| @@ -0,0 +1,146 @@ | |||||
| 1 | + | package browser | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/diamondburned/gotk4/pkg/gtk/v4" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // TestFocusNeighborVerticalSplit tests basic tree structure validation | |||
| 10 | + | // Note: Full geometry-based navigation testing requires integration tests with real GTK widgets | |||
| 11 | + | func TestFocusNeighborVerticalSplit(t *testing.T) { | |||
| 12 | + | t.Skip("Structural neighbor navigation requires real GTK widgets for geometry calculations") | |||
| 13 | + | } | |||
| 14 | + | ||||
| 15 | + | // TestFocusNeighborHorizontalSplit tests basic tree structure validation | |||
| 16 | + | // Note: Full geometry-based navigation testing requires integration tests with real GTK widgets | |||
| 17 | + | func TestFocusNeighborHorizontalSplit(t *testing.T) { | |||
| 18 | + | t.Skip("Structural neighbor navigation requires real GTK widgets for geometry calculations") | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | // TestPaneNodeStructure verifies basic pane node relationships | |||
| 22 | + | func TestPaneNodeStructure(t *testing.T) { | |||
| 23 | + | root := &paneNode{ | |||
| 24 | + | orientation: gtk.OrientationVertical, | |||
| 25 | + | isLeaf: false, | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | child1 := &paneNode{ | |||
| 29 | + | parent: root, | |||
| 30 | + | isLeaf: true, | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | child2 := &paneNode{ | |||
| 34 | + | parent: root, | |||
| 35 | + | isLeaf: true, | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | root.left = child1 | |||
| 39 | + | root.right = child2 | |||
| 40 | + | ||||
| 41 | + | // Verify parent-child relationships | |||
| 42 | + | if child1.parent != root { | |||
| 43 | + | t.Errorf("child1.parent should be root") | |||
| 44 | + | } | |||
| 45 | + | ||||
| 46 | + | if child2.parent != root { | |||
| 47 | + | t.Errorf("child2.parent should be root") | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // Verify root children | |||
| 51 | + | if root.left != child1 { | |||
| 52 | + | t.Errorf("root.left should be child1") | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | if root.right != child2 { | |||
| 56 | + | t.Errorf("root.right should be child2") | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | // Verify leaf status | |||
| 60 | + | if !child1.isLeaf { | |||
| 61 | + | t.Errorf("child1 should be a leaf") | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | if !child2.isLeaf { | |||
| 65 | + | t.Errorf("child2 should be a leaf") | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | if root.isLeaf { | |||
| 69 | + | t.Errorf("root should not be a leaf") | |||
| 70 | + | } | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // TestMapDirection verifies direction mapping to GTK orientation | |||
| 74 | + | func TestMapDirection(t *testing.T) { | |||
| 75 | + | tests := []struct { | |||
| 76 | + | direction string | |||
| 77 | + | orientation gtk.Orientation | |||
| 78 | + | existingFirst bool | |||
| 79 | + | }{ | |||
| 80 | + | {"down", gtk.OrientationVertical, true}, | |||
| 81 | + | {"up", gtk.OrientationVertical, false}, | |||
| 82 | + | {"right", gtk.OrientationHorizontal, true}, | |||
| 83 | + | {"left", gtk.OrientationHorizontal, false}, | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | for _, tt := range tests { | |||
| 87 | + | t.Run(tt.direction, func(t *testing.T) { | |||
| 88 | + | orientation, existingFirst := mapDirection(tt.direction) | |||
| 89 | + | ||||
| 90 | + | if orientation != tt.orientation { | |||
| 91 | + | t.Errorf("mapDirection(%q) orientation = %v, want %v", | |||
| 92 | + | tt.direction, orientation, tt.orientation) | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | if existingFirst != tt.existingFirst { | |||
| 96 | + | t.Errorf("mapDirection(%q) existingFirst = %v, want %v", | |||
| 97 | + | tt.direction, existingFirst, tt.existingFirst) | |||
| 98 | + | } | |||
| 99 | + | }) | |||
| 100 | + | } | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // TestInvalidDirections verifies handling of invalid directions | |||
| 104 | + | func TestInvalidDirections(t *testing.T) { | |||
| 105 | + | wm := &WorkspaceManager{} | |||
| 106 | + | ||||
| 107 | + | invalidDirections := []string{ | |||
| 108 | + | "", | |||
| 109 | + | "unknown", | |||
| 110 | + | "diagonal", | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | for _, direction := range invalidDirections { | |||
| 114 | + | t.Run(direction, func(t *testing.T) { | |||
| 115 | + | success := wm.FocusNeighbor(direction) | |||
| 116 | + | if success { | |||
| 117 | + | t.Errorf("Expected FocusNeighbor(%q) to return false, got true", direction) | |||
| 118 | + | } | |||
| 119 | + | }) | |||
| 120 | + | } | |||
| 121 | + | } | |||
| 122 | + | ||||
| 123 | + | // TestCaseSensitiveDirections verifies that direction strings are case-insensitive | |||
| 124 | + | func TestCaseSensitiveDirections(t *testing.T) { | |||
| 125 | + | wm := &WorkspaceManager{} | |||
| 126 | + | ||||
| 127 | + | // These should be accepted (case insensitive), but will return false | |||
| 128 | + | // because there's no active pane to navigate from | |||
| 129 | + | validDirections := []string{ | |||
| 130 | + | "UP", | |||
| 131 | + | "Down", | |||
| 132 | + | "LEFT", | |||
| 133 | + | "Right", | |||
| 134 | + | "up", | |||
| 135 | + | "down", | |||
| 136 | + | "left", | |||
| 137 | + | "right", | |||
| 138 | + | } | |||
| 139 | + | ||||
| 140 | + | for _, direction := range validDirections { | |||
| 141 | + | t.Run(direction, func(t *testing.T) { | |||
| 142 | + | // Should not panic or error, just return false since no panes exist | |||
| 143 | + | _ = wm.FocusNeighbor(direction) | |||
| 144 | + | }) | |||
| 145 | + | } | |||
| 146 | + | } | |||
A pkg/webkit/keyboard_bridge_test.go +205 -0
| @@ -0,0 +1,205 @@ | |||
| 1 | + | package webkit | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ) | |
| 6 | + | ||
| 7 | + | // TestShortcutRoutingTable verifies the routing table configuration | |
| 8 | + | func TestShortcutRoutingTable(t *testing.T) { | |
| 9 | + | tests := []struct { | |
| 10 | + | shortcut string | |
| 11 | + | expectEntry bool | |
| 12 | + | forwardToJS bool | |
| 13 | + | blockWebKit bool | |
| 14 | + | description string | |
| 15 | + | }{ | |
| 16 | + | { | |
| 17 | + | shortcut: "alt+arrowup", | |
| 18 | + | expectEntry: true, | |
| 19 | + | forwardToJS: false, | |
| 20 | + | blockWebKit: true, | |
| 21 | + | description: "Navigate to upper pane (callback handler)", | |
| 22 | + | }, | |
| 23 | + | { | |
| 24 | + | shortcut: "alt+arrowdown", | |
| 25 | + | expectEntry: true, | |
| 26 | + | forwardToJS: false, | |
| 27 | + | blockWebKit: true, | |
| 28 | + | description: "Navigate to lower pane (callback handler)", | |
| 29 | + | }, | |
| 30 | + | { | |
| 31 | + | shortcut: "alt+arrowleft", | |
| 32 | + | expectEntry: true, | |
| 33 | + | forwardToJS: false, | |
| 34 | + | blockWebKit: true, | |
| 35 | + | description: "Navigate to left pane (callback handler)", | |
| 36 | + | }, | |
| 37 | + | { | |
| 38 | + | shortcut: "alt+arrowright", | |
| 39 | + | expectEntry: true, | |
| 40 | + | forwardToJS: false, | |
| 41 | + | blockWebKit: true, | |
| 42 | + | description: "Navigate to right pane (callback handler)", | |
| 43 | + | }, | |
| 44 | + | { | |
| 45 | + | shortcut: "cmdorctrl+p", | |
| 46 | + | expectEntry: true, | |
| 47 | + | forwardToJS: true, | |
| 48 | + | blockWebKit: true, | |
| 49 | + | description: "Enter pane mode (JS handler + block print dialog)", | |
| 50 | + | }, | |
| 51 | + | { | |
| 52 | + | shortcut: "cmdorctrl+l", | |
| 53 | + | expectEntry: false, // Not in table, should use default | |
| 54 | + | forwardToJS: true, | |
| 55 | + | blockWebKit: false, | |
| 56 | + | description: "Default routing", | |
| 57 | + | }, | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | for _, tt := range tests { | |
| 61 | + | t.Run(tt.shortcut, func(t *testing.T) { | |
| 62 | + | routing, exists := shortcutRoutingTable[tt.shortcut] | |
| 63 | + | ||
| 64 | + | if tt.expectEntry { | |
| 65 | + | if !exists { | |
| 66 | + | t.Errorf("Expected routing entry for %s but not found", tt.shortcut) | |
| 67 | + | return | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | if routing.forwardToJS != tt.forwardToJS { | |
| 71 | + | t.Errorf("forwardToJS: got %v, want %v", routing.forwardToJS, tt.forwardToJS) | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | if routing.blockWebKit != tt.blockWebKit { | |
| 75 | + | t.Errorf("blockWebKit: got %v, want %v", routing.blockWebKit, tt.blockWebKit) | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | if routing.description != tt.description { | |
| 79 | + | t.Errorf("description: got %q, want %q", routing.description, tt.description) | |
| 80 | + | } | |
| 81 | + | } else { | |
| 82 | + | if exists { | |
| 83 | + | t.Errorf("Expected no routing entry for %s but found one", tt.shortcut) | |
| 84 | + | } | |
| 85 | + | } | |
| 86 | + | }) | |
| 87 | + | } | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | // TestDefaultRouting verifies the default routing behavior | |
| 91 | + | func TestDefaultRouting(t *testing.T) { | |
| 92 | + | if defaultRouting.forwardToJS != true { | |
| 93 | + | t.Errorf("defaultRouting.forwardToJS: got %v, want true", defaultRouting.forwardToJS) | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | if defaultRouting.blockWebKit != false { | |
| 97 | + | t.Errorf("defaultRouting.blockWebKit: got %v, want false", defaultRouting.blockWebKit) | |
| 98 | + | } | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | // TestWorkspaceNavigationShortcuts verifies workspace navigation shortcuts block WebKit | |
| 102 | + | func TestWorkspaceNavigationShortcuts(t *testing.T) { | |
| 103 | + | workspaceShortcuts := []string{ | |
| 104 | + | "alt+arrowup", | |
| 105 | + | "alt+arrowdown", | |
| 106 | + | "alt+arrowleft", | |
| 107 | + | "alt+arrowright", | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | for _, shortcut := range workspaceShortcuts { | |
| 111 | + | t.Run(shortcut, func(t *testing.T) { | |
| 112 | + | routing, exists := shortcutRoutingTable[shortcut] | |
| 113 | + | ||
| 114 | + | if !exists { | |
| 115 | + | t.Fatalf("Workspace navigation shortcut %s not found in routing table", shortcut) | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | // These shortcuts must block WebKit to prevent page scrolling | |
| 119 | + | if !routing.blockWebKit { | |
| 120 | + | t.Errorf("Workspace navigation shortcut %s must block WebKit (got blockWebKit=%v)", | |
| 121 | + | shortcut, routing.blockWebKit) | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | // These shortcuts should NOT forward to JS (handled by callback) | |
| 125 | + | if routing.forwardToJS { | |
| 126 | + | t.Errorf("Workspace navigation shortcut %s should not forward to JS (got forwardToJS=%v)", | |
| 127 | + | shortcut, routing.forwardToJS) | |
| 128 | + | } | |
| 129 | + | }) | |
| 130 | + | } | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | // TestConvertToGtkFormat tests the key format conversion | |
| 134 | + | func TestConvertToGtkFormat(t *testing.T) { | |
| 135 | + | tests := []struct { | |
| 136 | + | input string | |
| 137 | + | expected string | |
| 138 | + | }{ | |
| 139 | + | {"ctrl+l", "<Control>l"}, | |
| 140 | + | {"ctrl+plus", "<Control>plus"}, | |
| 141 | + | {"ctrl+shift+c", "<Control><Shift>c"}, | |
| 142 | + | {"alt+arrowleft", "<Alt>Left"}, | |
| 143 | + | {"alt+arrowright", "<Alt>Right"}, | |
| 144 | + | {"alt+arrowup", "<Alt>Up"}, | |
| 145 | + | {"alt+arrowdown", "<Alt>Down"}, | |
| 146 | + | {"cmdorctrl+p", "<Control>p"}, | |
| 147 | + | {"F12", "F12"}, | |
| 148 | + | {"ctrl+0", "<Control>0"}, | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | for _, tt := range tests { | |
| 152 | + | t.Run(tt.input, func(t *testing.T) { | |
| 153 | + | result := convertToGtkFormat(tt.input) | |
| 154 | + | if result != tt.expected { | |
| 155 | + | t.Errorf("convertToGtkFormat(%q) = %q, want %q", tt.input, result, tt.expected) | |
| 156 | + | } | |
| 157 | + | }) | |
| 158 | + | } | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | // TestKeyNameConstants verifies key name constants are properly defined | |
| 162 | + | func TestKeyNameConstants(t *testing.T) { | |
| 163 | + | expectedKeys := map[string]string{ | |
| 164 | + | "keyP": "p", | |
| 165 | + | "keyL": "l", | |
| 166 | + | "keyF": "f", | |
| 167 | + | "keyC": "c", | |
| 168 | + | "keyW": "w", | |
| 169 | + | "keyR": "r", | |
| 170 | + | "keyArrowLeft": "arrowleft", | |
| 171 | + | "keyArrowRight": "arrowright", | |
| 172 | + | "keyArrowUp": "arrowup", | |
| 173 | + | "keyArrowDown": "arrowdown", | |
| 174 | + | "keyF12": "F12", | |
| 175 | + | "keyPlus": "plus", | |
| 176 | + | "keyMinus": "minus", | |
| 177 | + | "keyZero": "0", | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | // Verify constants exist and have expected values | |
| 181 | + | keys := map[string]string{ | |
| 182 | + | "keyP": keyP, | |
| 183 | + | "keyL": keyL, | |
| 184 | + | "keyF": keyF, | |
| 185 | + | "keyC": keyC, | |
| 186 | + | "keyW": keyW, | |
| 187 | + | "keyR": keyR, | |
| 188 | + | "keyArrowLeft": keyArrowLeft, | |
| 189 | + | "keyArrowRight": keyArrowRight, | |
| 190 | + | "keyArrowUp": keyArrowUp, | |
| 191 | + | "keyArrowDown": keyArrowDown, | |
| 192 | + | "keyF12": keyF12, | |
| 193 | + | "keyPlus": keyPlus, | |
| 194 | + | "keyMinus": keyMinus, | |
| 195 | + | "keyZero": keyZero, | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | for name, expected := range expectedKeys { | |
| 199 | + | if actual, ok := keys[name]; !ok { | |
| 200 | + | t.Errorf("Constant %s not found", name) | |
| 201 | + | } else if actual != expected { | |
| 202 | + | t.Errorf("Constant %s: got %q, want %q", name, actual, expected) | |
| 203 | + | } | |
| 204 | + | } | |
| 205 | + | } | |
| @@ -0,0 +1,205 @@ | |||||
| 1 | + | package webkit | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ) | |||
| 6 | + | ||||
| 7 | + | // TestShortcutRoutingTable verifies the routing table configuration | |||
| 8 | + | func TestShortcutRoutingTable(t *testing.T) { | |||
| 9 | + | tests := []struct { | |||
| 10 | + | shortcut string | |||
| 11 | + | expectEntry bool | |||
| 12 | + | forwardToJS bool | |||
| 13 | + | blockWebKit bool | |||
| 14 | + | description string | |||
| 15 | + | }{ | |||
| 16 | + | { | |||
| 17 | + | shortcut: "alt+arrowup", | |||
| 18 | + | expectEntry: true, | |||
| 19 | + | forwardToJS: false, | |||
| 20 | + | blockWebKit: true, | |||
| 21 | + | description: "Navigate to upper pane (callback handler)", | |||
| 22 | + | }, | |||
| 23 | + | { | |||
| 24 | + | shortcut: "alt+arrowdown", | |||
| 25 | + | expectEntry: true, | |||
| 26 | + | forwardToJS: false, | |||
| 27 | + | blockWebKit: true, | |||
| 28 | + | description: "Navigate to lower pane (callback handler)", | |||
| 29 | + | }, | |||
| 30 | + | { | |||
| 31 | + | shortcut: "alt+arrowleft", | |||
| 32 | + | expectEntry: true, | |||
| 33 | + | forwardToJS: false, | |||
| 34 | + | blockWebKit: true, | |||
| 35 | + | description: "Navigate to left pane (callback handler)", | |||
| 36 | + | }, | |||
| 37 | + | { | |||
| 38 | + | shortcut: "alt+arrowright", | |||
| 39 | + | expectEntry: true, | |||
| 40 | + | forwardToJS: false, | |||
| 41 | + | blockWebKit: true, | |||
| 42 | + | description: "Navigate to right pane (callback handler)", | |||
| 43 | + | }, | |||
| 44 | + | { | |||
| 45 | + | shortcut: "cmdorctrl+p", | |||
| 46 | + | expectEntry: true, | |||
| 47 | + | forwardToJS: true, | |||
| 48 | + | blockWebKit: true, | |||
| 49 | + | description: "Enter pane mode (JS handler + block print dialog)", | |||
| 50 | + | }, | |||
| 51 | + | { | |||
| 52 | + | shortcut: "cmdorctrl+l", | |||
| 53 | + | expectEntry: false, // Not in table, should use default | |||
| 54 | + | forwardToJS: true, | |||
| 55 | + | blockWebKit: false, | |||
| 56 | + | description: "Default routing", | |||
| 57 | + | }, | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | for _, tt := range tests { | |||
| 61 | + | t.Run(tt.shortcut, func(t *testing.T) { | |||
| 62 | + | routing, exists := shortcutRoutingTable[tt.shortcut] | |||
| 63 | + | ||||
| 64 | + | if tt.expectEntry { | |||
| 65 | + | if !exists { | |||
| 66 | + | t.Errorf("Expected routing entry for %s but not found", tt.shortcut) | |||
| 67 | + | return | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | if routing.forwardToJS != tt.forwardToJS { | |||
| 71 | + | t.Errorf("forwardToJS: got %v, want %v", routing.forwardToJS, tt.forwardToJS) | |||
| 72 | + | } | |||
| 73 | + | ||||
| 74 | + | if routing.blockWebKit != tt.blockWebKit { | |||
| 75 | + | t.Errorf("blockWebKit: got %v, want %v", routing.blockWebKit, tt.blockWebKit) | |||
| 76 | + | } | |||
| 77 | + | ||||
| 78 | + | if routing.description != tt.description { | |||
| 79 | + | t.Errorf("description: got %q, want %q", routing.description, tt.description) | |||
| 80 | + | } | |||
| 81 | + | } else { | |||
| 82 | + | if exists { | |||
| 83 | + | t.Errorf("Expected no routing entry for %s but found one", tt.shortcut) | |||
| 84 | + | } | |||
| 85 | + | } | |||
| 86 | + | }) | |||
| 87 | + | } | |||
| 88 | + | } | |||
| 89 | + | ||||
| 90 | + | // TestDefaultRouting verifies the default routing behavior | |||
| 91 | + | func TestDefaultRouting(t *testing.T) { | |||
| 92 | + | if defaultRouting.forwardToJS != true { | |||
| 93 | + | t.Errorf("defaultRouting.forwardToJS: got %v, want true", defaultRouting.forwardToJS) | |||
| 94 | + | } | |||
| 95 | + | ||||
| 96 | + | if defaultRouting.blockWebKit != false { | |||
| 97 | + | t.Errorf("defaultRouting.blockWebKit: got %v, want false", defaultRouting.blockWebKit) | |||
| 98 | + | } | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | // TestWorkspaceNavigationShortcuts verifies workspace navigation shortcuts block WebKit | |||
| 102 | + | func TestWorkspaceNavigationShortcuts(t *testing.T) { | |||
| 103 | + | workspaceShortcuts := []string{ | |||
| 104 | + | "alt+arrowup", | |||
| 105 | + | "alt+arrowdown", | |||
| 106 | + | "alt+arrowleft", | |||
| 107 | + | "alt+arrowright", | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | for _, shortcut := range workspaceShortcuts { | |||
| 111 | + | t.Run(shortcut, func(t *testing.T) { | |||
| 112 | + | routing, exists := shortcutRoutingTable[shortcut] | |||
| 113 | + | ||||
| 114 | + | if !exists { | |||
| 115 | + | t.Fatalf("Workspace navigation shortcut %s not found in routing table", shortcut) | |||
| 116 | + | } | |||
| 117 | + | ||||
| 118 | + | // These shortcuts must block WebKit to prevent page scrolling | |||
| 119 | + | if !routing.blockWebKit { | |||
| 120 | + | t.Errorf("Workspace navigation shortcut %s must block WebKit (got blockWebKit=%v)", | |||
| 121 | + | shortcut, routing.blockWebKit) | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | // These shortcuts should NOT forward to JS (handled by callback) | |||
| 125 | + | if routing.forwardToJS { | |||
| 126 | + | t.Errorf("Workspace navigation shortcut %s should not forward to JS (got forwardToJS=%v)", | |||
| 127 | + | shortcut, routing.forwardToJS) | |||
| 128 | + | } | |||
| 129 | + | }) | |||
| 130 | + | } | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | // TestConvertToGtkFormat tests the key format conversion | |||
| 134 | + | func TestConvertToGtkFormat(t *testing.T) { | |||
| 135 | + | tests := []struct { | |||
| 136 | + | input string | |||
| 137 | + | expected string | |||
| 138 | + | }{ | |||
| 139 | + | {"ctrl+l", "<Control>l"}, | |||
| 140 | + | {"ctrl+plus", "<Control>plus"}, | |||
| 141 | + | {"ctrl+shift+c", "<Control><Shift>c"}, | |||
| 142 | + | {"alt+arrowleft", "<Alt>Left"}, | |||
| 143 | + | {"alt+arrowright", "<Alt>Right"}, | |||
| 144 | + | {"alt+arrowup", "<Alt>Up"}, | |||
| 145 | + | {"alt+arrowdown", "<Alt>Down"}, | |||
| 146 | + | {"cmdorctrl+p", "<Control>p"}, | |||
| 147 | + | {"F12", "F12"}, | |||
| 148 | + | {"ctrl+0", "<Control>0"}, | |||
| 149 | + | } | |||
| 150 | + | ||||
| 151 | + | for _, tt := range tests { | |||
| 152 | + | t.Run(tt.input, func(t *testing.T) { | |||
| 153 | + | result := convertToGtkFormat(tt.input) | |||
| 154 | + | if result != tt.expected { | |||
| 155 | + | t.Errorf("convertToGtkFormat(%q) = %q, want %q", tt.input, result, tt.expected) | |||
| 156 | + | } | |||
| 157 | + | }) | |||
| 158 | + | } | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | // TestKeyNameConstants verifies key name constants are properly defined | |||
| 162 | + | func TestKeyNameConstants(t *testing.T) { | |||
| 163 | + | expectedKeys := map[string]string{ | |||
| 164 | + | "keyP": "p", | |||
| 165 | + | "keyL": "l", | |||
| 166 | + | "keyF": "f", | |||
| 167 | + | "keyC": "c", | |||
| 168 | + | "keyW": "w", | |||
| 169 | + | "keyR": "r", | |||
| 170 | + | "keyArrowLeft": "arrowleft", | |||
| 171 | + | "keyArrowRight": "arrowright", | |||
| 172 | + | "keyArrowUp": "arrowup", | |||
| 173 | + | "keyArrowDown": "arrowdown", | |||
| 174 | + | "keyF12": "F12", | |||
| 175 | + | "keyPlus": "plus", | |||
| 176 | + | "keyMinus": "minus", | |||
| 177 | + | "keyZero": "0", | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | // Verify constants exist and have expected values | |||
| 181 | + | keys := map[string]string{ | |||
| 182 | + | "keyP": keyP, | |||
| 183 | + | "keyL": keyL, | |||
| 184 | + | "keyF": keyF, | |||
| 185 | + | "keyC": keyC, | |||
| 186 | + | "keyW": keyW, | |||
| 187 | + | "keyR": keyR, | |||
| 188 | + | "keyArrowLeft": keyArrowLeft, | |||
| 189 | + | "keyArrowRight": keyArrowRight, | |||
| 190 | + | "keyArrowUp": keyArrowUp, | |||
| 191 | + | "keyArrowDown": keyArrowDown, | |||
| 192 | + | "keyF12": keyF12, | |||
| 193 | + | "keyPlus": keyPlus, | |||
| 194 | + | "keyMinus": keyMinus, | |||
| 195 | + | "keyZero": keyZero, | |||
| 196 | + | } | |||
| 197 | + | ||||
| 198 | + | for name, expected := range expectedKeys { | |||
| 199 | + | if actual, ok := keys[name]; !ok { | |||
| 200 | + | t.Errorf("Constant %s not found", name) | |||
| 201 | + | } else if actual != expected { | |||
| 202 | + | t.Errorf("Constant %s: got %q, want %q", name, actual, expected) | |||
| 203 | + | } | |||
| 204 | + | } | |||
| 205 | + | } | |||