예제 #1
0
    public void ParameterViewSuppliedWithCascadingParametersCannotBeUsedAfterSynchronousReturn()
    {
        // Arrange
        var providedValue = "Initial value";
        var renderer      = new TestRenderer();
        var component     = new TestComponent(builder =>
        {
            builder.OpenComponent <CascadingValue <string> >(0);
            builder.AddAttribute(1, "Value", providedValue);
            builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder =>
            {
                childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0);
                childBuilder.CloseComponent();
            }));
            builder.CloseComponent();
        });

        // Initial render; capture nested component
        var componentId = renderer.AssignRootComponentId(component);

        component.TriggerRender();
        var firstBatch      = renderer.Batches.Single();
        var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId);

        // Re-render CascadingValue with new value, so it gets a new ParameterView
        providedValue = "Updated value";
        component.TriggerRender();
        Assert.Equal(2, renderer.Batches.Count);

        // It's no longer able to access anything in the ParameterView it just received
        var ex = Assert.Throws <InvalidOperationException>(nestedComponent.AttemptIllegalAccessToLastParameterView);

        Assert.Equal($"The {nameof(ParameterView)} instance can no longer be read because it has expired. {nameof(ParameterView)} can only be read synchronously and must not be stored for later use.", ex.Message);
    }
예제 #2
0
        public void WithCascadedAuthenticationState_DoesNotWrapOutputInCascadingAuthenticationState()
        {
            // Arrange
            var routeData     = new RouteData(typeof(TestPageWithNoAuthorization), EmptyParametersDictionary);
            var rootComponent = new AuthorizeRouteViewWithExistingCascadedAuthenticationState(
                _authenticationStateProvider.CurrentAuthStateTask,
                routeData);
            var rootComponentId = _renderer.AssignRootComponentId(rootComponent);

            // Act
            _renderer.RenderRootComponent(rootComponentId);

            // Assert
            var batch = _renderer.Batches.Single();
            var componentInstances = batch.ReferenceFrames
                                     .Where(f => f.FrameType == RenderTreeFrameType.Component)
                                     .Select(f => f.Component);

            Assert.Collection(componentInstances,
                              // This is the externally-supplied cascading value
                              component => Assert.IsType <CascadingValue <Task <AuthenticationState> > >(component),
                              component => Assert.IsType <AuthorizeRouteView>(component),

                              // This is the hierarchy inside the AuthorizeRouteView. It doesn't contain a
                              // further CascadingAuthenticationState
                              component => Assert.IsAssignableFrom <AuthorizeViewCore>(component),
                              component => Assert.IsType <LayoutView>(component),
                              component => Assert.IsType <TestPageWithNoAuthorization>(component));
        }
예제 #3
0
    public void CascadingValueThrowsIfFixedFlagChangesToFalse()
    {
        // Arrange
        var renderer  = new TestRenderer();
        var isFixed   = true;
        var component = new TestComponent(builder =>
        {
            builder.OpenComponent <CascadingValue <object> >(0);
            if (isFixed) // Showing also that "unset" is treated as "false"
            {
                builder.AddAttribute(1, "IsFixed", true);
            }
            builder.AddAttribute(2, "Value", new object());
            builder.CloseComponent();
        });

        renderer.AssignRootComponentId(component);
        component.TriggerRender();

        // Act/Assert
        isFixed = false;
        var ex = Assert.Throws <InvalidOperationException>(() => component.TriggerRender());

        Assert.Equal("The value of IsFixed cannot be changed dynamically.", ex.Message);
    }
예제 #4
0
        public async Task DoesNotRenderAfterOnInitAsyncTaskIsCancelledUsingCancellationToken()
        {
            // Arrange
            var renderer  = new TestRenderer();
            var component = new TestComponent()
            {
                Counter = 1
            };

            var cts = new CancellationTokenSource();

            cts.Cancel();
            component.OnInitAsyncLogic = async _ =>
            {
                await Task.Yield();

                cts.Token.ThrowIfCancellationRequested();
            };

            // Act
            var componentId = renderer.AssignRootComponentId(component);
            await renderer.RenderRootComponentAsync(componentId);

            // Assert
            // At least one call to StateHasChanged depending on how OnInitAsyncLogic gets scheduled.
            Assert.NotEmpty(renderer.Batches);
        }
예제 #5
0
        public async Task DoesNotRenderAfterOnParametersSetAsyncTaskIsCanceled()
        {
            // Arrange
            var renderer  = new TestRenderer();
            var component = new TestComponent()
            {
                Counter = 1
            };
            var onParametersSetTask = new TaskCompletionSource <object>();

            component.OnParametersSetAsyncLogic = _ => onParametersSetTask.Task;

            // Act
            var componentId = renderer.AssignRootComponentId(component);
            var renderTask  = renderer.RenderRootComponentAsync(componentId);

            // Assert
            Assert.Single(renderer.Batches);

            // Cancel task started by OnParametersSet
            component.Counter = 2;
            onParametersSetTask.SetCanceled();

            await renderTask;

            // Component should not be rendered again
            Assert.Single(renderer.Batches);
        }
        public void SuppliesSynchronouslyAvailableAuthStateToChildContent()
        {
            // Arrange: Service
            var services          = new ServiceCollection();
            var authStateProvider = new TestAuthStateProvider()
            {
                CurrentAuthStateTask = Task.FromResult(CreateAuthenticationState("Bert"))
            };

            services.AddSingleton <AuthenticationStateProvider>(authStateProvider);

            // Arrange: Renderer and component
            var renderer  = new TestRenderer(services.BuildServiceProvider());
            var component = new UseCascadingAuthenticationStateComponent();

            // Act
            renderer.AssignRootComponentId(component);
            component.TriggerRender();

            // Assert
            var batch = renderer.Batches.Single();
            var receiveAuthStateId   = batch.GetComponentFrames <ReceiveAuthStateComponent>().Single().ComponentId;
            var receiveAuthStateDiff = batch.DiffsByComponentId[receiveAuthStateId].Single();

            Assert.Collection(receiveAuthStateDiff.Edits, edit =>
            {
                Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
                AssertFrame.Text(
                    batch.ReferenceFrames[edit.ReferenceFrameIndex],
                    "Authenticated: True; Name: Bert; Pending: False; Renders: 1");
            });
        }
예제 #7
0
    public async Task Virtualize_DispatchesExceptionsFromItemsProviderThroughRenderer()
    {
        Virtualize <int> renderedVirtualize = null;

        var rootComponent = new VirtualizeTestHostcomponent
        {
            InnerContent = BuildVirtualize(10f, AlwaysThrowsItemsProvider <int>, null, virtualize => renderedVirtualize = virtualize)
        };

        var serviceProvider = new ServiceCollection()
                              .AddTransient((sp) => Mock.Of <IJSRuntime>())
                              .BuildServiceProvider();

        var testRenderer = new TestRenderer(serviceProvider);
        var componentId  = testRenderer.AssignRootComponentId(rootComponent);

        // Render to populate the component reference.
        await testRenderer.RenderRootComponentAsync(componentId);

        Assert.NotNull(renderedVirtualize);

        // Simulate a JS spacer callback.
        ((IVirtualizeJsCallbacks)renderedVirtualize).OnAfterSpacerVisible(10f, 50f, 100f);

        // Validate that the exception is dispatched through the renderer.
        var ex = await Assert.ThrowsAsync <InvalidOperationException>(async() => await testRenderer.RenderRootComponentAsync(componentId));

        Assert.Equal("Thrown from items provider.", ex.Message);
    }
        public async Task RespondsToValidationStateChangeNotifications()
        {
            // Arrange
            var model         = new TestModel();
            var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> >
            {
                EditContext     = new EditContext(model),
                ValueExpression = () => model.StringProperty
            };
            var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);
            var renderer        = new TestRenderer();
            var rootComponentId = renderer.AssignRootComponentId(rootComponent);
            await renderer.RenderRootComponentAsync(rootComponentId);

            // Initally, it rendered one batch and is valid
            var batch1           = renderer.Batches.Single();
            var componentFrame1  = batch1.GetComponentFrames <TestInputComponent <string> >().Single();
            var inputComponentId = componentFrame1.ComponentId;
            var component        = (TestInputComponent <string>)componentFrame1.Component;

            Assert.Equal("valid", component.CssClass);

            // Act: update the field state in the EditContext and notify
            var messageStore = new ValidationMessageStore(rootComponent.EditContext);

            messageStore.Add(fieldIdentifier, "Some message");
            await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged);

            // Assert: The input component rendered itself again and now has the new class
            var batch2 = renderer.Batches.Skip(1).Single();

            Assert.Equal(inputComponentId, batch2.DiffsByComponentId.Keys.Single());
            Assert.Equal("invalid", component.CssClass);
        }
