Skip to content

Commit c5c0498

Browse files
committed
refactor: replace WinForms-specific tests with cross-platform command binding tests
Remove the ReactiveUI.Binding.WinForms.Tests project which required Windows to run. Replace with cross-platform runtime tests using structural test doubles (WinFormsLikeButton, WpfLikeButton) that exercise all three binding plugins: EventEnabled, CommandProperty, and DefaultEvent — running on all platforms. Adds 14 new tests covering EventEnabled (Click+Enabled sync, CanExecute, dispose, expression/observable params) and CommandProperty (Command/ CommandParameter property setting, reactive updates, command changes).
1 parent 1e658c2 commit c5c0498

9 files changed

Lines changed: 375 additions & 524 deletions

File tree

src/ReactiveUI.Binding.SourceGenerators.slnx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,5 @@
3333
<Project Path="tests/ReactiveUI.Binding.GeneratedCode.TestModels/ReactiveUI.Binding.GeneratedCode.TestModels.csproj" />
3434
<Project Path="tests/ReactiveUI.Binding.GeneratedCode.Tests/ReactiveUI.Binding.GeneratedCode.Tests.csproj" />
3535
<Project Path="tests/ReactiveUI.Binding.AotValidation/ReactiveUI.Binding.AotValidation.csproj" />
36-
<Project Path="tests/ReactiveUI.Binding.WinForms.Tests/ReactiveUI.Binding.WinForms.Tests.csproj" />
3736
</Folder>
3837
</Solution>

