Skip to content

Commit bfeaa80

Browse files
authored
Merge pull request #393 from Somfic/BindingParser
2 parents e9a0f4d + 87be7cd commit bfeaa80

6 files changed

Lines changed: 2403 additions & 0 deletions

File tree

EliteAPI.Tests/BindingTests.cs

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
using EliteAPI.Bindings;
2+
using FluentAssertions;
3+
4+
namespace EliteAPI.Tests.Bindings;
5+
6+
public class BindingParserTests
7+
{
8+
9+
[Test]
10+
public void Parse_Sample_File()
11+
{
12+
string xml = File.ReadAllText(@"EliteAPI.Tests\test.binds");
13+
14+
var content = BindingParser.Parse(xml);
15+
16+
content.Should().NotBeNull();
17+
}
18+
19+
[Test]
20+
public void Parse_Should_Read_Simple_Primary_Keyboard_Bindings()
21+
{
22+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
23+
<Root PresetName=""Custom"">
24+
<YawLeftButton>
25+
<Primary Device=""Keyboard"" Key=""Key_A"" />
26+
<Secondary Device=""{NoDevice}"" Key="""" />
27+
</YawLeftButton>
28+
<YawRightButton>
29+
<Primary Device=""Keyboard"" Key=""Key_D"" />
30+
<Secondary Device=""{NoDevice}"" Key="""" />
31+
</YawRightButton>
32+
</Root>";
33+
34+
var controls = BindingParser
35+
.Parse(xml)
36+
.ToDictionary(c => c.Name);
37+
38+
controls.Should().ContainKeys("YawLeftButton", "YawRightButton");
39+
40+
var yawLeft = controls["YawLeftButton"];
41+
yawLeft.Primary.HasValue.Should().BeTrue();
42+
yawLeft.Primary!.Value.Device.Should().Be("Keyboard");
43+
yawLeft.Primary!.Value.Key.Should().Be("Key_A");
44+
yawLeft.Secondary.HasValue.Should().BeFalse();
45+
yawLeft.IsToggle.Should().BeNull();
46+
yawLeft.IsInverted.Should().BeNull();
47+
yawLeft.Deadzone.Should().BeNull();
48+
49+
var yawRight = controls["YawRightButton"];
50+
yawRight.Primary.HasValue.Should().BeTrue();
51+
yawRight.Primary!.Value.Device.Should().Be("Keyboard");
52+
yawRight.Primary!.Value.Key.Should().Be("Key_D");
53+
yawRight.Secondary.HasValue.Should().BeFalse();
54+
}
55+
56+
[Test]
57+
public void Parse_Should_Treat_NoDevice_EmptyKey_As_Unbound()
58+
{
59+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
60+
<Root>
61+
<MouseReset>
62+
<Primary Device=""{NoDevice}"" Key="""" />
63+
<Secondary Device=""{NoDevice}"" Key="""" />
64+
</MouseReset>
65+
66+
<RollLeftButton>
67+
<Primary Device=""{NoDevice}"" Key="""" />
68+
<Secondary Device=""{NoDevice}"" Key="""" />
69+
</RollLeftButton>
70+
</Root>";
71+
72+
var controls = BindingParser
73+
.Parse(xml)
74+
.ToDictionary(c => c.Name);
75+
76+
controls.Should().ContainKeys("MouseReset", "RollLeftButton");
77+
78+
foreach (var control in controls.Values)
79+
{
80+
control.Primary.HasValue.Should().BeFalse();
81+
control.Secondary.HasValue.Should().BeFalse();
82+
control.IsToggle.Should().BeNull();
83+
control.IsInverted.Should().BeNull();
84+
control.Deadzone.Should().BeNull();
85+
}
86+
}
87+
88+
[Test]
89+
public void Parse_Should_Map_Binding_Element_To_Primary_And_Read_Inverted_And_Deadzone()
90+
{
91+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
92+
<Root>
93+
<YawAxisRaw>
94+
<Binding Device=""Joystick"" Key=""Joy_X"" />
95+
<Inverted Value=""1"" />
96+
<Deadzone Value=""0.25000000"" />
97+
</YawAxisRaw>
98+
</Root>";
99+
100+
var control = BindingParser
101+
.Parse(xml)
102+
.Single(c => c.Name == "YawAxisRaw");
103+
104+
control.Primary.HasValue.Should().BeTrue();
105+
var binding = control.Primary!.Value;
106+
107+
binding.Device.Should().Be("Joystick");
108+
binding.Key.Should().Be("Joy_X");
109+
binding.Modifiers.Should().BeNull();
110+
111+
control.Secondary.HasValue.Should().BeFalse();
112+
control.IsInverted.Should().BeTrue();
113+
control.IsToggle.Should().BeNull();
114+
control.Deadzone.Should().Be(0.25f);
115+
}
116+
117+
[Test]
118+
public void Parse_Should_Handle_All_ToggleOn_Variants()
119+
{
120+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
121+
<Root>
122+
<!-- Explicit false -->
123+
<BlockMouseDecay>
124+
<Primary Device=""{NoDevice}"" Key="""" />
125+
<Secondary Device=""{NoDevice}"" Key="""" />
126+
<ToggleOn Value=""0"" />
127+
</BlockMouseDecay>
128+
129+
<!-- Explicit true -->
130+
<ToggleFlightAssist>
131+
<Primary Device=""Keyboard"" Key=""Key_Z"" />
132+
<Secondary Device=""{NoDevice}"" Key="""" />
133+
<ToggleOn Value=""1"" />
134+
</ToggleFlightAssist>
135+
136+
<!-- No Value attribute, taken from HCS pattern -->
137+
<YawToRollButton>
138+
<Primary Device=""{NoDevice}"" Key="""" />
139+
<Secondary Device=""{NoDevice}"" Key="""" />
140+
<ToggleOn />
141+
</YawToRollButton>
142+
143+
<!-- No ToggleOn at all -->
144+
<MouseReset>
145+
<Primary Device=""{NoDevice}"" Key="""" />
146+
<Secondary Device=""{NoDevice}"" Key="""" />
147+
</MouseReset>
148+
</Root>";
149+
150+
var controls = BindingParser
151+
.Parse(xml)
152+
.ToDictionary(c => c.Name);
153+
154+
controls["BlockMouseDecay"].IsToggle.Should().BeFalse();
155+
controls["ToggleFlightAssist"].IsToggle.Should().BeTrue();
156+
157+
// Presence of <ToggleOn /> with no Value attribute – treat as true
158+
controls["YawToRollButton"].IsToggle.Should().BeTrue();
159+
160+
// No <ToggleOn> element at all
161+
controls["MouseReset"].IsToggle.Should().BeNull();
162+
}
163+
164+
[Test]
165+
public void Parse_Should_Read_Modifiers_On_Primary_And_Secondary()
166+
{
167+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
168+
<Root>
169+
<!-- From the stock binds -->
170+
<PhotoCameraToggle>
171+
<Primary Device=""Keyboard"" Key=""Key_Space"">
172+
<Modifier Device=""Keyboard"" Key=""Key_LeftControl"" />
173+
<Modifier Device=""Keyboard"" Key=""Key_LeftAlt"" />
174+
</Primary>
175+
<Secondary Device=""{NoDevice}"" Key="""" />
176+
</PhotoCameraToggle>
177+
178+
<!-- From the HCS binds: modifiers on Secondary -->
179+
<ToggleReverseThrottleInput>
180+
<Primary Device=""{NoDevice}"" Key="""" />
181+
<Secondary Device=""Keyboard"" Key=""Key_A"">
182+
<Modifier Device=""Keyboard"" Key=""Key_LeftAlt"" />
183+
<Modifier Device=""Keyboard"" Key=""Key_RightShift"" />
184+
</Secondary>
185+
<ToggleOn Value=""1"" />
186+
</ToggleReverseThrottleInput>
187+
</Root>";
188+
189+
var controls = BindingParser
190+
.Parse(xml)
191+
.ToDictionary(c => c.Name);
192+
193+
// Primary modifiers
194+
var photo = controls["PhotoCameraToggle"];
195+
photo.Primary.HasValue.Should().BeTrue();
196+
var photoPrimary = photo.Primary!.Value;
197+
198+
photoPrimary.Device.Should().Be("Keyboard");
199+
photoPrimary.Key.Should().Be("Key_Space");
200+
photoPrimary.Modifiers.Should().NotBeNull();
201+
photoPrimary.Modifiers!.Should().HaveCount(2);
202+
203+
var firstPhotoMod = (Binding)photoPrimary.Modifiers![0];
204+
var secondPhotoMod = (Binding)photoPrimary.Modifiers![1];
205+
206+
firstPhotoMod.Device.Should().Be("Keyboard");
207+
firstPhotoMod.Key.Should().Be("Key_LeftControl");
208+
secondPhotoMod.Device.Should().Be("Keyboard");
209+
secondPhotoMod.Key.Should().Be("Key_LeftAlt");
210+
211+
// Secondary modifiers
212+
var reverse = controls["ToggleReverseThrottleInput"];
213+
reverse.Secondary.HasValue.Should().BeTrue();
214+
var reverseSecondary = reverse.Secondary!.Value;
215+
216+
reverseSecondary.Device.Should().Be("Keyboard");
217+
reverseSecondary.Key.Should().Be("Key_A");
218+
reverseSecondary.Modifiers.Should().NotBeNull();
219+
reverseSecondary.Modifiers!.Should().HaveCount(2);
220+
221+
var firstReverseMod = (Binding)reverseSecondary.Modifiers![0];
222+
var secondReverseMod = (Binding)reverseSecondary.Modifiers![1];
223+
224+
firstReverseMod.Device.Should().Be("Keyboard");
225+
firstReverseMod.Key.Should().Be("Key_LeftAlt");
226+
secondReverseMod.Device.Should().Be("Keyboard");
227+
secondReverseMod.Key.Should().Be("Key_RightShift");
228+
}
229+
230+
[Test]
231+
public void Parse_Should_Ignore_Hold_Child_And_Still_Parse_Binding_And_Toggle()
232+
{
233+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
234+
<Root>
235+
<HumanoidItemWheelButton>
236+
<Primary Device=""Keyboard"" Key=""Key_LeftAlt"">
237+
<Hold Value=""1"" />
238+
</Primary>
239+
<Secondary Device=""{NoDevice}"" Key="""" />
240+
<ToggleOn Value=""0"" />
241+
</HumanoidItemWheelButton>
242+
</Root>";
243+
244+
var control = BindingParser
245+
.Parse(xml)
246+
.Single(c => c.Name == "HumanoidItemWheelButton");
247+
248+
control.Primary.HasValue.Should().BeTrue();
249+
var primary = control.Primary!.Value;
250+
251+
primary.Device.Should().Be("Keyboard");
252+
primary.Key.Should().Be("Key_LeftAlt");
253+
254+
// Hold isn't currently modelled on Binding – at minimum it must not break parsing
255+
primary.Modifiers.Should().BeNull();
256+
257+
control.IsToggle.Should().BeFalse(); // Value=""0""
258+
}
259+
260+
[Test]
261+
public void Parse_Should_Treat_Key_Attribute_Case_Insensitive()
262+
{
263+
// Based on ToggleVanityCamera in HCS, but with a meaningful secondary key
264+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
265+
<Root>
266+
<ToggleVanityCamera>
267+
<Primary Device=""Keyboard"" Key=""Key_RightShift"" />
268+
<!-- key attribute is lowercase here on purpose -->
269+
<Secondary Device=""Keyboard"" key=""Key_F12"" />
270+
</ToggleVanityCamera>
271+
</Root>";
272+
273+
var control = BindingParser
274+
.Parse(xml)
275+
.Single(c => c.Name == "ToggleVanityCamera");
276+
277+
control.Primary.HasValue.Should().BeTrue();
278+
control.Primary!.Value.Key.Should().Be("Key_RightShift");
279+
280+
control.Secondary.HasValue.Should().BeTrue();
281+
control.Secondary!.Value.Device.Should().Be("Keyboard");
282+
control.Secondary!.Value.Key.Should().Be("Key_F12");
283+
}
284+
285+
[Test]
286+
public void Parse_Should_Handle_Float_Deadzone_Formats()
287+
{
288+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
289+
<Root>
290+
<RollAxisRaw>
291+
<Binding Device=""Joystick"" Key=""Joy_Roll"" />
292+
<Deadzone Value=""0.00000000"" />
293+
</RollAxisRaw>
294+
295+
<LateralThrustRaw>
296+
<Binding Device=""Joystick"" Key=""Joy_Lateral"" />
297+
<Deadzone Value=""0.05"" />
298+
</LateralThrustRaw>
299+
300+
<VerticalThrustRaw>
301+
<Binding Device=""Joystick"" Key=""Joy_Vertical"" />
302+
<Deadzone Value=""7"" />
303+
</VerticalThrustRaw>
304+
</Root>";
305+
306+
var controls = BindingParser
307+
.Parse(xml)
308+
.ToDictionary(c => c.Name);
309+
310+
controls["RollAxisRaw"].Deadzone.Should().Be(0.0f);
311+
controls["LateralThrustRaw"].Deadzone.Should().Be(0.05f);
312+
controls["VerticalThrustRaw"].Deadzone.Should().Be(7.0f);
313+
}
314+
315+
[Test]
316+
public void Parse_Should_Ignore_Value_Only_Elements_And_Only_Return_Controls_With_Bindings()
317+
{
318+
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
319+
<Root PresetName=""Custom"">
320+
<KeyboardLayout>nl-NL</KeyboardLayout>
321+
<MouseXMode Value=""Bindings_MouseRoll"" />
322+
<MouseSensitivity Value=""1.00000000"" />
323+
<MouseGUI Value=""1"" />
324+
325+
<YawLeftButton>
326+
<Primary Device=""Keyboard"" Key=""Key_A"" />
327+
<Secondary Device=""{NoDevice}"" Key="""" />
328+
</YawLeftButton>
329+
</Root>";
330+
331+
var controls = BindingParser.Parse(xml).ToArray();
332+
333+
controls.Should().ContainSingle(c => c.Name == "YawLeftButton");
334+
controls.Select(c => c.Name).Should().NotContain("MouseXMode");
335+
controls.Select(c => c.Name).Should().NotContain("MouseSensitivity");
336+
controls.Select(c => c.Name).Should().NotContain("MouseGUI");
337+
}
338+
}

0 commit comments

Comments
 (0)