예제 #9
0
        public void RespondsToChangeInAuthorizationState()
        {
            // Arrange
            var renderer      = new TestRenderer();
            var rootComponent = WrapInAuthorizeView(
                childContent: context => builder =>
                builder.AddContent(0, $"You are authenticated as {context.User.Identity.Name}"));

            rootComponent.AuthenticationState = CreateAuthenticationState("Nellie");

            // Render in initial state. From other tests, we know this renders
            // a single batch with the correct output.
            renderer.AssignRootComponentId(rootComponent);
            rootComponent.TriggerRender();
            var authorizeViewComponentId = renderer.Batches.Single()
                                           .GetComponentFrames <AuthorizeView>().Single().ComponentId;

            // Act
            rootComponent.AuthenticationState = CreateAuthenticationState("Ronaldo");
            rootComponent.TriggerRender();

            // Assert: It's only one new diff. We skip the intermediate "await" render state
            // because the task was completed synchronously.
            Assert.Equal(2, renderer.Batches.Count);
            var batch = renderer.Batches.Last();
            var diff  = batch.DiffsByComponentId[authorizeViewComponentId].Single();

            Assert.Collection(diff.Edits, edit =>
            {
                Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
                AssertFrame.Text(
                    batch.ReferenceFrames[edit.ReferenceFrameIndex],
                    "You are authenticated as Ronaldo");
            });
        }
예제 #10
0
        public async Task UserSpecifiedAriaValueIsNotChangedIfInvalid()
        {
            // Arrange// Arrange
            var model          = new TestModel();
            var invalidContext = new EditContext(model);

            var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> >
            {
                EditContext     = invalidContext,
                ValueExpression = () => model.StringProperty
            };

            rootComponent.AdditionalAttributes = new Dictionary <string, object>();
            rootComponent.AdditionalAttributes["aria-invalid"] = "userSpecifiedValue";

            var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);
            var messageStore    = new ValidationMessageStore(invalidContext);

            messageStore.Add(fieldIdentifier, "Test error message");

            var renderer        = new TestRenderer();
            var rootComponentId = renderer.AssignRootComponentId(rootComponent);
            await renderer.RenderRootComponentAsync(rootComponentId);

            // Initally, it rendered one batch and is valid
            var batch1           = renderer.Batches.Single();
            var componentFrame1  = batch1.GetComponentFrames <TestInputComponent <string> >().Single();
            var inputComponentId = componentFrame1.ComponentId;
            var component        = (TestInputComponent <string>)componentFrame1.Component;

            Assert.Equal("invalid", component.CssClass);
            Assert.NotNull(component.AdditionalAttributes);
            Assert.Equal(1, component.AdditionalAttributes.Count);
            Assert.Equal("userSpecifiedValue", component.AdditionalAttributes["aria-invalid"]);
        }
예제 #11
0
    public async Task AriaAttributeIsRenderedWhenTheValidationStateIsInvalidOnFirstRender()
    {
        // Arrange// Arrange
        var model          = new TestModel();
        var invalidContext = new EditContext(model);

        var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> >
        {
            EditContext     = invalidContext,
            ValueExpression = () => model.StringProperty
        };

        var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);
        var messageStore    = new ValidationMessageStore(invalidContext);

        messageStore.Add(fieldIdentifier, "Test error message");

        var renderer        = new TestRenderer();
        var rootComponentId = renderer.AssignRootComponentId(rootComponent);
        await renderer.RenderRootComponentAsync(rootComponentId);

        // Initally, it rendered one batch and is valid
        var batch1           = renderer.Batches.Single();
        var componentFrame1  = batch1.GetComponentFrames <TestInputComponent <string> >().Single();
        var inputComponentId = componentFrame1.ComponentId;
        var component        = (TestInputComponent <string>)componentFrame1.Component;

        Assert.Equal("invalid", component.CssClass);
        Assert.NotNull(component.AdditionalAttributes);
        Assert.Equal(1, component.AdditionalAttributes.Count);
        //Check for "true" see https://www.w3.org/TR/wai-aria-1.1/#aria-invalid
        Assert.Equal("true", component.AdditionalAttributes["aria-invalid"]);
    }
