Forge
csharpdeeb25a2
1using Avalonia.Headless.XUnit;
2using Avalonia.Input;
3using Avalonia.Threading;
4using CascadeIDE.Services;
5using CascadeIDE.ViewModels;
6using CascadeIDE.Views;
7using Xunit;
8
9namespace CascadeIDE.Tests;
10
11/// <summary>
12/// Регрессия: tunnel <see cref="MainWindowHotkeyService.TryHandleTunnelKeyDownForMainVm"/> — Ctrl+Q ↔ палитра, Esc ↔ закрыть.
13/// Map подменяется явно, чтобы не зависеть от пользовательского overlay в <c>%LocalAppData%\CascadeIDE\hotkeys.toml</c>.
14/// </summary>
15public sealed class MainWindowHotkeyServiceTests
16{
17 private static Dictionary<string, string> MinimalTogglePaletteMap() =>
18 new(StringComparer.OrdinalIgnoreCase) { ["toggle_command_palette"] = "Ctrl+Q" };
19
20 [Fact]
21 public void BundledHotkeysToml_FileNextToTestAssembly_DefinesToggleCommandPalette()
22 {
23 var path = Path.Combine(AppContext.BaseDirectory, "Hotkeys", "hotkeys.toml");
24 Assert.True(File.Exists(path), $"Ожидался шипнутый hotkeys.toml: {path}");
25 var text = File.ReadAllText(path);
26 Assert.Contains("toggle_command_palette", text, StringComparison.OrdinalIgnoreCase);
27 Assert.Contains("Ctrl+Q", text, StringComparison.OrdinalIgnoreCase);
28 }
29
30 [Fact]
31 public void TryHandleTunnelKeyDownForMainVm_CtrlQ_TogglesCommandPaletteOpen()
32 {
33 MainWindowHotkeyService.ReplaceMergedMapForTests(MinimalTogglePaletteMap());
34 try
35 {
36 var vm = new MainWindowViewModel();
37 Assert.False(vm.IsCommandPaletteOpen);
38
39 var e = new KeyEventArgs
40 {
41 RoutedEvent = InputElement.KeyDownEvent,
42 Key = Key.Q,
43 KeyModifiers = KeyModifiers.Control,
44 PhysicalKey = PhysicalKey.Q
45 };
46
47 var handled = MainWindowHotkeyService.TryHandleTunnelKeyDownForMainVm(e, vm);
48 Assert.True(handled);
49 Assert.True(e.Handled);
50 Assert.True(vm.IsCommandPaletteOpen);
51
52 var e2 = new KeyEventArgs
53 {
54 RoutedEvent = InputElement.KeyDownEvent,
55 Key = Key.Q,
56 KeyModifiers = KeyModifiers.Control,
57 PhysicalKey = PhysicalKey.Q
58 };
59 Assert.True(MainWindowHotkeyService.TryHandleTunnelKeyDownForMainVm(e2, vm));
60 Assert.False(vm.IsCommandPaletteOpen);
61 }
62 finally
63 {
64 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
65 }
66 }
67
68 /// <summary>Как в <see cref="KeyGestureChordMatchingTests"/>: нелатинская раскладка даёт другой <see cref="KeyEventArgs.Key"/>.</summary>
69 [Fact]
70 public void TryHandleTunnelKeyDownForMainVm_CtrlQ_PhysicalQ_VirtualMismatch_StillOpensPalette()
71 {
72 MainWindowHotkeyService.ReplaceMergedMapForTests(MinimalTogglePaletteMap());
73 try
74 {
75 var vm = new MainWindowViewModel();
76 var e = new KeyEventArgs
77 {
78 RoutedEvent = InputElement.KeyDownEvent,
79 Key = Key.W,
80 KeyModifiers = KeyModifiers.Control,
81 PhysicalKey = PhysicalKey.Q
82 };
83
84 Assert.True(MainWindowHotkeyService.TryHandleTunnelKeyDownForMainVm(e, vm));
85 Assert.True(vm.IsCommandPaletteOpen);
86 }
87 finally
88 {
89 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
90 }
91 }
92
93 [Fact]
94 public void TryHandleTunnelKeyDownForMainVm_Esc_ClosesPalette_WhenOpen()
95 {
96 MainWindowHotkeyService.ReplaceMergedMapForTests(MinimalTogglePaletteMap());
97 try
98 {
99 var vm = new MainWindowViewModel { IsCommandPaletteOpen = true };
100
101 var e = new KeyEventArgs
102 {
103 RoutedEvent = InputElement.KeyDownEvent,
104 Key = Key.Escape,
105 KeyModifiers = KeyModifiers.None,
106 PhysicalKey = PhysicalKey.Escape
107 };
108
109 Assert.True(MainWindowHotkeyService.TryHandleTunnelKeyDownForMainVm(e, vm));
110 Assert.True(e.Handled);
111 Assert.False(vm.IsCommandPaletteOpen);
112 }
113 finally
114 {
115 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
116 }
117 }
118
119 [Fact]
120 public void TryHandleTunnelKeyDownFromWindow_CtrlK_StartsCascadeChord_EvenWhenHandledAlready()
121 {
122 MainWindowHotkeyService.ReplaceMergedMapForTests(
123 new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
124 {
125 [CascadeChordHotkey.TomlKey] = "Ctrl+K"
126 });
127 try
128 {
129 var vm = new MainWindowViewModel();
130 var e = new KeyEventArgs
131 {
132 RoutedEvent = InputElement.KeyDownEvent,
133 Key = Key.K,
134 KeyModifiers = KeyModifiers.Control,
135 PhysicalKey = PhysicalKey.K,
136 Handled = true
137 };
138
139 MainWindowHotkeyService.TryHandleTunnelKeyDownFromWindow("TestWindow", e, vm);
140
141 Assert.True(vm.IsCascadeChordOverlayVisible);
142 Assert.Equal("", vm.CascadeChordOverlayInputText);
143 }
144 finally
145 {
146 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
147 }
148 }
149
150 [AvaloniaFact]
151 public async Task TryHandleTunnelKeyDownForMainVm_CtrlShiftO_ExecutesRegistryCommand_WhenPaletteOpen()
152 {
153 MainWindowHotkeyService.ReplaceMergedMapForTests(
154 new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
155 {
156 ["open_solution_dialog"] = "Ctrl+Shift+O"
157 });
158
159 try
160 {
161 var vm = new MainWindowViewModel
162 {
163 IsCommandPaletteOpen = true
164 };
165
166 var requested = false;
167 vm.RequestOpenSolution = () => requested = true;
168
169 var e = new KeyEventArgs
170 {
171 RoutedEvent = InputElement.KeyDownEvent,
172 Key = Key.O,
173 KeyModifiers = KeyModifiers.Control | KeyModifiers.Shift,
174 PhysicalKey = PhysicalKey.O
175 };
176
177 var handled = MainWindowHotkeyService.TryHandleTunnelKeyDownForMainVm(e, vm);
178 await Dispatcher.UIThread.InvokeAsync(() => { });
179
180 Assert.True(handled);
181 Assert.True(e.Handled);
182 Assert.True(requested);
183 }
184 finally
185 {
186 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
187 }
188 }
189
190 [Fact]
191 public void TryHandleTunnelKeyDownForMainVm_CtrlP_OpensGoToFilePalette()
192 {
193 MainWindowHotkeyService.ReplaceMergedMapForTests(
194 new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
195 {
196 ["workspace_go_to_file"] = "Ctrl+P"
197 });
198 try
199 {
200 var vm = new MainWindowViewModel();
201 Assert.False(vm.IsCommandPaletteOpen);
202
203 var e = new KeyEventArgs
204 {
205 RoutedEvent = InputElement.KeyDownEvent,
206 Key = Key.P,
207 KeyModifiers = KeyModifiers.Control,
208 PhysicalKey = PhysicalKey.P
209 };
210
211 Assert.True(MainWindowHotkeyService.TryHandleTunnelKeyDownForMainVm(e, vm));
212 Assert.True(e.Handled);
213 Assert.True(vm.IsCommandPaletteOpen);
214 Assert.Equal("f:", vm.CommandPaletteQuery);
215 }
216 finally
217 {
218 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
219 }
220 }
221
222 [Fact]
223 public void TryExecuteEditorHostShortcut_WorkspaceGoToFile_OpensGoToFilePalette()
224 {
225 try
226 {
227 var vm = new MainWindowViewModel();
228 Assert.True(MainWindowHotkeyService.TryExecuteEditorHostShortcut("workspace_go_to_file", vm));
229 Assert.True(vm.IsCommandPaletteOpen);
230 Assert.Equal("f:", vm.CommandPaletteQuery);
231 }
232 finally
233 {
234 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
235 }
236 }
237
238 [AvaloniaFact]
239 public void ApplyAll_AddsWindowKeyBinding_ForToggleCommandPalette()
240 {
241 MainWindowHotkeyService.ReplaceMergedMapForTests(
242 new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
243 {
244 ["toggle_command_palette"] = "Ctrl+Q"
245 });
246
247 try
248 {
249 var vm = new MainWindowViewModel();
250 var window = new MainWindow
251 {
252 DataContext = vm
253 };
254
255 MainWindowHotkeyService.ApplyAll(window, vm);
256
257 var binding = Assert.Single(window.KeyBindings, kb => kb.Gesture is KeyGesture gesture
258 && gesture.Key == Key.Q
259 && gesture.KeyModifiers == KeyModifiers.Control);
260 Assert.NotNull(binding.Command);
261 }
262 finally
263 {
264 MainWindowHotkeyService.ReplaceMergedMapForTests(null);
265 }
266 }
267}
268
View only · write via MCP/CIDE