Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit testing ViewModel with ReactiveCommand #281

Open
powerdude opened this issue Dec 7, 2024 · 1 comment
Open

Unit testing ViewModel with ReactiveCommand #281

powerdude opened this issue Dec 7, 2024 · 1 comment

Comments

@powerdude
Copy link

Hi, I'm trying to test this view model and running into some issues. When the command executes, the test just times out. I'm new to reactive programming so there could be a problem with how I've set things up. Any help would be greatly appreciated.

using FluentAssertions;
using R3;
using System.Windows.Input;

public class Item
{
    public required string Text { get; set; }
}

public partial class SampleViewCommandModel
{
    public ObservableList<Item> Items { get; set; } = [
        new Item { Text = "Item 1" },
        new Item { Text = "Item 2" },
        new Item { Text = "Item 3" }
    ];

    public ValueTask RemoveItem(Item item)
    {
        _ = Items.Remove(item);
        return ValueTask.CompletedTask;
    }
}

public partial class BindableSampleViewCommandModel : INotifyPropertyChanged, IDisposable
{
    private readonly CompositeDisposable compositeDisposable=[];
    public BindableSampleViewCommandModel() : this(new SampleViewCommandModel())
    {

    }

    public BindableSampleViewCommandModel(SampleViewCommandModel model)
    {
        Model = model;
        Items ??= model.Items.ToNotifyCollectionChanged(
                SynchronizationContextCollectionEventDispatcher.Current
            );

        RemoveItem = new ReactiveCommand<Item>((x, ctx) => model.RemoveItem(x));

        if (model is INotifyPropertyChanged npc)
        {
            npc.PropertyChanged += Npc_PropertyChanged;
        }
    }

    private void Npc_PropertyChanged(object? sender, PropertyChangedEventArgs e) => OnPropertyChanged(e.PropertyName);

    private readonly BindableReactiveProperty<Item> item = new();
    public SampleViewCommandModel Model { get; private set; }
    public NotifyCollectionChangedSynchronizedViewList<Item> Items { get; private set; }
    public ReactiveCommand<Item> RemoveItem { get; private set; }

    /// <inheritdoc />
    public void Dispose() => this.compositeDisposable.Dispose();

    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public class BindableSampleViewModelCommandTests
{
    [Fact]
    public void RemoveItem()
    {
        var vm = new BindableSampleViewCommandModel();
        using var monitoredVm = vm.Monitor();
        using var monitoredList = vm.Items.Monitor();

        _ = vm
            .Items.Should()
            .HaveCount(3);
         vm.RemoveItem.Execute(vm.Items[1]);
       
        _ = vm
            .Items.Should()
            .HaveCount(2);
           _ = monitoredList.Should().Raise("CollectionChanged");
              }
}

Also, is it possible for ReactiveCommand<T> and ReactiveCommand<TInput, TOutput> to take a Func<T,bool>, or an equivalent, for CanExecute so that it can check the input parameter to make sure it is valid before running the command? I see the command could be created from an IObservable<bool>, but it doesn't help if you have a UI like the following:

<custom:ParentView Source="{Binding Items}">
		<DataTemplate>
		  <ListView ItemsSource="{Binding Data}"
					Margin="0, 50, 0, 10">
			<ListView.ItemTemplate>
			  <DataTemplate>
				<StackPanel Orientation="Horizontal">
				  <TextBlock Text="{Binding Text}"
							 VerticalAlignment="Center" />

				  <Button Content="Delete"
						  Command="{utu:AncestorBinding AncestorType=custom:ParentView, Path=DataContext.RemoveItem}"
						  CommandParameter="{Binding}" />
				</StackPanel>
			  </DataTemplate>
			</ListView.ItemTemplate>
		  </ListView>
		</DataTemplate>
	</custom:ParentView>

the problem is the CommandParameter for the command. Not a real opportunity to create an observable without compromising the UI as far as I know.

@powerdude
Copy link
Author

I think I sorted this out. Using AwaitOption.Drop worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant