Skip to content

Commit 1371da1

Browse files
committed
Add unit tests for cancel commands
1 parent 5afbbdf commit 1371da1

File tree

4 files changed

+282
-0
lines changed

4 files changed

+282
-0
lines changed

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,68 @@ private void GreetUser(User user)
554554
VerifyGeneratedDiagnostics<ICommandGenerator>(source, "MVVMTK0012");
555555
}
556556

557+
[TestMethod]
558+
public void InvalidICommandIncludeCancelCommandSettings_SynchronousMethod()
559+
{
560+
string source = @"
561+
using CommunityToolkit.Mvvm.Input;
562+
563+
namespace MyApp
564+
{
565+
public partial class SampleViewModel
566+
{
567+
[ICommand(IncludeCancelCommand = true)]
568+
private void GreetUser(User user)
569+
{
570+
}
571+
}
572+
}";
573+
574+
VerifyGeneratedDiagnostics<ICommandGenerator>(source, "MVVMTK0013");
575+
}
576+
577+
[TestMethod]
578+
public void InvalidICommandIncludeCancelCommandSettings_AsynchronousMethodWithNoCancellationToken()
579+
{
580+
string source = @"
581+
using System.Threading.Tasks;
582+
using CommunityToolkit.Mvvm.Input;
583+
584+
namespace MyApp
585+
{
586+
public partial class SampleViewModel
587+
{
588+
[ICommand(IncludeCancelCommand = true)]
589+
private async Task DoWorkAsync()
590+
{
591+
}
592+
}
593+
}";
594+
595+
VerifyGeneratedDiagnostics<ICommandGenerator>(source, "MVVMTK0013");
596+
}
597+
598+
[TestMethod]
599+
public void InvalidICommandIncludeCancelCommandSettings_AsynchronousMethodWithParameterAndNoCancellationToken()
600+
{
601+
string source = @"
602+
using System.Threading.Tasks;
603+
using CommunityToolkit.Mvvm.Input;
604+
605+
namespace MyApp
606+
{
607+
public partial class SampleViewModel
608+
{
609+
[ICommand(IncludeCancelCommand = true)]
610+
private async Task GreetUserAsync(User user)
611+
{
612+
}
613+
}
614+
}";
615+
616+
VerifyGeneratedDiagnostics<ICommandGenerator>(source, "MVVMTK0013");
617+
}
618+
557619
/// <summary>
558620
/// Verifies the output of a source generator.
559621
/// </summary>

tests/CommunityToolkit.Mvvm.UnitTests/Test_AsyncRelayCommand.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.ComponentModel;
88
using System.Threading.Tasks;
9+
using System.Windows.Input;
910
using CommunityToolkit.Mvvm.Input;
1011
using CommunityToolkit.Mvvm.UnitTests.Helpers;
1112
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -418,4 +419,68 @@ public void Test_AsyncRelayCommand_ExecuteWithoutConcurrencyRaisesCanExecuteChan
418419

419420
tcs.SetResult(null);
420421
}
422+
423+
[TestMethod]
424+
public void Test_AsyncRelayCommand_GetCancelCommand_DisabledCommand()
425+
{
426+
TaskCompletionSource<object?> tcs = new();
427+
428+
AsyncRelayCommand command = new(() => tcs.Task);
429+
430+
ICommand cancelCommand = command.CreateCancelCommand();
431+
432+
Assert.IsNotNull(cancelCommand);
433+
Assert.IsFalse(cancelCommand.CanExecute(null));
434+
435+
// No-op
436+
cancelCommand.Execute(null);
437+
438+
Assert.AreEqual("CommunityToolkit.Mvvm.Input.Internals.DisabledCommand", cancelCommand.GetType().ToString());
439+
440+
ICommand cancelCommand2 = command.CreateCancelCommand();
441+
442+
Assert.IsNotNull(cancelCommand2);
443+
Assert.IsFalse(cancelCommand2.CanExecute(null));
444+
445+
Assert.AreSame(cancelCommand, cancelCommand2);
446+
}
447+
448+
[TestMethod]
449+
public void Test_AsyncRelayCommand_GetCancelCommand_WithToken()
450+
{
451+
TaskCompletionSource<object?> tcs = new();
452+
453+
AsyncRelayCommand command = new(token => tcs.Task);
454+
455+
ICommand cancelCommand = command.CreateCancelCommand();
456+
457+
Assert.IsNotNull(cancelCommand);
458+
Assert.IsFalse(cancelCommand.CanExecute(null));
459+
460+
// No-op
461+
cancelCommand.Execute(null);
462+
463+
Assert.AreEqual("CommunityToolkit.Mvvm.Input.Internals.CancelCommand", cancelCommand.GetType().ToString());
464+
465+
List<(object? Sender, EventArgs Args)> cancelCommandCanExecuteChangedArgs = new();
466+
467+
cancelCommand.CanExecuteChanged += (s, e) => cancelCommandCanExecuteChangedArgs.Add((s, e));
468+
469+
command.Execute(null);
470+
471+
Assert.AreEqual(1, cancelCommandCanExecuteChangedArgs.Count);
472+
Assert.AreSame(cancelCommand, cancelCommandCanExecuteChangedArgs[0].Sender);
473+
Assert.AreSame(EventArgs.Empty, cancelCommandCanExecuteChangedArgs[0].Args);
474+
475+
Assert.IsTrue(cancelCommand.CanExecute(null));
476+
477+
cancelCommand.Execute(null);
478+
479+
Assert.IsFalse(cancelCommand.CanExecute(null));
480+
Assert.AreEqual(2, cancelCommandCanExecuteChangedArgs.Count);
481+
Assert.AreSame(cancelCommand, cancelCommandCanExecuteChangedArgs[1].Sender);
482+
Assert.AreSame(EventArgs.Empty, cancelCommandCanExecuteChangedArgs[1].Args);
483+
Assert.IsFalse(command.CanBeCanceled);
484+
Assert.IsTrue(command.IsCancellationRequested);
485+
}
421486
}

