diff --git a/src/BootstrapBlazor/Components/Timer/Timer.razor b/src/BootstrapBlazor/Components/Timer/Timer.razor index ffc51e67463e3becaa8db921cad936b354395650..f36ac98ff776fa3cdfc8a11e708a9740aa1a17be 100644 --- a/src/BootstrapBlazor/Components/Timer/Timer.razor +++ b/src/BootstrapBlazor/Components/Timer/Timer.razor @@ -19,6 +19,6 @@ } else { - + } - \ No newline at end of file + diff --git a/src/BootstrapBlazor/Components/Timer/Timer.razor.cs b/src/BootstrapBlazor/Components/Timer/Timer.razor.cs index 0fc64c911bc89244525a6ba04fe4c0973fbaff3d..c9d1ad3d1e9cde1b0b62b7f1a12dbd515183ca6b 100644 --- a/src/BootstrapBlazor/Components/Timer/Timer.razor.cs +++ b/src/BootstrapBlazor/Components/Timer/Timer.razor.cs @@ -42,7 +42,7 @@ public partial class Timer : IDisposable private CancellationTokenSource? CancelTokenSource { get; set; } - private ManualResetEvent ResetEvent { get; set; } = new(true); + private ManualResetEvent ResetEvent { get; } = new(true); private bool Vibrate { get; set; } @@ -153,44 +153,49 @@ public partial class Timer : IDisposable Task.Run(async () => { + // 点击 Cancel 后重新设置再点击 Star if (CancelTokenSource != null) { + CancelTokenSource.Cancel(); CancelTokenSource.Dispose(); } CancelTokenSource = new CancellationTokenSource(); - try + do { - do + try { - await Task.Delay(1000, CancelTokenSource?.Token ?? new CancellationToken(true)); + await Task.Delay(1000, CancelTokenSource.Token); + } + catch (TaskCanceledException) { } - if (!(CancelTokenSource?.IsCancellationRequested ?? true)) - { - ResetEvent.WaitOne(); - CurrentTimespan = CurrentTimespan.Subtract(TimeSpan.FromSeconds(1)); - await InvokeAsync(StateHasChanged); - } + if (!CancelTokenSource.IsCancellationRequested) + { + ResetEvent.WaitOne(); + CurrentTimespan = CurrentTimespan.Subtract(TimeSpan.FromSeconds(1)); + await InvokeAsync(StateHasChanged); } - while (!(CancelTokenSource?.IsCancellationRequested ?? true) && CurrentTimespan > TimeSpan.Zero); + } + while (!CancelTokenSource.IsCancellationRequested && CurrentTimespan > TimeSpan.Zero); - if (CurrentTimespan == TimeSpan.Zero) + if (CurrentTimespan == TimeSpan.Zero) + { + await Task.Delay(500, CancelTokenSource.Token); + if (!CancelTokenSource.IsCancellationRequested) { - await Task.Delay(500, CancelTokenSource?.Token ?? new CancellationToken(true)); - if (!(CancelTokenSource?.IsCancellationRequested ?? true)) + Value = TimeSpan.Zero; + await InvokeAsync(() => { - Value = TimeSpan.Zero; - await InvokeAsync(() => + Vibrate = IsVibrate; + StateHasChanged(); + if (OnTimeout != null) { - Vibrate = IsVibrate; - StateHasChanged(); - OnTimeout?.Invoke(); - }); - } + OnTimeout(); + } + }); } } - catch (TaskCanceledException) { } }); } @@ -214,8 +219,14 @@ public partial class Timer : IDisposable private async Task OnClickCancel() { Value = TimeSpan.Zero; - CancelTokenSource?.Cancel(); - if (OnCancel != null) await OnCancel.Invoke(); + if (CancelTokenSource != null) + { + CancelTokenSource.Cancel(); + } + if (OnCancel != null) + { + await OnCancel(); + } } /// @@ -226,9 +237,12 @@ public partial class Timer : IDisposable { if (disposing) { - CancelTokenSource?.Dispose(); - CancelTokenSource = null; - + if (CancelTokenSource != null) + { + CancelTokenSource.Cancel(); + CancelTokenSource.Dispose(); + CancelTokenSource = null; + } ResetEvent.Dispose(); } } diff --git a/test/UnitTest/Components/TimerTest.cs b/test/UnitTest/Components/TimerTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..26ed13a9e9603ceadd423f3df3c382f97ad440a0 --- /dev/null +++ b/test/UnitTest/Components/TimerTest.cs @@ -0,0 +1,147 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Threading; +using Timer = BootstrapBlazor.Components.Timer; + +namespace UnitTest.Components; + +public class TimerTest : BootstrapBlazorTestBase +{ + [Fact] + public void Value_Ok() + { + var cut = Context.RenderComponent(builder => builder.Add(a => a.Value, TimeSpan.FromSeconds(10))); + + Assert.Contains("circle-body", cut.Markup); + + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.Value, TimeSpan.Zero); + }); + Assert.DoesNotContain("circle-body", cut.Markup); + } + + [Fact] + public void Width_Ok() + { + var cut = Context.RenderComponent(builder => + { + builder.Add(a => a.Value, TimeSpan.FromSeconds(5)); + builder.Add(a => a.Width, 100); + }); + + Assert.Contains("width: 100px", cut.Markup); + } + + [Fact] + public void StrokeWidth_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromSeconds(5)); + pb.Add(a => a.StrokeWidth, 5); + }); + Assert.Contains("stroke-width=\"5\"", cut.Markup); + + // 增加代码覆盖率 + //Width / 2 < StrokeWidth + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.Width, 6); + pb.Add(a => a.StrokeWidth, 6); + }); + Assert.Equal(2, cut.Instance.StrokeWidth); + } + + [Fact] + public void Color_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromSeconds(5)); + pb.Add(a => a.Color, Color.Success); + }); + Assert.Contains("circle-success", cut.Markup); + } + + [Fact] + public void ShowProgress_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromSeconds(5)); + pb.Add(a => a.ShowProgress, false); + }); + Assert.Contains("circle-title d-none", cut.Markup); + } + + [Fact] + public void IsVibrate_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromSeconds(5)); + pb.Add(a => a.IsVibrate, false); + }); + } + + [Fact] + public async Task OnStart_Ok() + { + var timeout = false; + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.OnTimeout, () => + { + timeout = true; + return Task.CompletedTask; + }); + }); + var downs = cut.FindAll(".time-spinner-arrow.fa-angle-down"); + downs[2].Click(); + cut.Find(".time-panel-btn.confirm").Click(); + + await Task.Delay(2000); + Assert.True(timeout); + } + + [Fact] + public async Task OnCancel_Ok() + { + var cancelled = false; + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.OnCancel, () => + { + cancelled = true; + return Task.CompletedTask; + }); + }); + var downs = cut.FindAll(".time-spinner-arrow.fa-angle-down"); + downs[0].Click(); + cut.Find(".time-panel-btn.confirm").Click(); + await Task.Delay(1000); + var buttons = cut.FindAll(".timer-buttons button"); + // pause + Assert.True(buttons[1].ClassList.Contains("btn-warning")); + buttons[1].Click(); + + // resume + buttons = cut.FindAll(".timer-buttons button"); + Assert.True(buttons[1].ClassList.Contains("btn-success")); + buttons[1].Click(); + + // cancel + buttons = cut.FindAll(".timer-buttons button"); + buttons[0].Click(); + Assert.True(cancelled); + + // 代码覆盖率 Cancel 后再 Star + downs = cut.FindAll(".time-spinner-arrow.fa-angle-down"); + downs[0].Click(); + cut.Find(".time-panel-btn.confirm").Click(); + await Task.Delay(1000); + } +}