Forge
csharpdeeb25a2
1using CascadeIDE.Services;
2using Xunit;
3
4namespace CascadeIDE.Tests;
5
6public sealed class IntentMelodyAliasesTests
7{
8 [Theory]
9 [InlineData("c:gs", "gs")]
10 [InlineData("c: GS", "gs")]
11 [InlineData(" c:br ", "br")]
12 [InlineData("c:", "")]
13 public void TryGetTail_ParsesPrefix(string raw, string expectedTail) =>
14 Assert.True(IntentMelodyAliases.TryGetTail(raw, out var tail) && tail == expectedTail);
15
16 [Fact]
17 public void TryGetTail_RejectsNonMelody() =>
18 Assert.False(IntentMelodyAliases.TryGetTail("f:foo", out _));
19
20 [Fact]
21 public void TryResolveExact_gs_IsGitStatus() =>
22 Assert.Equal(IdeCommands.GitStatus, IntentMelodyAliases.TryResolveExactCommandId("gs"));
23
24 [Fact]
25 public void TryResolveExact_dl_IsDebugLaunch() =>
26 Assert.Equal(IdeCommands.DebugLaunch, IntentMelodyAliases.TryResolveExactCommandId("dl"));
27
28 [Fact]
29 public void TryResolveExact_debug_step_melodies_map_to_dap_commands()
30 {
31 Assert.Equal(IdeCommands.DebugStepOver, IntentMelodyAliases.TryResolveExactCommandId("dn"));
32 Assert.Equal(IdeCommands.DebugStepInto, IntentMelodyAliases.TryResolveExactCommandId("di"));
33 Assert.Equal(IdeCommands.DebugStepOut, IntentMelodyAliases.TryResolveExactCommandId("df"));
34 Assert.Equal(IdeCommands.DebugStop, IntentMelodyAliases.TryResolveExactCommandId("dx"));
35 }
36
37 [Fact]
38 public void TryResolveExact_so_IsOpenSolutionDialog() =>
39 Assert.Equal(IdeCommands.OpenSolutionDialog, IntentMelodyAliases.TryResolveExactCommandId("so"));
40
41 [Fact]
42 public void TryResolveExact_tol_IsToggleWorkspaceSplittersLock() =>
43 Assert.Equal(IdeCommands.ToggleWorkspaceSplittersLock, IntentMelodyAliases.TryResolveExactCommandId("tol"));
44
45 [Fact]
46 public void TryResolveExact_chat_and_agent_melodies_map_to_commands()
47 {
48 Assert.Equal(IdeCommands.ShowChatPage, IntentMelodyAliases.TryResolveExactCommandId("cps"));
49 Assert.Equal(IdeCommands.SendChat, IntentMelodyAliases.TryResolveExactCommandId("cs"));
50 Assert.Equal(IdeCommands.ChatExportReadable, IntentMelodyAliases.TryResolveExactCommandId("cex"));
51 Assert.Equal(IdeCommands.ForkChatThread, IntentMelodyAliases.TryResolveExactCommandId("ctf"));
52 Assert.Equal(IdeCommands.ChatSelectPrevMessage, IntentMelodyAliases.TryResolveExactCommandId("amp"));
53 Assert.Equal(IdeCommands.ChatSelectNextMessage, IntentMelodyAliases.TryResolveExactCommandId("amn"));
54 Assert.Equal(IdeCommands.ChatToggleSelectedThinking, IntentMelodyAliases.TryResolveExactCommandId("amt"));
55 Assert.Equal(IdeCommands.ChatToggleShowThinkingInHistory, IntentMelodyAliases.TryResolveExactCommandId("amh"));
56 Assert.Equal(IdeCommands.ChatSelectPrevThread, IntentMelodyAliases.TryResolveExactCommandId("atp"));
57 Assert.Equal(IdeCommands.ChatSelectNextThread, IntentMelodyAliases.TryResolveExactCommandId("atn"));
58 Assert.Equal(IdeCommands.ChatOpenSelectedThread, IntentMelodyAliases.TryResolveExactCommandId("ato"));
59 Assert.Equal(IdeCommands.ChatShowThreadOverview, IntentMelodyAliases.TryResolveExactCommandId("atb"));
60 }
61
62 [Fact]
63 public void TryResolveExact_environment_and_terminal_surface_melodies()
64 {
65 Assert.Equal(IdeCommands.BuildSolutionUi, IntentMelodyAliases.TryResolveExactCommandId("br"));
66 Assert.Equal(IdeCommands.BuildStructured, IntentMelodyAliases.TryResolveExactCommandId("bs"));
67 Assert.Equal(IdeCommands.ShowEnvironmentReadinessPage, IntentMelodyAliases.TryResolveExactCommandId("ers"));
68 Assert.Equal(IdeCommands.ShowHybridIndexPage, IntentMelodyAliases.TryResolveExactCommandId("his"));
69 Assert.Equal(IdeCommands.ShowWebAiPortalPage, IntentMelodyAliases.TryResolveExactCommandId("wai"));
70 Assert.Equal(IdeCommands.ShowTerminalPanel, IntentMelodyAliases.TryResolveExactCommandId("ts"));
71 Assert.Equal(IdeCommands.Select, IntentMelodyAliases.TryResolveExactCommandId("els"));
72 Assert.Equal(IdeCommands.ApplyEdit, IntentMelodyAliases.TryResolveExactCommandId("eld"));
73 }
74
75 [Fact]
76 public void HasStrictLongerAliasPrefix_er_true_ers_extends() =>
77 Assert.True(IntentMelodyAliases.HasStrictLongerAliasPrefix("er"));
78
79 [Fact]
80 public void Bundled_intent_melody_toml_is_readable_like_runtime()
81 {
82 Assert.True(BundledAppContent.TryReadDiskThenEmbedded(IntentMelodyAliases.BundledRelativePath, out var text));
83 Assert.Contains("intent_catalog_schema_version = 2", text, StringComparison.Ordinal);
84 Assert.Contains("[[tail_wire_class]]", text, StringComparison.Ordinal);
85 Assert.Contains("[[command]]", text, StringComparison.Ordinal);
86 Assert.Contains("[[command.slash]]", text, StringComparison.Ordinal);
87 Assert.Contains("path = \"/terminal show\"", text, StringComparison.Ordinal);
88 Assert.Contains("melody_slug = \"ers\"", text, StringComparison.Ordinal);
89 Assert.Contains("command_id = \"show_environment_readiness_page\"", text, StringComparison.Ordinal);
90 Assert.Contains("melody_slug = \"his\"", text, StringComparison.Ordinal);
91 Assert.Contains("command_id = \"show_hybrid_index_page\"", text, StringComparison.Ordinal);
92 Assert.Contains("melody_slug = \"wai\"", text, StringComparison.Ordinal);
93 Assert.Contains("melody_palette_hint_slug = \"wai-url\"", text, StringComparison.Ordinal);
94 Assert.Contains("command_id = \"show_web_ai_portal_page\"", text, StringComparison.Ordinal);
95 Assert.Contains("melody_slug = \"ts\"", text, StringComparison.Ordinal);
96 Assert.Contains("command_id = \"show_terminal_panel\"", text, StringComparison.Ordinal);
97 Assert.Contains("melody_slug = \"dl\"", text, StringComparison.Ordinal);
98 Assert.Contains("command_id = \"debug_launch\"", text, StringComparison.Ordinal);
99 Assert.Contains("melody_slug = \"dn\"", text, StringComparison.Ordinal);
100 Assert.Contains("command_id = \"debug_step_over\"", text, StringComparison.Ordinal);
101 Assert.Contains("melody_slug = \"df\"", text, StringComparison.Ordinal);
102 Assert.Contains("command_id = \"debug_step_out\"", text, StringComparison.Ordinal);
103 Assert.Contains("melody_slug = \"els\"", text, StringComparison.Ordinal);
104 Assert.Contains("command_id = \"select\"", text, StringComparison.Ordinal);
105 Assert.Contains("melody_slug = \"eld\"", text, StringComparison.Ordinal);
106 Assert.Contains("command_id = \"apply_edit\"", text, StringComparison.Ordinal);
107 Assert.Contains("melody_slug = \"tol\"", text, StringComparison.Ordinal);
108 Assert.Contains("command_id = \"toggle_workspace_splitters_lock\"", text, StringComparison.Ordinal);
109 Assert.Contains("slash_group = \"Панели\"", text, StringComparison.Ordinal);
110 Assert.Contains("path = \"/mfd toggle\"", text, StringComparison.Ordinal);
111 }
112
113 [Fact]
114 public void ParseBundle_command_model_flat_melody_and_form_slash()
115 {
116 const string toml =
117 """
118 intent_catalog_schema_version = 1
119 [[command]]
120 command_id = "git_status"
121 melody_slug = "gs"
122 melody_shape = "simple"
123 [[command.form.slash]]
124 path = "/git status"
125 help = "git status"
126 """;
127
128 var bundle = IntentMelodyAliases.ParseBundleForTests(toml.Trim());
129
130 Assert.Equal(IdeCommands.GitStatus, IntentMelodyAliases.TryResolveExactCommandId("gs"));
131 Assert.True(bundle.Catalog.SlashRoutes.TryGetValue("/git status", out var slash));
132 Assert.Equal(IdeCommands.GitStatus, slash.CommandId);
133 }
134
135 [Fact]
136 public void Tomlyn_deserializes_between_slots_two_single_char_literals()
137 {
138 const string minimal =
139 """
140 intent_catalog_schema_version = 1
141
142 [[tail_wire_class]]
143 id = "int_chain_colon_space"
144 kind = "delimited_slots"
145 between_slots_any_of = [":", " "]
146
147 [[command]]
148 command_id = "git_status"
149 [command.melody]
150 slug = "z"
151 shape = "simple"
152 """;
153
154 var parsed = CascadeTomlSerializer.Deserialize<IntentMelodyAliases.IntentMelodyTomlRoot>(minimal.Trim());
155 Assert.NotNull(parsed?.TailWireClass);
156 Assert.Single(parsed.TailWireClass);
157 var row = parsed.TailWireClass[0];
158 Assert.NotNull(row.BetweenSlotsAnyOf);
159 Assert.Equal(2, row.BetweenSlotsAnyOf!.Length);
160 Assert.Equal(":", row.BetweenSlotsAnyOf![0]);
161 Assert.Single(row.BetweenSlotsAnyOf![1]!);
162 Assert.Equal(' ', row.BetweenSlotsAnyOf![1]![0]);
163
164 IntentMelodyAliases.ParseBundleForTests(minimal.Trim());
165 }
166
167 [Fact]
168 public void ParseBundle_command_model_melody_and_slash_forms()
169 {
170 const string toml =
171 """
172 intent_catalog_schema_version = 1
173 [[command]]
174 command_id = "git_status"
175 [command.melody]
176 slug = "gs"
177 shape = "simple"
178 [[command.slash]]
179 path = "/git status"
180 help = "git status"
181 """;
182
183 var bundle = IntentMelodyAliases.ParseBundleForTests(toml.Trim());
184
185 Assert.Equal(IdeCommands.GitStatus, IntentMelodyAliases.TryResolveExactCommandId("gs"));
186 Assert.True(bundle.Catalog.SlashRoutes.TryGetValue("/git status", out var slash));
187 Assert.Equal(IdeCommands.GitStatus, slash.CommandId);
188 }
189
190 [Fact]
191 public void ParseBundle_infers_show_usage_hint_for_two_int_parametric_without_explicit_flag()
192 {
193 const string toml =
194 """
195 intent_catalog_schema_version = 1
196
197 [[tail_wire_class]]
198 id = "int_chain_colon_space"
199 kind = "delimited_slots"
200 between_slots_any_of = [":", " "]
201
202 [[command]]
203 command_id = "select"
204 [command.melody]
205 slug = "zz"
206 shape = "parametric"
207 tail_signature = "<start:ln>:<end:ln>"
208 wire_class = "int_chain_colon_space"
209 chord_commit = "enter"
210 """;
211
212 var bundle = IntentMelodyAliases.ParseBundleForTests(toml.Trim());
213
214 Assert.True(bundle.Catalog.Roots.TryGetValue("zz", out var z));
215 Assert.True(z.ShowUsageHintIfBareSlug);
216 }
217
218 /// <summary>При локальном <c>dotnet test</c> полный проход <see cref="IntentMelodyAliases.Build"/> по embedded TOML (без дискового оверлея в <c>bin/</c>).</summary>
219 [Fact]
220 public void Embedded_intent_melody_ParseBundle_Build_succeeds()
221 {
222 Assert.True(BundledAppContent.TryReadEmbeddedText(IntentMelodyAliases.BundledRelativePath, out var text));
223 var bundle = IntentMelodyAliases.ParseBundleForTests(text.Trim());
224
225 Assert.NotEmpty(bundle.AliasToCommandId);
226 Assert.NotEmpty(bundle.Catalog.Roots);
227 Assert.NotEmpty(bundle.Catalog.TailWireClasses);
228 Assert.NotEmpty(bundle.Catalog.SlashRoutes);
229 Assert.True(bundle.Catalog.SlashRoutes.TryGetValue("/terminal show", out var term));
230 Assert.Equal(IdeCommands.SetMfdShellPage, term.CommandId);
231 Assert.Equal("Terminal", term.MfdPage);
232 Assert.Contains(bundle.Catalog.Roots.Values, static e => e.Shape == IntentMelodyShape.Parametric);
233 Assert.True(bundle.Catalog.Roots["els"].ShowUsageHintIfBareSlug, "els — два int-слота, подсказка по умолчанию");
234 Assert.False(bundle.Catalog.Roots["wai"].ShowUsageHintIfBareSlug, "wai — явно false в TOML");
235 }
236
237 [Fact]
238 public void HasStrictLongerAliasPrefix_ce_true_cex_extends() =>
239 Assert.True(IntentMelodyAliases.HasStrictLongerAliasPrefix("ce"));
240
241 [Fact]
242 public void FilterByTailPrefix_c_Matches_chat_core_melodies() =>
243 Assert.Equal(6, IntentMelodyAliases.FilterByTailPrefix("c").Count);
244
245 [Fact]
246 public void FilterByTailPrefix_a_Matches_agent_navigation_melodies() =>
247 Assert.Equal(8, IntentMelodyAliases.FilterByTailPrefix("a").Count);
248
249 [Fact]
250 public void FilterByTailPrefix_g_Matches_gs_gc_gp_gsu() =>
251 Assert.Equal(4, IntentMelodyAliases.FilterByTailPrefix("g").Count);
252
253 [Fact]
254 public void HasStrictLongerAliasPrefix_gs_true_gsu_extends() =>
255 Assert.True(IntentMelodyAliases.HasStrictLongerAliasPrefix("gs"));
256
257 [Fact]
258 public void HasStrictLongerAliasPrefix_so_false() =>
259 Assert.False(IntentMelodyAliases.HasStrictLongerAliasPrefix("so"));
260
261 [Fact]
262 public void SampleAliasesForFooter_lists_aliases()
263 {
264 var s = IntentMelodyAliases.SampleAliasesForFooter(4);
265 Assert.False(string.IsNullOrWhiteSpace(s));
266 Assert.Contains(",", s, StringComparison.Ordinal);
267 }
268}
269
View only · write via MCP/CIDE