tests/CommunityToolkit.Mvvm.UnitTests/Test_AsyncRelayCommand{T}.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.ComponentModel;
88
using System.Threading.Tasks;
9+
using System.Windows.Input;
910
using CommunityToolkit.Mvvm.Input;
1011
using CommunityToolkit.Mvvm.UnitTests.Helpers;
1112
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -360,4 +361,68 @@ public void Test_AsyncRelayCommand_ExecuteWithoutConcurrencyRaisesCanExecuteChan
360361

361362
tcs.SetResult(null);
362363
}
364+
365+
[TestMethod]
366+
public void Test_AsyncRelayCommandOfT_GetCancelCommand_DisabledCommand()
367+
{
368+
TaskCompletionSource<object?> tcs = new();
369+
370+
AsyncRelayCommand<string> command = new(s => tcs.Task);
371+
372+
ICommand cancelCommand = command.CreateCancelCommand();
373+
374+
Assert.IsNotNull(cancelCommand);
375+
Assert.IsFalse(cancelCommand.CanExecute(null));
376+
377+
// No-op
378+
cancelCommand.Execute(null);
379+
380+
Assert.AreEqual("CommunityToolkit.Mvvm.Input.Internals.DisabledCommand", cancelCommand.GetType().ToString());
381+
382+
ICommand cancelCommand2 = command.CreateCancelCommand();
383+
384+
Assert.IsNotNull(cancelCommand2);
385+
Assert.IsFalse(cancelCommand2.CanExecute(null));
386+
387+
Assert.AreSame(cancelCommand, cancelCommand2);
388+
}
389+
390+
[TestMethod]
391+
public void Test_AsyncRelayCommandOfT_GetCancelCommand_WithToken()
392+
{
393+
TaskCompletionSource<object?> tcs = new();
394+
395+
AsyncRelayCommand<string> command = new((s, token) => tcs.Task);
396+
397+
ICommand cancelCommand = command.CreateCancelCommand();
398+
399+
Assert.IsNotNull(cancelCommand);
400+
Assert.IsFalse(cancelCommand.CanExecute(null));
401+
402+
// No-op
403+
cancelCommand.Execute(null);
404+
405+
Assert.AreEqual("CommunityToolkit.Mvvm.Input.Internals.CancelCommand", cancelCommand.GetType().ToString());
406+
407+
List<(object? Sender, EventArgs Args)> cancelCommandCanExecuteChangedArgs = new();
408+
409+
cancelCommand.CanExecuteChanged += (s, e) => cancelCommandCanExecuteChangedArgs.Add((s, e));
410+
411+
command.Execute(null);
412+
413+
Assert.AreEqual(1, cancelCommandCanExecuteChangedArgs.Count);
414+
Assert.AreSame(cancelCommand, cancelCommandCanExecuteChangedArgs[0].Sender);
415+
Assert.AreSame(EventArgs.Empty, cancelCommandCanExecuteChangedArgs[0].Args);
416+
417+
Assert.IsTrue(cancelCommand.CanExecute(null));
418+
419+
cancelCommand.Execute(null);
420+
421+
Assert.IsFalse(cancelCommand.CanExecute(null));
422+
Assert.AreEqual(2, cancelCommandCanExecuteChangedArgs.Count);
423+
Assert.AreSame(cancelCommand, cancelCommandCanExecuteChangedArgs[1].Sender);
424+
Assert.AreSame(EventArgs.Empty, cancelCommandCanExecuteChangedArgs[1].Args);
425+
Assert.IsFalse(command.CanBeCanceled);
426+
Assert.IsTrue(command.IsCancellationRequested);
427+
}
363428
}