src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/Scenarios/BindCommandScenarios.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,92 @@ public static IDisposable DeepCommandPath(
6868
SharedScenarios.BindCommand.DeepCommandPath.MyViewModel vm,
6969
SharedScenarios.BindCommand.DeepCommandPath.MyView view)
7070
=> SharedScenarios.BindCommand.DeepCommandPath.Scenario.Execute(vm, view);
71+
72+
/// <summary>
73+
/// Binds the Save command via Click+Enabled (no Command property, no parameter).
74+
/// Routes through EventEnabledBindingPlugin.
75+
/// </summary>
76+
/// <param name="vm">The source view model.</param>
77+
/// <param name="view">The target view.</param>
78+
/// <returns>A disposable representing the binding.</returns>
79+
public static IDisposable EventEnabled(
80+
SharedScenarios.BindCommand.EventEnabled.MyViewModel vm,
81+
SharedScenarios.BindCommand.EventEnabled.MyView view)
82+
=> SharedScenarios.BindCommand.EventEnabled.Scenario.Execute(vm, view);
83+
84+
/// <summary>
85+
/// Binds the Save command via Click+Enabled with an expression parameter.
86+
/// Routes through EventEnabledBindingPlugin (expression parameter variant).
87+
/// </summary>
88+
/// <param name="vm">The source view model.</param>
89+
/// <param name="view">The target view.</param>
90+
/// <returns>A disposable representing the binding.</returns>
91+
public static IDisposable EventEnabledExprParam(
92+
SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel vm,
93+
SharedScenarios.BindCommand.EventEnabledExprParam.MyView view)
94+
=> SharedScenarios.BindCommand.EventEnabledExprParam.Scenario.Execute(vm, view);
95+
96+
/// <summary>
97+
/// Binds the Save command via Click+Enabled with an observable parameter.
98+
/// Routes through EventEnabledBindingPlugin (observable parameter variant).
99+
/// </summary>
100+
/// <param name="vm">The source view model.</param>
101+
/// <param name="view">The target view.</param>
102+
/// <param name="parameter">An observable producing command parameters.</param>
103+
/// <returns>A disposable representing the binding.</returns>
104+
public static IDisposable EventEnabledObsParam(
105+
SharedScenarios.BindCommand.EventEnabledObsParam.MyViewModel vm,
106+
SharedScenarios.BindCommand.EventEnabledObsParam.MyView view,
107+
IObservable<string> parameter)
108+
=> SharedScenarios.BindCommand.EventEnabledObsParam.Scenario.Execute(vm, view, parameter);
109+
110+
/// <summary>
111+
/// Binds the Save command via Command property (no event, no parameter).
112+
/// Routes through CommandPropertyBindingPlugin.
113+
/// </summary>
114+
/// <param name="vm">The source view model.</param>
115+
/// <param name="view">The target view.</param>
116+
/// <returns>A disposable representing the binding.</returns>
117+
public static IDisposable CommandProperty(
118+
SharedScenarios.BindCommand.CommandProperty.MyViewModel vm,
119+
SharedScenarios.BindCommand.CommandProperty.MyView view)
120+
=> SharedScenarios.BindCommand.CommandProperty.Scenario.Execute(vm, view);
121+
122+
/// <summary>
123+
/// Binds the Save command via Command property with an expression parameter.
124+
/// Routes through CommandPropertyBindingPlugin (expression parameter variant).
125+
/// </summary>
126+
/// <param name="vm">The source view model.</param>
127+
/// <param name="view">The target view.</param>
128+
/// <returns>A disposable representing the binding.</returns>
129+
public static IDisposable CommandPropertyExprParam(
130+
SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel vm,
131+
SharedScenarios.BindCommand.CommandPropertyExprParam.MyView view)
132+
=> SharedScenarios.BindCommand.CommandPropertyExprParam.Scenario.Execute(vm, view);
133+
134+
/// <summary>
135+
/// Binds the Save command via Command property with an observable parameter.
136+
/// Routes through CommandPropertyBindingPlugin (observable parameter variant).
137+
/// </summary>
138+
/// <param name="vm">The source view model.</param>
139+
/// <param name="view">The target view.</param>
140+
/// <param name="parameter">An observable producing command parameters.</param>
141+
/// <returns>A disposable representing the binding.</returns>
142+
public static IDisposable CommandPropertyObsParam(
143+
SharedScenarios.BindCommand.CommandPropertyObsParam.MyViewModel vm,
144+
SharedScenarios.BindCommand.CommandPropertyObsParam.MyView view,
145+
IObservable<string> parameter)
146+
=> SharedScenarios.BindCommand.CommandPropertyObsParam.Scenario.Execute(vm, view, parameter);
147+
148+
/// <summary>
149+
/// Binds the Save command to a control with no default event.
150+
/// Falls back to runtime command binding service.
151+
/// </summary>
152+
/// <param name="vm">The source view model.</param>
153+
/// <param name="view">The target view.</param>
154+
/// <returns>A disposable representing the binding.</returns>
155+
public static IDisposable NoEvent(
156+
SharedScenarios.BindCommand.NoEvent.MyViewModel vm,
157+
SharedScenarios.BindCommand.NoEvent.MyView view)
158+
=> SharedScenarios.BindCommand.NoEvent.Scenario.Execute(vm, view);
71159
}

src/tests/ReactiveUI.Binding.GeneratedCode.Tests/Binding/BindCommandTests.cs

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,293 @@ public async Task BasicNoParam_DoubleDispose_DoesNotThrow()
346346
await Assert.That(action).ThrowsNothing();
347347
}
348348

