Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,13 @@ public void SetPlatformRenderMode(IComponentRenderMode renderMode)

private void InferRenderModes(Renderer renderer)
{
for (var i = 0; i < _registeredCallbacks.Count; i++)
// We are iterating backwards to allow the callbacks to remove themselves from the list.
// Otherwise, we would have to make a copy of the list to avoid running into situations
// where we don't run all the callbacks because the count of the list changed while we
// were iterating over it.
// It is not allowed to register a callback while we are persisting the state, so we don't
// need to worry about new callbacks being added to the list.
for (var i = _registeredCallbacks.Count - 1; i >= 0; i--)
{
var registration = _registeredCallbacks[i];
if (registration.RenderMode != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,53 @@ public async Task PersistStateAsync_InvokesAllCallbacksEvenIfACallbackIsRemovedA
Assert.Equal(4, executionSequence.Count);
}

[Fact]
public async Task PersistStateAsync_InvokesAllCallbacksWhenFirstCallbackUnregistersItself()
{
// Arrange
var state = new Dictionary<string, byte[]>();
var store = new TestStore(state);
var persistenceManager = new ComponentStatePersistenceManager(
NullLogger<ComponentStatePersistenceManager>.Instance,
CreateServiceProvider());
var renderer = new TestRenderer();

var executionSequence = new List<int>();

// Register the first callback that will unregister itself - this is the key test case
// where the first callback removes itself from the collection during iteration
PersistingComponentStateSubscription firstSubscription = default;
firstSubscription = persistenceManager.State.RegisterOnPersisting(() =>
{
executionSequence.Add(1);
firstSubscription.Dispose(); // Remove itself from the collection
return Task.CompletedTask;
}, new TestRenderMode());

// Register additional callbacks to ensure they still get executed
persistenceManager.State.RegisterOnPersisting(() =>
{
executionSequence.Add(2);
return Task.CompletedTask;
}, new TestRenderMode());

persistenceManager.State.RegisterOnPersisting(() =>
{
executionSequence.Add(3);
return Task.CompletedTask;
}, new TestRenderMode());

// Act
await persistenceManager.PersistStateAsync(store, renderer);

// Assert
// All callbacks should be executed even though the first one removed itself
Assert.Contains(1, executionSequence);
Assert.Contains(2, executionSequence);
Assert.Contains(3, executionSequence);
Assert.Equal(3, executionSequence.Count);
}

[Fact]
public async Task RestoreStateAsync_ValidatesOnlySupportUpdatesWhenRestoreContextValueUpdate()
{
Expand Down
Loading