예제 #12
0
        public void RendersNotAuthorizedContentIfNotAuthorized()
        {
            // Arrange
            var renderer      = new TestRenderer();
            var rootComponent = WrapInAuthorizeView(
                childContent:
                context => builder => builder.AddContent(0, "This should not be rendered"),
                notAuthorizedContent:
                builder => builder.AddContent(0, "You are not authorized"));

            // Act
            renderer.AssignRootComponentId(rootComponent);
            rootComponent.TriggerRender();

            // Assert
            var diff = renderer.Batches.Single().GetComponentDiffs <AuthorizeView>().Single();

            Assert.Collection(diff.Edits, edit =>
            {
                Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
                AssertFrame.Text(
                    renderer.Batches.Single().ReferenceFrames[edit.ReferenceFrameIndex],
                    "You are not authorized");
            });
        }
예제 #13
0
        public async Task RendersAfterParametersSetAsyncTaskIsCompleted()
        {
            // Arrange
            var renderer  = new TestRenderer();
            var component = new TestComponent();

            component.Counter = 1;
            var parametersSetTask = new TaskCompletionSource <bool>();

            component.RunsBaseOnParametersSetAsync = false;
            component.OnParametersSetAsyncLogic    = c => parametersSetTask.Task;

            // Act
            var componentId = renderer.AssignRootComponentId(component);
            var renderTask  = renderer.RenderRootComponentAsync(componentId);

            // Assert
            Assert.Single(renderer.Batches);

            // Completes task started by OnParametersSetAsync
            component.Counter = 2;
            parametersSetTask.SetResult(true);

            await renderTask;

            // Component should be rendered again
            Assert.Equal(2, renderer.Batches.Count);
        }
예제 #14
0
        public async Task DoesNotRenderAfterOnInitAsyncTaskIsCancelled()
        {
            // Arrange
            var renderer  = new TestRenderer();
            var component = new TestComponent()
            {
                Counter = 1
            };
            var initTask = new TaskCompletionSource <object>();

            component.OnInitAsyncLogic = _ => initTask.Task;

            // Act
            var componentId = renderer.AssignRootComponentId(component);
            var renderTask  = renderer.RenderRootComponentAsync(componentId);

            // Assert
            Assert.False(renderTask.IsCompleted);
            Assert.Single(renderer.Batches);

            // Cancel task started by OnInitAsync
            component.Counter = 2;
            initTask.SetCanceled();

            await renderTask;

            // Component should only be rendered again due to
            // the call to StateHasChanged after SetParametersAsync
            Assert.Equal(2, renderer.Batches.Count);
        }
예제 #15
0
        public void DoesNotRenderAfterOnInitAsyncTaskIsCancelled()
        {
            // Arrange
            var renderer  = new TestRenderer();
            var component = new TestComponent()
            {
                Counter = 1
            };
            var initTask = new TaskCompletionSource <object>();

            component.OnInitAsyncLogic = _ => initTask.Task;

            // Act
            var componentId = renderer.AssignRootComponentId(component);

            renderer.RenderRootComponent(componentId);

            // Assert
            Assert.Single(renderer.Batches);

            // Cancel task started by OnInitAsync
            component.Counter = 2;
            initTask.SetCanceled();

            // Component should not be rendered again
            Assert.Single(renderer.Batches);
        }
예제 #16
0
        public async Task UnsubscribesFromValidationStateChangeNotifications()
        {
            // Arrange
            var model         = new TestModel();
            var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> >
            {
                EditContext     = new EditContext(model),
                ValueExpression = () => model.StringProperty
            };
            var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);
            var renderer        = new TestRenderer();
            var rootComponentId = renderer.AssignRootComponentId(rootComponent);
            await renderer.RenderRootComponentAsync(rootComponentId);

            var component = renderer.Batches.Single().GetComponentFrames <TestInputComponent <string> >().Single().Component;

            // Act: dispose, then update the field state in the EditContext and notify
            ((IDisposable)component).Dispose();
            var messageStore = new ValidationMessageStore(rootComponent.EditContext);

            messageStore.Add(fieldIdentifier, "Some message");
            await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged);

            // Assert: No additional render
            Assert.Empty(renderer.Batches.Skip(1));
        }