349+
// ── EventEnabled (Click + Enabled, no Command property) ─────────────
350+
351+
/// <summary>
352+
/// Verifies that clicking executes the command via event+Enabled binding.
353+
/// </summary>
354+
/// <returns>A task representing the asynchronous test operation.</returns>
355+
[Test]
356+
public async Task EventEnabled_ClickExecutesCommand()
357+
{
358+
var vm = new SharedScenarios.BindCommand.EventEnabled.MyViewModel();
359+
var view = new SharedScenarios.BindCommand.EventEnabled.MyView();
360+
var command = new TrackingCommand();
361+
vm.Save = command;
362+
363+
using var binding = BindCommandScenarios.EventEnabled(vm, view);
364+
view.SaveButton.PerformClick();
365+
366+
await Assert.That(command.ExecuteCount).IsEqualTo(1);
367+
}
368+
369+
/// <summary>
370+
/// Verifies that the Enabled property is synchronized with CanExecute.
371+
/// </summary>
372+
/// <returns>A task representing the asynchronous test operation.</returns>
373+
[Test]
374+
public async Task EventEnabled_EnabledSyncedWithCanExecute()
375+
{
376+
var vm = new SharedScenarios.BindCommand.EventEnabled.MyViewModel();
377+
var view = new SharedScenarios.BindCommand.EventEnabled.MyView();
378+
var command = new TrackingCommand { CanExecuteResult = false };
379+
vm.Save = command;
380+
381+
using var binding = BindCommandScenarios.EventEnabled(vm, view);
382+
383+
await Assert.That(view.SaveButton.Enabled).IsFalse();
384+
}
385+
386+
/// <summary>
387+
/// Verifies that the Enabled property becomes false when the command is null.
388+
/// </summary>
389+
/// <returns>A task representing the asynchronous test operation.</returns>
390+
[Test]
391+
public async Task EventEnabled_NullCommand_DisablesControl()
392+
{
393+
var vm = new SharedScenarios.BindCommand.EventEnabled.MyViewModel();
394+
var view = new SharedScenarios.BindCommand.EventEnabled.MyView();
395+
396+
using var binding = BindCommandScenarios.EventEnabled(vm, view);
397+
398+
await Assert.That(view.SaveButton.Enabled).IsFalse();
399+
}
400+
401+
/// <summary>
402+
/// Verifies that disposing the binding stops command execution.
403+
/// </summary>
404+
/// <returns>A task representing the asynchronous test operation.</returns>
405+
[Test]
406+
public async Task EventEnabled_Dispose_StopsExecution()
407+
{
408+
var vm = new SharedScenarios.BindCommand.EventEnabled.MyViewModel();
409+
var view = new SharedScenarios.BindCommand.EventEnabled.MyView();
410+
var command = new TrackingCommand();
411+
vm.Save = command;
412+
413+
var binding = BindCommandScenarios.EventEnabled(vm, view);
414+
view.SaveButton.PerformClick();
415+
await Assert.That(command.ExecuteCount).IsEqualTo(1);
416+
417+
binding.Dispose();
418+
view.SaveButton.PerformClick();
419+
420+
await Assert.That(command.ExecuteCount).IsEqualTo(1);
421+
}
422+
423+
/// <summary>
424+
/// Verifies that CanExecute=false prevents execution via event+Enabled binding.
425+
/// </summary>
426+
/// <returns>A task representing the asynchronous test operation.</returns>
427+
[Test]
428+
public async Task EventEnabled_CanExecuteFalse_DoesNotExecute()
429+
{
430+
var vm = new SharedScenarios.BindCommand.EventEnabled.MyViewModel();
431+
var view = new SharedScenarios.BindCommand.EventEnabled.MyView();
432+
var command = new TrackingCommand { CanExecuteResult = false };
433+
vm.Save = command;
434+
435+
using var binding = BindCommandScenarios.EventEnabled(vm, view);
436+
view.SaveButton.PerformClick();
437+
438+
await Assert.That(command.ExecuteCount).IsEqualTo(0);
439+
}
440+
441+
/// <summary>
442+
/// Verifies event+Enabled binding with an expression parameter.
443+
/// </summary>
444+
/// <returns>A task representing the asynchronous test operation.</returns>
445+
[Test]
446+
public async Task EventEnabledExprParam_PassesParameter()
447+
{
448+
var vm = new SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel
449+
{
450+
CurrentItem = "TestItem",
451+
};
452+
var view = new SharedScenarios.BindCommand.EventEnabledExprParam.MyView();
453+
var command = new TrackingCommand();
454+
vm.Save = command;
455+
456+
using var binding = BindCommandScenarios.EventEnabledExprParam(vm, view);
457+
view.SaveButton.PerformClick();
458+
459+
await Assert.That(command.ExecuteCount).IsEqualTo(1);
460+
await Assert.That(command.LastParameter).IsEqualTo("TestItem");
461+
}
462+
463+
/// <summary>
464+
/// Verifies event+Enabled binding with an observable parameter.
465+
/// </summary>
466+
/// <returns>A task representing the asynchronous test operation.</returns>
467+
[Test]
468+
public async Task EventEnabledObsParam_PassesParameter()
469+
{
470+
var vm = new SharedScenarios.BindCommand.EventEnabledObsParam.MyViewModel();
471+
var view = new SharedScenarios.BindCommand.EventEnabledObsParam.MyView();
472+
var command = new TrackingCommand();
473+
var paramSubject = new System.Reactive.Subjects.BehaviorSubject<string>("obs-param");
474+
vm.Save = command;
475+
476+
using var binding = BindCommandScenarios.EventEnabledObsParam(vm, view, paramSubject);
477+
view.SaveButton.PerformClick();
478+
479+
await Assert.That(command.ExecuteCount).IsEqualTo(1);
480+
await Assert.That(command.LastParameter).IsEqualTo("obs-param");
481+
}
482+
483+
/// <summary>
484+
/// Verifies event+Enabled binding with observable parameter updates reactively.
485+
/// </summary>
486+
/// <returns>A task representing the asynchronous test operation.</returns>
487+
[Test]
488+
public async Task EventEnabledObsParam_ParameterUpdates()
489+
{
490+
var vm = new SharedScenarios.BindCommand.EventEnabledObsParam.MyViewModel();
491+
var view = new SharedScenarios.BindCommand.EventEnabledObsParam.MyView();
492+
var command = new TrackingCommand();
493+
var paramSubject = new System.Reactive.Subjects.BehaviorSubject<string>("initial");
494+
vm.Save = command;
495+
496+
using var binding = BindCommandScenarios.EventEnabledObsParam(vm, view, paramSubject);
497+
498+
paramSubject.OnNext("updated");
499+
view.SaveButton.PerformClick();
500+
501+
await Assert.That(command.LastParameter).IsEqualTo("updated");
502+
}
503+
504+
// ── CommandProperty (Command + CommandParameter, no event) ──────────
505+
506+
/// <summary>
507+
/// Verifies that binding sets the Command property on the control.
508+
/// </summary>
509+
/// <returns>A task representing the asynchronous test operation.</returns>
510+
[Test]
511+
public async Task CommandProperty_SetsCommandOnControl()
512+
{
513+
var vm = new SharedScenarios.BindCommand.CommandProperty.MyViewModel();
514+
var view = new SharedScenarios.BindCommand.CommandProperty.MyView();
515+
var command = new TrackingCommand();
516+
vm.Save = command;
517+
518+
using var binding = BindCommandScenarios.CommandProperty(vm, view);
519+
520+
await Assert.That(view.SaveButton.Command).IsEqualTo(command);
521+
}
522+
523+
/// <summary>
524+
/// Verifies that setting the command after binding updates the control.
525+
/// </summary>
526+
/// <returns>A task representing the asynchronous test operation.</returns>
527+
[Test]
528+
public async Task CommandProperty_CommandSetAfterBinding_UpdatesControl()
529+
{
530+
var vm = new SharedScenarios.BindCommand.CommandProperty.MyViewModel();
531+
var view = new SharedScenarios.BindCommand.CommandProperty.MyView();
532+
var command = new TrackingCommand();
533+
534+
using var binding = BindCommandScenarios.CommandProperty(vm, view);
535+
536+
vm.Save = command;
537+
538+
await Assert.That(view.SaveButton.Command).IsEqualTo(command);
539+
}
540+
541+
/// <summary>
542+
/// Verifies that changing the command updates the control's Command property.
543+
/// </summary>
544+
/// <returns>A task representing the asynchronous test operation.</returns>
545+
[Test]
546+
public async Task CommandProperty_CommandChanges_ControlUpdated()
547+
{
548+
var vm = new SharedScenarios.BindCommand.CommandProperty.MyViewModel();
549+
var view = new SharedScenarios.BindCommand.CommandProperty.MyView();
550+
var first = new TrackingCommand();
551+
var second = new TrackingCommand();
552+
vm.Save = first;
553+
554+
using var binding = BindCommandScenarios.CommandProperty(vm, view);
555+
await Assert.That(view.SaveButton.Command).IsEqualTo(first);
556+
557+
vm.Save = second;
558+
await Assert.That(view.SaveButton.Command).IsEqualTo(second);
559+
}
560+
561+
/// <summary>
562+
/// Verifies that null command results in null on the control.
563+
/// </summary>
564+
/// <returns>A task representing the asynchronous test operation.</returns>
565+
[Test]
566+
public async Task CommandProperty_NullCommand_SetsControlCommandNull()
567+
{
568+
var vm = new SharedScenarios.BindCommand.CommandProperty.MyViewModel();
569+
var view = new SharedScenarios.BindCommand.CommandProperty.MyView();
570+
571+
using var binding = BindCommandScenarios.CommandProperty(vm, view);
572+
573+
await Assert.That(view.SaveButton.Command).IsNull();
574+
}
575+
576+
/// <summary>
577+
/// Verifies that expression parameter sets CommandParameter on the control.
578+
/// </summary>
579+
/// <returns>A task representing the asynchronous test operation.</returns>
580+
[Test]
581+
public async Task CommandPropertyExprParam_SetsCommandParameter()
582+
{
583+
var vm = new SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel
584+
{
585+
CurrentItem = "TestItem",
586+
};
587+
var view = new SharedScenarios.BindCommand.CommandPropertyExprParam.MyView();
588+
var command = new TrackingCommand();
589+
vm.Save = command;
590+
591+
using var binding = BindCommandScenarios.CommandPropertyExprParam(vm, view);
592+
593+
await Assert.That(view.SaveButton.Command).IsEqualTo(command);
594+
await Assert.That(view.SaveButton.CommandParameter).IsEqualTo("TestItem");
595+
}
596+
597+
/// <summary>
598+
/// Verifies that observable parameter sets CommandParameter on the control.
599+
/// </summary>
600+
/// <returns>A task representing the asynchronous test operation.</returns>
601+
[Test]
602+
public async Task CommandPropertyObsParam_SetsCommandParameter()
603+
{
604+
var vm = new SharedScenarios.BindCommand.CommandPropertyObsParam.MyViewModel();
605+
var view = new SharedScenarios.BindCommand.CommandPropertyObsParam.MyView();
606+
var command = new TrackingCommand();
607+
var paramSubject = new System.Reactive.Subjects.BehaviorSubject<string>("obs-param");
608+
vm.Save = command;
609+
610+
using var binding = BindCommandScenarios.CommandPropertyObsParam(vm, view, paramSubject);
611+
612+
await Assert.That(view.SaveButton.Command).IsEqualTo(command);
613+
await Assert.That(view.SaveButton.CommandParameter).IsEqualTo("obs-param");
614+
}
615+
616+
/// <summary>
617+
/// Verifies that the observable parameter updates CommandParameter reactively.
618+
/// </summary>
619+
/// <returns>A task representing the asynchronous test operation.</returns>
620+
[Test]
621+
public async Task CommandPropertyObsParam_ParameterUpdates()
622+
{
623+
var vm = new SharedScenarios.BindCommand.CommandPropertyObsParam.MyViewModel();
624+
var view = new SharedScenarios.BindCommand.CommandPropertyObsParam.MyView();
625+
var command = new TrackingCommand();
626+
var paramSubject = new System.Reactive.Subjects.BehaviorSubject<string>("initial");
627+
vm.Save = command;
628+
629+
using var binding = BindCommandScenarios.CommandPropertyObsParam(vm, view, paramSubject);
630+
631+
paramSubject.OnNext("updated");
632+
633+
await Assert.That(view.SaveButton.CommandParameter).IsEqualTo("updated");
634+
}
635+
349636
/// <summary>
350637
/// A simple <see cref="ICommand"/> implementation that tracks invocations for testing.
351638
/// </summary>

0 commit comments

Comments
 (0)