You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix Test : InteractionBinder: immediately unregister interaction handlers when ViewModel becomes null (#4140)
### What’s fixed
When a view’s `ViewModel` is set to `null`, any previously bound
`Interaction<TInput,TOutput>` handlers are now unregistered immediately.
This restores the expected behavior where calling
`Interaction.Handle(...)` after clearing the `ViewModel` throws an
`UnhandledInteractionException<,>`.
### Symptoms
* After `view.ViewModel = null;`, invoking
`vm.Interaction1.Handle("123")` still hits the old handler instead of
throwing.
* NUnit tests like:
```csharp
Assert.That(async () => await vm.Interaction1.Handle("123").ToTask(),
Throws.TypeOf<UnhandledInteractionException<string,bool>>());
```
fail with “But was: no exception thrown”.
### Before vs After
**Before**
* `InteractionBinderImplementation` only reacted to non-null `ViewModel`
transitions.
* If `ViewModel` became `null`, the binding stream didn’t emit a value
to trigger disposal, so the previously registered handler could remain
attached.
**After**
* We explicitly merge a “VM became null” stream into the binding
sequence:
* When `ViewModel` is `null`, we emit a `default(IInteraction<,>)`
value.
* The binder disposes the current handler (`SerialDisposable`) on that
emission.
* Result: as soon as the `ViewModel` is cleared, handlers are
unregistered and subsequent `Handle(...)` calls correctly surface
`UnhandledInteractionException<,>`.
### Technical summary
* Added:
```csharp
var vmNulls = view.WhenAnyValue(x => x.ViewModel)
.Where(x => x is null)
.Select(_ => default(IInteraction<TInput, TOutput>));
var source = Reflection.ViewModelWhenAnyValue(viewModel, view,
vmExpression)
.Cast<IInteraction<TInput, TOutput>?>()
.Merge(vmNulls);
```
* On each emission we set `interactionDisposable.Disposable` to either
the new handler registration or `Disposable.Empty` when `null` is seen,
which disposes the prior registration.
* Implemented in both overloads (`Task` and `IObservable<TDontCare>`
handlers).
### Impact
* Restores the contract many apps rely on: no handler after `ViewModel =
null`.
* Aligns runtime behavior with test expectations in NUnit (no timing
hacks/sleeps needed).
* No API changes; behavior is more predictable and correct.
### How to verify
1. Bind an interaction handler.
2. Set `ViewModel` to `null`.
3. Call `Interaction.Handle(...)`.
* **Expected:** `UnhandledInteractionException<,>` is thrown.
4. Run the test suite. Previously failing tests like:
* `UnregisterTaskHandlerWhenViewModelIsSetToNull`
* `UnregisterObservableHandlerWhenViewModelIsSetToNull`
should pass.
### Compatibility & risk
* Low risk: only affects the unbinding path when `ViewModel` to `null`.
* If any app previously depended on handlers remaining active after
clearing the `ViewModel` (unlikely and contrary to docs/intent),
behavior will now be corrected.
0 commit comments