Forge
csharp4405de34
1using CascadeIDE.Cockpit.Composition.HostSurface;
2using CascadeIDE.Cockpit.Composition.Shell;
3using CascadeIDE.Models;
4using CascadeIDE.Services.Presentation;
5using Xunit;
6
7using CascadeIDE.Features.Agent.Environment;
8
9namespace CascadeIDE.Tests;
10
11/// <summary>
12/// Регрессии вида «открыли решение — дерево не там»: одна карта инструментов (<see cref="InstrumentPlacementRuntime"/> + <see cref="DisplaySettings"/>)
13/// должна давать тот же смысл, что и кадр композитора и что и <see cref="MainWindowDockedGridInstrumentSlots"/> (те же биндинги, что у <c>MainWindowViewModel</c>).
14/// </summary>
15[Collection("UiModeCatalog")]
16public sealed class SolutionExplorerDockPlacementInvariantTests : IDisposable
17{
18 private static PresentationGrammarTokens DefaultGrammar() =>
19 PresentationGrammarTokens.FromSettings("()", " ", "+", "P", "F", "M");
20
21 public SolutionExplorerDockPlacementInvariantTests() =>
22 InstrumentPlacementRuntime.ResetToCodeDefaults();
23
24 public void Dispose() =>
25 InstrumentPlacementRuntime.ResetToCodeDefaults();
26
27 private static MainWindowHostSurfaceFrame Compose(
28 string presentationLine,
29 bool intentSolutionExplorerVisible,
30 DisplaySettings display)
31 {
32 var parse = PresentationParser.Parse(presentationLine, DefaultGrammar());
33 Assert.True(parse.IsSuccess);
34
35 return MainWindowHostSurfaceCompositor.ComposeFrame(
36 new MainWindowShellSurfaceCompositionInput(
37 parse,
38 IntentSolutionExplorerVisible: intentSolutionExplorerVisible,
39 IntentChatPanelExpanded: false,
40 SuppressPfdColumnForPfdHostWindow: false,
41 SuppressMfdColumnForMfdHostWindow: false,
42 ExpandedMfdWidthPixels: 300,
43 CollapsedMfdWidthPixels: 12,
44 DisplaySettings: display,
45 SafetyLevel: AgentSafetyLevel.Confirm));
46 }
47
48 private static string? InstrumentInSlot(MainWindowHostSurfaceFrame frame, string slotId)
49 {
50 foreach (var i in frame.Instruments)
51 {
52 if (string.Equals(i.SlotId, slotId, StringComparison.OrdinalIgnoreCase))
53 return i.InstrumentId;
54 }
55
56 return null;
57 }
58
59 [Fact]
60 public void DefaultRouting_tree_in_pfd_slot_matches_frame_and_mfd_shell_is_not_the_tree_host()
61 {
62 var display = new DisplaySettings();
63 var frame = Compose("(P+F) (M)", intentSolutionExplorerVisible: true, display);
64
65 Assert.True(MainWindowDockedGridInstrumentSlots.IsDockedPfdSolutionExplorerTree(display));
66 Assert.False(MainWindowDockedGridInstrumentSlots.IsDockedMfdSolutionExplorerTree(display));
67
68 Assert.Equal(CockpitStandardInstrumentIds.SolutionExplorerTree, InstrumentInSlot(frame, CockpitSlotIds.Pfd));
69 Assert.Null(InstrumentInSlot(frame, CockpitSlotIds.Mfd));
70 }
71
72 [Fact]
73 public void Pfd_workspace_map_mfd_tree_mfd_shell_can_show_solution_explorer_page()
74 {
75 var display = new DisplaySettings
76 {
77 Instruments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
78 {
79 [InstrumentRoutingSlotKeys.PfdPrimary] = "workspace_map",
80 [InstrumentRoutingSlotKeys.MfdPrimary] = "solution_explorer_tree",
81 },
82 };
83
84 // Якорь M на первом экране — иначе (P+F) (M) даёт MfdColumnVisibleInMainGrid=false и слот MFD в main не монтируется.
85 var frame = Compose("(P+F+M)", intentSolutionExplorerVisible: true, display);
86
87 Assert.False(MainWindowDockedGridInstrumentSlots.IsDockedPfdSolutionExplorerTree(display));
88 Assert.True(MainWindowDockedGridInstrumentSlots.IsDockedMfdSolutionExplorerTree(display));
89
90 Assert.Equal(CockpitStandardInstrumentIds.WorkspaceNavigationMap, InstrumentInSlot(frame, CockpitSlotIds.Pfd));
91 Assert.Equal(CockpitStandardInstrumentIds.SolutionExplorerTree, InstrumentInSlot(frame, CockpitSlotIds.Mfd));
92 }
93
94 [Fact]
95 public void Frame_instruments_agree_with_resolved_slot_ids_for_each_slot()
96 {
97 var display = new DisplaySettings
98 {
99 Instruments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
100 {
101 [InstrumentRoutingSlotKeys.PfdPrimary] = "workspace_map",
102 },
103 PreferRepoInstruments = false,
104 };
105
106 var frame = Compose("(P+F) (M)", intentSolutionExplorerVisible: true, display);
107
108 var pfdResolved = MainWindowDockedGridInstrumentSlots.ResolvePfdInstrumentId(display);
109 var mfdResolved = MainWindowDockedGridInstrumentSlots.ResolveMfdInstrumentId(display);
110
111 if (frame.Shell.PfdSurfaceVisible)
112 {
113 var inFrame = InstrumentInSlot(frame, CockpitSlotIds.Pfd);
114 if (inFrame is not null)
115 Assert.Equal(pfdResolved, inFrame);
116 }
117
118 if (frame.Shell.MfdColumnVisibleInMainGrid)
119 {
120 var inFrame = InstrumentInSlot(frame, CockpitSlotIds.Mfd);
121 if (inFrame is not null)
122 Assert.Equal(mfdResolved, inFrame);
123 }
124 }
125
126 [Fact]
127 public void Workspace_routing_alias_prefers_repo_when_flag_set_compositor_and_flags_align()
128 {
129 try
130 {
131 InstrumentPlacementRuntime.ApplyWorkspaceInstrumentRouting(
132 new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
133 {
134 [InstrumentRoutingSlotKeys.PfdPrimary] = "workspace_map",
135 });
136
137 var display = new DisplaySettings { PreferRepoInstruments = true };
138 var frame = Compose("(P+F) (M)", intentSolutionExplorerVisible: true, display);
139
140 Assert.False(MainWindowDockedGridInstrumentSlots.IsDockedPfdSolutionExplorerTree(display));
141 Assert.Equal(CockpitStandardInstrumentIds.WorkspaceNavigationMap, InstrumentInSlot(frame, CockpitSlotIds.Pfd));
142 }
143 finally
144 {
145 InstrumentPlacementRuntime.ResetToCodeDefaults();
146 }
147 }
148}
149
View only · write via MCP/CIDE