예제 #17
0
        public void RendersChildContentIfAuthorized()
        {
            // Arrange
            var renderer      = new TestRenderer();
            var rootComponent = WrapInAuthorizeView(
                childContent: context => builder =>
                builder.AddContent(0, $"You are authenticated as {context.User.Identity.Name}"));

            rootComponent.AuthenticationState = CreateAuthenticationState("Nellie");

            // Act
            renderer.AssignRootComponentId(rootComponent);
            rootComponent.TriggerRender();

            // Assert
            var diff = renderer.Batches.Single().GetComponentDiffs <AuthorizeView>().Single();

            Assert.Collection(diff.Edits, edit =>
            {
                Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
                AssertFrame.Text(
                    renderer.Batches.Single().ReferenceFrames[edit.ReferenceFrameIndex],
                    "You are authenticated as Nellie");
            });
        }
예제 #18
0
        public void StopsNotifyingDescendantsIfTheyAreRemoved()
        {
            // Arrange
            var providedValue          = "Initial value";
            var displayNestedComponent = true;
            var renderer  = new TestRenderer();
            var component = new TestComponent(builder =>
            {
                builder.OpenComponent <CascadingValue <string> >(0);
                builder.AddAttribute(1, "Value", providedValue);
                builder.AddAttribute(2, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder =>
                {
                    if (displayNestedComponent)
                    {
                        childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0);
                        childBuilder.AddAttribute(1, "RegularParameter", "Goodbye");
                        childBuilder.CloseComponent();
                    }
                }));
                builder.CloseComponent();
            });

            // Act 1: Initial render; capture nested component ID
            var componentId = renderer.AssignRootComponentId(component);

            component.TriggerRender();
            var firstBatch      = renderer.Batches.Single();
            var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId);

            Assert.Equal(1, nestedComponent.NumSetParametersCalls);
            Assert.Equal(1, nestedComponent.NumRenders);

            // Act/Assert 2: Re-render the CascadingValue; observe nested component wasn't re-rendered
            providedValue          = "Updated value";
            displayNestedComponent = false; // Remove the nested componet
            component.TriggerRender();

            // Assert: We did not render the nested component now it's been removed
            Assert.Equal(2, renderer.Batches.Count);
            var secondBatch = renderer.Batches[1];

            Assert.Equal(1, nestedComponent.NumRenders);
            Assert.Equal(2, secondBatch.DiffsByComponentId.Count); // Root + CascadingValue, but not nested one

            // We *did* send updated params during the first render where it was removed,
            // because the params are sent before the disposal logic runs. We could avoid
            // this by moving the notifications into the OnAfterRender phase, but then we'd
            // often render descendants twice (once because they are descendants and some
            // direct parameter might have changed, then once because a cascading parameter
            // changed). We can't have it both ways, so optimize for the case when the
            // nested component *hasn't* just been removed.
            Assert.Equal(2, nestedComponent.NumSetParametersCalls);

            // Act 3: However, after disposal, the subscription is removed, so we won't send
            // updated params on subsequent CascadingValue renders.
            providedValue = "Updated value 2";
            component.TriggerRender();
            Assert.Equal(2, nestedComponent.NumSetParametersCalls);
        }
예제 #19
0
        private static async Task <IEnumerable <TestInputRadio> > RenderAndGetTestInputComponentAsync(TestInputRadioHostComponent <TestEnum> rootComponent)
        {
            var testRenderer = new TestRenderer();
            var componentId  = testRenderer.AssignRootComponentId(rootComponent);
            await testRenderer.RenderRootComponentAsync(componentId);

            return(FindInputRadioComponents(testRenderer.Batches.Single()));
        }
