Skip to content

Commit fd7f3c4

Browse files
authored
Merge pull request #15 from xboxoneresearch/feat/buttons
Single Dis-/connect button, clear log button, width scaling, donation link in footer
2 parents 79de754 + ab600e0 commit fd7f3c4

22 files changed

Lines changed: 973 additions & 135 deletions
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using PostCodeSerialMonitor.Utils;
2+
using Xunit;
3+
4+
namespace PostCodeSerialMonitor.Tests;
5+
6+
public class AutoScrollTrackerTests
7+
{
8+
[Fact]
9+
public void OnScrollChanged_ContentGrowsFasterThanScrollToEndCanCatchUp_StaysAutoScrolled()
10+
{
11+
var tracker = new AutoScrollTracker();
12+
13+
// Simulates a burst of fast-arriving log lines: ScrollToEnd() was called while the
14+
// extent was still 104 (viewport 50), setting the offset to 54. Before that resulting
15+
// ScrollChanged is processed, more lines already grew the extent further to 130. The
16+
// offset only ever moved forward, so this is not the user scrolling away.
17+
tracker.OnScrollChanged(offsetY: 54, extentHeight: 130, viewportHeight: 50);
18+
19+
Assert.True(tracker.AutoScroll);
20+
}
21+
22+
[Fact]
23+
public void OnScrollChanged_UserDragsScrollbarUp_Detaches()
24+
{
25+
var tracker = new AutoScrollTracker();
26+
tracker.OnScrollChanged(offsetY: 76, extentHeight: 130, viewportHeight: 50); // starts at bottom
27+
28+
// The offset moves backward - only a manual scrollbar drag does that.
29+
tracker.OnScrollChanged(offsetY: 20, extentHeight: 130, viewportHeight: 50);
30+
31+
Assert.False(tracker.AutoScroll);
32+
}
33+
34+
[Fact]
35+
public void OnScrollChanged_UserDragsAwayAfterSustainedAutoScrolling_StillDetaches()
36+
{
37+
var tracker = new AutoScrollTracker();
38+
39+
// Content keeps growing, offset keeps chasing it forward - auto-scroll stays engaged.
40+
tracker.OnScrollChanged(offsetY: 50, extentHeight: 104, viewportHeight: 50);
41+
tracker.OnScrollChanged(offsetY: 76, extentHeight: 130, viewportHeight: 50);
42+
Assert.True(tracker.AutoScroll);
43+
44+
// The user then grabs the scrollbar and drags it up.
45+
tracker.OnScrollChanged(offsetY: 20, extentHeight: 130, viewportHeight: 50);
46+
47+
Assert.False(tracker.AutoScroll);
48+
}
49+
}

PostCodeSerialMonitor.Tests/SerialServiceTests.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
using System.IO;
33
using System.Threading;
44
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Logging;
56
using Moq;
67
using PostCodeSerialMonitor.Models;
8+
using PostCodeSerialMonitor.Services;
79
using Xunit;
810

911
namespace PostCodeSerialMonitor.Tests;
@@ -16,8 +18,9 @@ public class SerialServiceTests
1618
1719
public SerialServiceTests()
1820
{
21+
var logger = new Mock<ILogger<SerialService>>();
1922
_mockSerialPort = new Mock<ISerialPort>();
20-
_serialService = new SerialService();
23+
_serialService = new SerialService(logger.Object);
2124
}
2225
2326
[Fact]
@@ -140,3 +143,48 @@ public async Task Connect_ShouldRetryOnFailedReset()
140143
}
141144
}
142145
*/
146+
147+
public class SerialServiceDisconnectTests
148+
{
149+
[Fact]
150+
public async Task Disconnect_WhileReadLoopIsBlockedOnRead_FiresDisconnectedExactlyOnce()
151+
{
152+
// Arrange
153+
var logger = new Mock<ILogger<SerialService>>();
154+
var service = new SerialService(logger.Object);
155+
156+
var readerBlocked = new ManualResetEventSlim(false); // ReadLoop has entered ReadChar()
157+
var releaseReader = new ManualResetEventSlim(false); // Close() happened, ReadChar() may throw
158+
159+
var mockPort = new Mock<ISerialPort>();
160+
mockPort.SetupGet(p => p.IsOpen).Returns(true);
161+
mockPort.Setup(p => p.ReadChar()).Returns(() =>
162+
{
163+
readerBlocked.Set();
164+
releaseReader.Wait(TimeSpan.FromSeconds(5));
165+
throw new IOException("Port closed while a read was pending");
166+
});
167+
mockPort.Setup(p => p.Close()).Callback(() => releaseReader.Set());
168+
169+
service._serialPort = mockPort.Object;
170+
service._readCts = new CancellationTokenSource();
171+
172+
var disconnectedCount = 0;
173+
service.Disconnected += () => Interlocked.Increment(ref disconnectedCount);
174+
175+
var readLoopTask = Task.Run(() => service.ReadLoop(service._readCts.Token));
176+
177+
// Ensure ReadLoop is genuinely blocked inside ReadChar() before disconnecting,
178+
// to reproduce "close happens while a read is pending" deterministically.
179+
Assert.True(readerBlocked.Wait(TimeSpan.FromSeconds(2)), "ReadLoop never reached ReadChar()");
180+
181+
// Act: single call, same as MainWindowViewModel.ToggleConnectionAsync does
182+
service.Disconnect();
183+
184+
// Wait for the background ReadLoop thread's catch block to finish too.
185+
await readLoopTask;
186+
187+
// Assert
188+
Assert.Equal(1, disconnectedCount);
189+
}
190+
}

