Commit 89d9200
authored
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.1 parent a83c0e0 commit 89d9200
8 files changed
Lines changed: 22 additions & 11 deletions
File tree
- src
- ReactiveUI.Builder.Maui.Tests
- ReactiveUI.Builder.Tests
- ReactiveUI.Testing
- ReactiveUI.Tests/Commands
- ReactiveUI.WinUI
- ReactiveUI/Bindings/Interaction
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
53 | 57 | | |
54 | 58 | | |
55 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1952 | 1952 | | |
1953 | 1953 | | |
1954 | 1954 | | |
| 1955 | + | |
1955 | 1956 | | |
1956 | 1957 | | |
1957 | 1958 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | 22 | | |
26 | 23 | | |
27 | 24 | | |
| |||
Lines changed: 11 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
32 | 35 | | |
33 | 36 | | |
34 | 37 | | |
35 | 38 | | |
36 | | - | |
37 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
38 | 42 | | |
39 | 43 | | |
40 | 44 | | |
| |||
57 | 61 | | |
58 | 62 | | |
59 | 63 | | |
60 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
61 | 68 | | |
62 | 69 | | |
63 | 70 | | |
| |||
0 commit comments