예제 #20
0
        private static async Task <TComponent> RenderAndGetTestInputComponentAsync <TValue, TComponent>(TestInputHostComponent <TValue, TComponent> hostComponent) where TComponent : TestInputComponent <TValue>
        {
            var testRenderer = new TestRenderer();
            var componentId  = testRenderer.AssignRootComponentId(hostComponent);
            await testRenderer.RenderRootComponentAsync(componentId);

            return(FindComponent <TComponent>(testRenderer.Batches.Single()));
        }
예제 #21
0
        private static async Task <EditForm> RenderAndGetTestEditFormComponentAsync(TestEditFormHostComponent hostComponent)
        {
            var testRenderer = new TestRenderer();
            var componentId  = testRenderer.AssignRootComponentId(hostComponent);
            await testRenderer.RenderRootComponentAsync(componentId);

            return(FindEditFormComponent(testRenderer.Batches.Single()));
        }
예제 #22
0
        public void RendersAuthorizingContentUntilAuthorizationCompleted()
        {
            // Arrange
            var @event   = new ManualResetEventSlim();
            var renderer = new TestRenderer()
            {
                OnUpdateDisplayComplete = () => { @event.Set(); },
            };
            var rootComponent = WrapInAuthorizeView(
                authorizingContent: builder => builder.AddContent(0, "Auth pending..."),
                authorizedContent: context => builder => builder.AddContent(0, $"Hello, {context.User.Identity.Name}!"));
            var authTcs = new TaskCompletionSource <AuthenticationState>();

            rootComponent.AuthenticationState = authTcs.Task;

            // Act/Assert 1: Auth pending
            renderer.AssignRootComponentId(rootComponent);
            rootComponent.TriggerRender();
            var batch1 = renderer.Batches.Single();
            var authorizeViewComponentId = batch1.GetComponentFrames <AuthorizeView>().Single().ComponentId;
            var diff1 = batch1.DiffsByComponentId[authorizeViewComponentId].Single();

            Assert.Collection(diff1.Edits, edit =>
            {
                Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
                AssertFrame.Text(
                    batch1.ReferenceFrames[edit.ReferenceFrameIndex],
                    "Auth pending...");
            });

            // Act/Assert 2: Auth process completes asynchronously
            @event.Reset();
            authTcs.SetResult(CreateAuthenticationState("Monsieur").Result);

            // We need to wait here because the continuations of SetResult will be scheduled to run asynchronously.
            @event.Wait(Timeout);

            Assert.Equal(2, renderer.Batches.Count);
            var batch2 = renderer.Batches[1];
            var diff2  = batch2.DiffsByComponentId[authorizeViewComponentId].Single();

            Assert.Collection(diff2.Edits,
                              edit =>
            {
                Assert.Equal(RenderTreeEditType.RemoveFrame, edit.Type);
                Assert.Equal(0, edit.SiblingIndex);
            },
                              edit =>
            {
                Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
                Assert.Equal(0, edit.SiblingIndex);
                AssertFrame.Text(
                    batch2.ReferenceFrames[edit.ReferenceFrameIndex],
                    "Hello, Monsieur!");
            });
        }
예제 #23
0
        public void SuppliesAsynchronouslyAvailableAuthStateToChildContent()
        {
            // Arrange: Service
            var services = new ServiceCollection();
            var authStateTaskCompletionSource = new TaskCompletionSource <AuthenticationState>();
            var authStateProvider             = new TestAuthenticationStateProvider()
            {
                CurrentAuthStateTask = authStateTaskCompletionSource.Task
            };

            services.AddSingleton <AuthenticationStateProvider>(authStateProvider);

            // Arrange: Renderer and component
            var renderer  = new TestRenderer(services.BuildServiceProvider());
            var component = new UseCascadingAuthenticationStateComponent();

            // Act 1: Initial synchronous render
            renderer.AssignRootComponentId(component);
            component.TriggerRender();

            // Assert 1: Empty state
            var batch1 = renderer.Batches.Single();
            var receiveAuthStateFrame     = batch1.GetComponentFrames <ReceiveAuthStateComponent>().Single();
            var receiveAuthStateId        = receiveAuthStateFrame.ComponentId;
            var receiveAuthStateComponent = (ReceiveAuthStateComponent)receiveAuthStateFrame.Component;
            var receiveAuthStateDiff1     = batch1.DiffsByComponentId[receiveAuthStateId].Single();

            Assert.Collection(receiveAuthStateDiff1.Edits, edit =>
            {
                Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
                AssertFrame.Text(
                    batch1.ReferenceFrames[edit.ReferenceFrameIndex],
                    "Authenticated: False; Name: ; Pending: True; Renders: 1");
            });

            // Act/Assert 2: Auth state fetch task completes in background
            // No new renders yet, because the cascading parameter itself hasn't changed
            authStateTaskCompletionSource.SetResult(CreateAuthenticationState("Bert"));
            Assert.Single(renderer.Batches);

            // Act/Assert 3: Refresh display
            receiveAuthStateComponent.TriggerRender();
            Assert.Equal(2, renderer.Batches.Count);
            var batch2 = renderer.Batches.Last();
            var receiveAuthStateDiff2 = batch2.DiffsByComponentId[receiveAuthStateId].Single();

            Assert.Collection(receiveAuthStateDiff2.Edits, edit =>
            {
                Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
                AssertFrame.Text(
                    batch2.ReferenceFrames[edit.ReferenceFrameIndex],
                    "Authenticated: True; Name: Bert; Pending: False; Renders: 2");
            });
        }