PostCodeSerialMonitor/App.axaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
<Application.Resources>
1010
<StreamGeometry x:Key="settings_regular">M24 13.616v-3.183l-2.869-1.02c-.198-.687-.472-1.342-.811-1.955l1.308-2.751-2.257-2.258-2.752 1.307c-.613-.339-1.269-.613-1.955-.811l-1.021-2.869h-3.183l-1.021 2.869c-.686.198-1.342.472-1.955.811l-2.751-1.308-2.258 2.257 1.307 2.752c-.339.613-.614 1.268-.811 1.955l-2.869 1.02v3.183l2.869 1.02c.197.687.472 1.342.811 1.955l-1.308 2.752 2.257 2.258 2.752-1.308c.613.339 1.269.613 1.955.811l1.021 2.869h3.183l1.021-2.869c.687-.198 1.342-.472 1.955-.811l2.751 1.308 2.258-2.257-1.308-2.752c.339-.613.613-1.268.811-1.955l2.869-1.02zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z</StreamGeometry>
1111
<StreamGeometry x:Key="textbox_align_bottom_regular">M6.75 13.5H17.25C17.6642 13.5 18 13.8358 18 14.25C18 14.6297 17.7178 14.9435 17.3518 14.9932L17.25 15H6.75C6.33579 15 6 14.6642 6 14.25C6 13.8703 6.28215 13.5565 6.64823 13.5068L6.75 13.5Z M17.25 16.5H6.75L6.64823 16.5068C6.28215 16.5565 6 16.8703 6 17.25C6 17.6642 6.33579 18 6.75 18H17.25L17.3518 17.9932C17.7178 17.9435 18 17.6297 18 17.25C18 16.8358 17.6642 16.5 17.25 16.5Z M21 5.75C21 4.23122 19.7688 3 18.25 3H5.75C4.23122 3 3 4.23122 3 5.75V18.25C3 19.7688 4.23122 21 5.75 21H18.25C19.7688 21 21 19.7688 21 18.25V5.75ZM5.75 4.5H18.25C18.9404 4.5 19.5 5.05964 19.5 5.75V18.25C19.5 18.9404 18.9404 19.5 18.25 19.5H5.75C5.05964 19.5 4.5 18.9404 4.5 18.25V5.75C4.5 5.05964 5.05964 4.5 5.75 4.5Z</StreamGeometry>
12+
<StreamGeometry x:Key="arrow_sync_regular">M7.74944331,5.18010908 C8.0006303,5.50946902 7.93725859,5.9800953 7.60789865,6.23128229 C5.81957892,7.59514774 4.75,9.70820889 4.75,12 C4.75,15.7359812 7.57583716,18.8119527 11.2066921,19.2070952 L10.5303301,18.5303301 C10.2374369,18.2374369 10.2374369,17.7625631 10.5303301,17.4696699 C10.7965966,17.2034034 11.2132603,17.1791973 11.5068718,17.3970518 L11.5909903,17.4696699 L13.5909903,19.4696699 C13.8572568,19.7359365 13.8814629,20.1526002 13.6636084,20.4462117 L13.5909903,20.5303301 L11.5909903,22.5303301 C11.298097,22.8232233 10.8232233,22.8232233 10.5303301,22.5303301 C10.2640635,22.2640635 10.2398575,21.8473998 10.4577119,21.5537883 L10.5303301,21.4696699 L11.280567,20.7208479 C6.78460951,20.3549586 3.25,16.5902554 3.25,12 C3.25,9.23526399 4.54178532,6.68321165 6.6982701,5.03856442 C7.02763004,4.78737743 7.49825632,4.85074914 7.74944331,5.18010908 Z M13.4696699,1.46966991 C13.7625631,1.76256313 13.7625631,2.23743687 13.4696699,2.53033009 L12.7204313,3.27923335 C17.2159137,3.64559867 20.75,7.4100843 20.75,12 C20.75,14.6444569 19.5687435,17.0974104 17.5691913,18.7491089 C17.2498402,19.0129038 16.7771069,18.9678666 16.513312,18.6485156 C16.2495171,18.3291645 16.2945543,17.8564312 16.6139054,17.5926363 C18.2720693,16.2229363 19.25,14.1922015 19.25,12 C19.25,8.26436254 16.4246828,5.18861329 12.7943099,4.7930139 L13.4696699,5.46966991 C13.7625631,5.76256313 13.7625631,6.23743687 13.4696699,6.53033009 C13.1767767,6.8232233 12.701903,6.8232233 12.4090097,6.53033009 L10.4090097,4.53033009 C10.1161165,4.23743687 10.1161165,3.76256313 10.4090097,3.46966991 L12.4090097,1.46966991 C12.701903,1.1767767 13.1767767,1.1767767 13.4696699,1.46966991 Z</StreamGeometry>
13+
<StreamGeometry x:Key="save_regular">M3 5.75C3 4.23122 4.23122 3 5.75 3H15.7145C16.5764 3 17.4031 3.34241 18.0126 3.9519L20.0481 5.98744C20.6576 6.59693 21 7.42358 21 8.28553V18.25C21 19.7688 19.7688 21 18.25 21H5.75C4.23122 21 3 19.7688 3 18.25V5.75ZM5.75 4.5C5.05964 4.5 4.5 5.05964 4.5 5.75V18.25C4.5 18.9404 5.05964 19.5 5.75 19.5H6V14.25C6 13.0074 7.00736 12 8.25 12H15.75C16.9926 12 18 13.0074 18 14.25V19.5H18.25C18.9404 19.5 19.5 18.9404 19.5 18.25V8.28553C19.5 7.8214 19.3156 7.37629 18.9874 7.0481L16.9519 5.01256C16.6918 4.75246 16.3582 4.58269 16 4.52344V7.25C16 8.49264 14.9926 9.5 13.75 9.5H9.25C8.00736 9.5 7 8.49264 7 7.25V4.5H5.75ZM16.5 19.5V14.25C16.5 13.8358 16.1642 13.5 15.75 13.5H8.25C7.83579 13.5 7.5 13.8358 7.5 14.25V19.5H16.5ZM8.5 4.5V7.25C8.5 7.66421 8.83579 8 9.25 8H13.75C14.1642 8 14.5 7.66421 14.5 7.25V4.5H8.5Z</StreamGeometry>
14+
<StreamGeometry x:Key="erase_regular">M15.8698693,2.66881311 L20.838395,7.63733874 C21.7170746,8.5160184 21.7170746,9.9406396 20.838395,10.8193193 L12.1565953,19.4998034 L18.25448,19.5 C18.6341758,19.5 18.947971,19.7821539 18.9976334,20.1482294 L19.00448,20.25 C19.00448,20.6296958 18.7223262,20.943491 18.3562506,20.9931534 L18.25448,21 L9.84446231,21.0012505 C9.22825282,21.0348734 8.60085192,20.8163243 8.13013068,20.345603 L3.16160505,15.3770774 C2.28292539,14.4983977 2.28292539,13.0737765 3.16160505,12.1950969 L12.6878888,2.66881311 C13.5665685,1.79013346 14.9911897,1.79013346 15.8698693,2.66881311 Z M5.70859531,11.7678034 L4.22226522,13.255757 C3.929372,13.5486503 3.929372,14.023524 4.22226522,14.3164172 L9.19079085,19.2849428 C9.33723746,19.4313895 9.5291792,19.5046128 9.72112094,19.5046128 L9.75,19.5 L9.78849588,19.5015989 C9.95740385,19.4864544 10.1221581,19.4142357 10.251451,19.2849428 L11.7375953,17.7978034 L5.70859531,11.7678034 Z M13.748549,3.72947329 L6.76959531,10.7068034 L12.7985953,16.7368034 L19.7777348,9.75865909 C20.070628,9.46576587 20.070628,8.99089214 19.7777348,8.69799892 L14.8092091,3.72947329 C14.5163159,3.43658007 14.0414422,3.43658007 13.748549,3.72947329 Z</StreamGeometry>
15+
<StreamGeometry x:Key="play_regular">M13.7501344,8.41212026 L38.1671892,21.1169293 C39.7594652,21.9454306 40.3786269,23.9078584 39.5501255,25.5001344 C39.2420737,26.0921715 38.7592263,26.5750189 38.1671892,26.8830707 L13.7501344,39.5878797 C12.1578584,40.4163811 10.1954306,39.7972194 9.36692926,38.2049434 C9.12586301,37.7416442 9,37.2270724 9,36.704809 L9,11.295191 C9,9.50026556 10.4550746,8.045191 12.25,8.045191 C12.6976544,8.045191 13.1396577,8.13766178 13.5485655,8.31589049 L13.7501344,8.41212026 Z M12.5961849,10.629867 L12.4856981,10.5831892 C12.4099075,10.5581 12.3303482,10.545191 12.25,10.545191 C11.8357864,10.545191 11.5,10.8809774 11.5,11.295191 L11.5,36.704809 C11.5,36.8253313 11.5290453,36.9440787 11.584676,37.0509939 C11.7758686,37.4184422 12.2287365,37.5613256 12.5961849,37.370133 L37.0132397,24.665324 C37.1498636,24.5942351 37.2612899,24.4828088 37.3323788,24.3461849 C37.5235714,23.9787365 37.380688,23.5258686 37.0132397,23.334676 L12.5961849,10.629867 Z</StreamGeometry>
16+
<StreamGeometry x:Key="plug_disconnected_regular">M22.7399 6.32717C24.3781 8.48282 24.2132 11.571 22.2453 13.5389L20.3007 15.4835C20.0078 15.7764 19.533 15.7764 19.2401 15.4835L12.5226 8.76595C12.2297 8.47306 12.2297 7.99818 12.5226 7.70529L14.4671 5.76075C16.4352 3.79268 19.5237 3.62792 21.6793 5.26646L24.7238 2.22166C25.0167 1.92875 25.4916 1.92873 25.7845 2.22161C26.0774 2.51449 26.0774 2.98936 25.7845 3.28227L22.7399 6.32717ZM19.7704 13.8925L21.1846 12.4783C22.7467 10.9162 22.7467 8.3835 21.1846 6.82141C19.6225 5.25931 17.0899 5.25931 15.5278 6.82141L14.1135 8.23562L19.7704 13.8925Z M12.7778 11.215C13.0707 11.5079 13.0707 11.9828 12.7778 12.2757L10.6514 14.402L13.5982 17.3489L15.7238 15.2234C16.0167 14.9305 16.4916 14.9305 16.7844 15.2234C17.0773 15.5163 17.0773 15.9912 16.7844 16.284L14.6589 18.4095L15.4858 19.2364C15.7787 19.5293 15.7787 20.0042 15.4858 20.2971L13.5412 22.2416C11.5732 24.2096 8.48484 24.3745 6.32918 22.7361L3.28475 25.7808C2.99187 26.0737 2.517 26.0737 2.22409 25.7808C1.93118 25.488 1.93116 25.0131 2.22404 24.7202L5.26853 21.6754C3.63025 19.5197 3.79509 16.4314 5.76306 14.4635L7.7076 12.5189C8.0005 12.226 8.47537 12.226 8.76826 12.5189L9.59072 13.3414L11.7172 11.215C12.0101 10.9221 12.485 10.9221 12.7778 11.215ZM6.83028 21.1875C8.3929 22.7431 10.9207 22.7409 12.4806 21.181L13.8948 19.7668L8.23793 14.1099L6.82372 15.5241C5.26383 17.084 5.26163 19.6117 6.81709 21.1743L6.82366 21.1808L6.83028 21.1875Z</StreamGeometry>
1217
</Application.Resources>
1318

1419
<Application.DataTemplates>

PostCodeSerialMonitor/Assets/Resources.Designer.cs

Lines changed: 132 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)