tests/CommunityToolkit.Mvvm.UnitTests/Test_ICommandAttribute.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using System.Linq;
78
using System.Threading;
@@ -74,6 +75,18 @@ public async Task Test_ICommandAttribute_RelayCommand()
7475
{
7576
Assert.AreSame(Task.CompletedTask, tasks[i]);
7677
}
78+
79+
tasks.Clear();
80+
81+
for (int i = 11; i < 21; i++)
82+
{
83+
tasks.Add(model.AddValueToListAndDelayWithDefaultConcurrencyAsync_WithCancelCommandCommand.ExecuteAsync(i));
84+
}
85+
86+
Assert.AreEqual(10, tasks.Count);
87+
88+
// Only the first item should have been added, like the previous case
89+
CollectionAssert.AreEqual(model.Values, Enumerable.Range(0, 12).ToArray());
7790
}
7891

7992
[TestMethod]
@@ -334,6 +347,34 @@ public void Test_ICommandAttribute_ViewModelRightAfterRegion()
334347
Assert.IsInstanceOfType(model.GreetCommand, typeof(RelayCommand));
335348
}
336349

350+
[TestMethod]
351+
public async void Test_ICommandAttribute_CancelCommands()
352+
{
353+
CancelCommandViewModel model = new();
354+
355+
model.DoWorkCommand.Execute(null);
356+
357+
Assert.IsTrue(model.DoWorkCancelCommand.CanExecute(null));
358+
359+
model.DoWorkCancelCommand.Execute(null);
360+
361+
await Task.Yield();
362+
363+
Assert.IsTrue(model.Tcs1.Task.IsCompleted);
364+
Assert.IsTrue(model.Tcs1.Task.Result is OperationCanceledException);
365+
366+
model.DoWorkWithParameterCommand.Execute(null);
367+
368+
Assert.IsTrue(model.DoWorkWithParameterCancelCommand.CanExecute(null));
369+
370+
model.DoWorkWithParameterCancelCommand.Execute(42);
371+
372+
await Task.Yield();
373+
374+
Assert.IsTrue(model.Tcs2.Task.IsCompleted);
375+
Assert.IsTrue(model.Tcs2.Task.Result is 42);
376+
}
377+
337378
#region Region
338379
public class Region
339380
{
@@ -399,6 +440,14 @@ private async Task AddValueToListAndDelayWithDefaultConcurrencyAsync(int value)
399440
await Task.Delay(1000);
400441
}
401442

443+
[ICommand(IncludeCancelCommand = true)]
444+
private async Task AddValueToListAndDelayWithDefaultConcurrencyAsync_WithCancelCommand(int value, CancellationToken token)
445+
{
446+
Values.Add(value);
447+
448+
await Task.Delay(1000);
449+
}
450+
402451
#region Test region
403452

404453
/// <summary>
@@ -567,4 +616,45 @@ public sealed class User
567616
{
568617
public string? Name { get; set; }
569618
}
619+
620+
public partial class CancelCommandViewModel
621+
{
622+
public TaskCompletionSource<object?> Tcs1 { get; } = new();
623+
624+
public TaskCompletionSource<object?> Tcs2 { get; } = new();
625+
626+
[ICommand(IncludeCancelCommand = true)]
627+
private async Task DoWorkAsync(CancellationToken token)
628+
{
629+
using CancellationTokenRegistration registration = token.Register(static state => ((TaskCompletionSource<object?>)state!).TrySetCanceled(), Tcs1);
630+
631+
try
632+
{
633+
_ = await Tcs1.Task;
634+
635+
_ = Tcs1.TrySetResult(null);
636+
}
637+
catch (OperationCanceledException e)
638+
{
639+
_ = Tcs1.TrySetResult(e);
640+
}
641+
}
642+
643+
[ICommand(IncludeCancelCommand = true)]
644+
private async Task DoWorkWithParameterAsync(int number, CancellationToken token)
645+
{
646+
using CancellationTokenRegistration registration = token.Register(static state => ((TaskCompletionSource<object?>)state!).TrySetCanceled(), Tcs2);
647+
648+
try
649+
{
650+
_ = await Tcs2.Task;
651+
652+
_ = Tcs2.TrySetResult(null);
653+
}
654+
catch (OperationCanceledException)
655+
{
656+
_ = Tcs2.TrySetResult(number);
657+
}
658+
}
659+
}
570660
}

0 commit comments

Comments
 (0)