예제 #24
0
    public async Task RunsOnAfterRenderAsync_AfterRenderingCompletes()
    {
        // Arrange
        var renderer  = new TestRenderer();
        var component = new TestComponent()
        {
            Counter = 1
        };

        var onAfterRenderCompleted = false;
        var tcs = new TaskCompletionSource <object>();

        component.OnAfterRenderAsyncLogic = async(c, firstRender) =>
        {
            Assert.True(firstRender);
            Assert.Single(renderer.Batches);
            onAfterRenderCompleted = true;
            await tcs.Task;
        };

        // Act
        var componentId = renderer.AssignRootComponentId(component);
        var renderTask  = renderer.RenderRootComponentAsync(componentId);

        // Assert
        tcs.SetResult(null);
        await renderTask;

        Assert.True(onAfterRenderCompleted);

        // Component should not be rendered again. OnAfterRenderAsync doesn't do that.
        Assert.Single(renderer.Batches);

        // Act: Render again!
        onAfterRenderCompleted = false;
        tcs = new TaskCompletionSource <object>();
        component.OnAfterRenderAsyncLogic = async(c, firstRender) =>
        {
            Assert.False(firstRender);
            Assert.Equal(2, renderer.Batches.Count);
            onAfterRenderCompleted = true;
            await tcs.Task;
        };

        renderTask = renderer.RenderRootComponentAsync(componentId);

        // Assert
        tcs.SetResult(null);
        await renderTask;

        Assert.True(onAfterRenderCompleted);
        Assert.Equal(2, renderer.Batches.Count);
    }
예제 #25
0
        public async Task RendersAfterParametersSetAndInitAsyncTasksAreCompleted()
        {
            // Arrange
            var @event = new ManualResetEventSlim();

            var renderer = new TestRenderer()
            {
                OnUpdateDisplayComplete = () => { @event.Set(); },
            };
            var component = new TestComponent();

            component.Counter = 1;
            var initTask          = new TaskCompletionSource <bool>();
            var parametersSetTask = new TaskCompletionSource <bool>();

            component.RunsBaseOnInitAsync          = true;
            component.RunsBaseOnParametersSetAsync = true;
            component.OnInitAsyncLogic             = c => initTask.Task;
            component.OnParametersSetAsyncLogic    = c => parametersSetTask.Task;

            // Act
            var componentId = renderer.AssignRootComponentId(component);
            var renderTask  = renderer.RenderRootComponentAsync(componentId);

            // Assert
            // A rendering should have happened after the synchronous execution of Init
            Assert.Single(renderer.Batches);

            @event.Reset();

            // Completes task started by OnInitAsync
            component.Counter = 2;
            initTask.SetResult(true);

            // We need to wait here, because the continuation from SetResult needs to be scheduled.
            @event.Wait(Timeout);
            @event.Reset();

            // Component should be rendered once, after set parameters
            Assert.Equal(2, renderer.Batches.Count);

            // Completes task started by OnParametersSetAsync
            component.Counter = 3;
            parametersSetTask.SetResult(false);

            await renderTask;

            Assert.True(@event.IsSet);

            // Component should be rendered again
            // after the async part of onparameterssetasync completes
            Assert.Equal(3, renderer.Batches.Count);
        }
        public void RequiresTypeParameter()
        {
            var instance    = new DynamicComponent();
            var renderer    = new TestRenderer();
            var componentId = renderer.AssignRootComponentId(instance);

            var ex = Assert.Throws <InvalidOperationException>(
                () => renderer.RenderRootComponent(componentId, ParameterView.Empty));

            Assert.StartsWith(
                $"{ nameof(DynamicComponent)} requires a non-null value for the parameter {nameof(DynamicComponent.Type)}.",
                ex.Message);
        }
