Skip to content

Commit 6b82560

Browse files
Copilotjaviercn
andcommitted
Add E2E test pages for conditional component rendering to test persistent state restoration
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
1 parent f54388f commit 6b82560

File tree

6 files changed

+1124
-154
lines changed

6 files changed

+1124
-154
lines changed

src/Components/Components/test/PersistentValueProviderComponentSubscriptionTests.cs

Lines changed: 0 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -708,158 +708,4 @@ public void Constructor_WorksCorrectly_ForPublicProperty()
708708
Assert.NotNull(subscription);
709709
subscription.Dispose();
710710
}
711-
712-
[Fact]
713-
public async Task ComponentRecreation_PreservesPersistedState_WhenComponentIsRecreatedDuringNavigation()
714-
{
715-
// This test simulates the scenario where a component is destroyed and recreated (like during navigation)
716-
// and verifies that the persisted state is correctly restored in the new component instance
717-
718-
// Arrange
719-
var appState = new Dictionary<string, byte[]>();
720-
var manager = new ComponentStatePersistenceManager(NullLogger<ComponentStatePersistenceManager>.Instance);
721-
var serviceProvider = PersistentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(new ServiceCollection())
722-
.AddSingleton(manager)
723-
.AddSingleton(manager.State)
724-
.AddFakeLogging()
725-
.BuildServiceProvider();
726-
var renderer = new TestRenderer(serviceProvider);
727-
var provider = (PersistentStateValueProvider)renderer.ServiceProviderCascadingValueSuppliers.Single();
728-
var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
729-
730-
// Setup initial persisted state
731-
var component1 = new TestComponent { State = "initial-property-value" };
732-
var componentId1 = renderer.AssignRootComponentId(component1);
733-
var componentState1 = renderer.GetComponentState(component1);
734-
var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState1, nameof(TestComponent.State));
735-
736-
appState[key] = JsonSerializer.SerializeToUtf8Bytes("persisted-value-from-previous-session", JsonSerializerOptions.Web);
737-
await manager.RestoreStateAsync(new TestStore(appState), RestoreContext.InitialValue);
738-
739-
// Act & Assert - First component instance should get the persisted value
740-
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId1, ParameterView.Empty));
741-
Assert.Equal("persisted-value-from-previous-session", component1.State);
742-
743-
// Simulate component destruction (like during navigation away)
744-
renderer.RemoveRootComponent(componentId1);
745-
746-
// Simulate component recreation (like during navigation back) - NEW SUBSCRIPTION CREATED
747-
var component2 = new TestComponent { State = "new-component-initial-value" };
748-
var componentId2 = renderer.AssignRootComponentId(component2);
749-
var componentState2 = renderer.GetComponentState(component2);
750-
751-
// Verify the key is the same (important for components without @key)
752-
var key2 = PersistentStateValueProviderKeyResolver.ComputeKey(componentState2, nameof(TestComponent.State));
753-
Assert.Equal(key, key2);
754-
755-
// The state should still be available for restoration
756-
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId2, ParameterView.Empty));
757-
758-
// Assert - The new component instance should get the same persisted value
759-
Assert.Equal("persisted-value-from-previous-session", component2.State);
760-
}
761-
762-
[Fact]
763-
public async Task ComponentRecreation_WithStateUpdates_PreservesCorrectValueTransitionSequence()
764-
{
765-
// This test simulates the full lifecycle with component recreation and state updates
766-
// following the pattern from GetOrComputeLastValue_FollowsCorrectValueTransitionSequence
767-
// but with subscription recreation between state restorations
768-
769-
// Arrange
770-
var appState = new Dictionary<string, byte[]>();
771-
var manager = new ComponentStatePersistenceManager(NullLogger<ComponentStatePersistenceManager>.Instance);
772-
var serviceProvider = PersistentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(new ServiceCollection())
773-
.AddSingleton(manager)
774-
.AddSingleton(manager.State)
775-
.AddFakeLogging()
776-
.BuildServiceProvider();
777-
var renderer = new TestRenderer(serviceProvider);
778-
var provider = (PersistentStateValueProvider)renderer.ServiceProviderCascadingValueSuppliers.Single();
779-
var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
780-
781-
// First component lifecycle
782-
var component1 = new TestComponent { State = "initial-property-value" };
783-
var componentId1 = renderer.AssignRootComponentId(component1);
784-
var componentState1 = renderer.GetComponentState(component1);
785-
var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState1, nameof(TestComponent.State));
786-
787-
// Pre-populate with first persisted value
788-
appState[key] = JsonSerializer.SerializeToUtf8Bytes("first-restored-value", JsonSerializerOptions.Web);
789-
await manager.RestoreStateAsync(new TestStore(appState), RestoreContext.InitialValue);
790-
791-
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId1, ParameterView.Empty));
792-
793-
// Act & Assert - First component gets restored value
794-
Assert.Equal("first-restored-value", component1.State);
795-
796-
// Update component property
797-
component1.State = "updated-by-component-1";
798-
Assert.Equal("updated-by-component-1", provider.GetCurrentValue(componentState1, cascadingParameterInfo));
799-
800-
// Simulate component destruction and recreation (NEW SUBSCRIPTION CREATED)
801-
renderer.RemoveRootComponent(componentId1);
802-
803-
var component2 = new TestComponent { State = "new-component-initial-value" };
804-
var componentId2 = renderer.AssignRootComponentId(component2);
805-
var componentState2 = renderer.GetComponentState(component2);
806-
807-
// Restore state with a different value
808-
appState.Clear();
809-
appState[key] = JsonSerializer.SerializeToUtf8Bytes("second-restored-value", JsonSerializerOptions.Web);
810-
await manager.RestoreStateAsync(new TestStore(appState), RestoreContext.ValueUpdate);
811-
812-
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId2, ParameterView.Empty));
813-
814-
// Assert - New component gets the updated restored value
815-
Assert.Equal("second-restored-value", component2.State);
816-
817-
// Continue with property updates on the new component
818-
component2.State = "updated-by-component-2";
819-
Assert.Equal("updated-by-component-2", provider.GetCurrentValue(componentState2, cascadingParameterInfo));
820-
}
821-
822-
[Fact]
823-
public async Task ComponentRecreation_WithSkipNotifications_StillRestoresCorrectly()
824-
{
825-
// This test verifies that the fix works even when skipNotifications is true during component recreation,
826-
// which is the core scenario that was broken before our fix
827-
828-
// Arrange
829-
var appState = new Dictionary<string, byte[]>();
830-
var manager = new ComponentStatePersistenceManager(NullLogger<ComponentStatePersistenceManager>.Instance);
831-
var serviceProvider = PersistentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(new ServiceCollection())
832-
.AddSingleton(manager)
833-
.AddSingleton(manager.State)
834-
.AddFakeLogging()
835-
.BuildServiceProvider();
836-
var renderer = new TestRenderer(serviceProvider);
837-
var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
838-
839-
// Setup persisted state
840-
var component1 = new TestComponent { State = "component-initial-value" };
841-
var componentId1 = renderer.AssignRootComponentId(component1);
842-
var componentState1 = renderer.GetComponentState(component1);
843-
var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState1, nameof(TestComponent.State));
844-
845-
appState[key] = JsonSerializer.SerializeToUtf8Bytes("persisted-value", JsonSerializerOptions.Web);
846-
await manager.RestoreStateAsync(new TestStore(appState), RestoreContext.InitialValue);
847-
848-
// First component gets the persisted value
849-
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId1, ParameterView.Empty));
850-
Assert.Equal("persisted-value", component1.State);
851-
852-
// Destroy and recreate component (simulating navigation or component without @key)
853-
renderer.RemoveRootComponent(componentId1);
854-
855-
// Create new component instance - this will create a NEW SUBSCRIPTION
856-
var component2 = new TestComponent { State = "different-initial-value" };
857-
var componentId2 = renderer.AssignRootComponentId(component2);
858-
859-
// Render the new component - this should restore the persisted value even if skipNotifications is true
860-
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId2, ParameterView.Empty));
861-
862-
// Assert - The new component should get the persisted value, not its initial property value
863-
Assert.Equal("persisted-value", component2.State);
864-
}
865711
}

0 commit comments

Comments
 (0)