예제 #27
0
        public async Task ThrowsOnFirstRenderIfNoEditContextIsSupplied()
        {
            // Arrange
            var inputComponent = new TestInputComponent <string>();
            var testRenderer   = new TestRenderer();
            var componentId    = testRenderer.AssignRootComponentId(inputComponent);

            // Act/Assert
            var ex = await Assert.ThrowsAsync <InvalidOperationException>(
                () => testRenderer.RenderRootComponentAsync(componentId));

            Assert.StartsWith($"{typeof(TestInputComponent<string>)} requires a cascading parameter of type {nameof(EditContext)}", ex.Message);
        }
예제 #28
0
        public async Task ThrowsIfBothEditContextAndModelAreNull()
        {
            // Arrange
            var editForm     = new EditForm();
            var testRenderer = new TestRenderer();
            var componentId  = testRenderer.AssignRootComponentId(editForm);

            // Act/Assert
            var ex = await Assert.ThrowsAsync <InvalidOperationException>(
                () => testRenderer.RenderRootComponentAsync(componentId));

            Assert.StartsWith($"{nameof(EditForm)} requires either a {nameof(EditForm.Model)} parameter, or an {nameof(EditContext)} parameter, please provide one of these.", ex.Message);
        }
예제 #29
0
        public void ThrowsIfBothChildContentAndAuthorizedContentProvided()
        {
            // Arrange
            var renderer      = new TestRenderer();
            var rootComponent = WrapInAuthorizeView(
                authorizedContent: context => builder => { },
                childContent: context => builder => { });

            // Act/Assert
            renderer.AssignRootComponentId(rootComponent);
            var ex = Assert.Throws <InvalidOperationException>(() =>
                                                               rootComponent.TriggerRender());

            Assert.Equal("When using AuthorizeView, do not specify both 'Authorized' and 'ChildContent'.", ex.Message);
        }
예제 #30
0
        public void RetainsCascadingParametersWhenUpdatingDirectParameters()
        {
            // Arrange
            var renderer = new TestRenderer();
            var regularParameterValue = "Initial value";
            var component             = new TestComponent(builder =>
            {
                builder.OpenComponent <CascadingValue <string> >(0);
                builder.AddAttribute(1, "Value", "Hello");
                builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder =>
                {
                    childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0);
                    childBuilder.AddAttribute(1, "RegularParameter", regularParameterValue);
                    childBuilder.CloseComponent();
                }));
                builder.CloseComponent();
            });

            // Act 1: Render in initial state
            var componentId = renderer.AssignRootComponentId(component);

            component.TriggerRender();

            // Capture the nested component so we can verify the update later
            var firstBatch      = renderer.Batches.Single();
            var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId);

            Assert.Equal(1, nestedComponent.NumRenders);

            // Act 2: Render again with updated regular parameter
            regularParameterValue = "Changed value";
            component.TriggerRender();

            // Assert
            Assert.Equal(2, renderer.Batches.Count);
            var secondBatch         = renderer.Batches[1];
            var nestedComponentDiff = secondBatch.DiffsByComponentId[nestedComponentId].Single();

            // The nested component was rendered with the correct parameters
            Assert.Collection(nestedComponentDiff.Edits,
                              edit =>
            {
                Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
                Assert.Equal(0, edit.ReferenceFrameIndex);     // This is the only change
                AssertFrame.Text(secondBatch.ReferenceFrames[0], "CascadingParameter=Hello; RegularParameter=Changed value");
            });
            Assert.Equal(2, nestedComponent.NumRenders);
        }