TestRenderer.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Runtime.ExceptionServices;
  7. using System.Threading.Tasks;
  8. using Microsoft.AspNetCore.Components.Rendering;
  9. using Xunit;
  10. namespace Microsoft.AspNetCore.Components.Test.Helpers
  11. {
  12. public class TestRenderer : Renderer
  13. {
  14. public TestRenderer() : this(new TestServiceProvider())
  15. {
  16. }
  17. public TestRenderer(IDispatcher dispatcher) : base(new TestServiceProvider(), dispatcher)
  18. {
  19. }
  20. public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider, new RendererSynchronizationContext())
  21. {
  22. }
  23. public Action<RenderBatch> OnUpdateDisplay { get; set; }
  24. public List<CapturedBatch> Batches { get; }
  25. = new List<CapturedBatch>();
  26. public List<Exception> HandledExceptions { get; } = new List<Exception>();
  27. public bool ShouldHandleExceptions { get; set; }
  28. public new int AssignRootComponentId(IComponent component)
  29. => base.AssignRootComponentId(component);
  30. public void RenderRootComponent(int componentId, ParameterCollection? parameters = default)
  31. {
  32. var task = InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters ?? ParameterCollection.Empty));
  33. UnwrapTask(task);
  34. }
  35. public new Task RenderRootComponentAsync(int componentId)
  36. => InvokeAsync(() => base.RenderRootComponentAsync(componentId));
  37. public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters)
  38. => InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters));
  39. public new Task DispatchEventAsync(int componentId, int eventHandlerId, UIEventArgs args)
  40. {
  41. var task = InvokeAsync(() => base.DispatchEventAsync(componentId, eventHandlerId, args));
  42. return UnwrapTask(task);
  43. }
  44. private static Task UnwrapTask(Task task)
  45. {
  46. // This should always be run synchronously
  47. Assert.True(task.IsCompleted);
  48. if (task.IsFaulted)
  49. {
  50. var exception = task.Exception.Flatten().InnerException;
  51. while (exception is AggregateException e)
  52. {
  53. exception = e.InnerException;
  54. }
  55. ExceptionDispatchInfo.Capture(exception).Throw();
  56. }
  57. return task;
  58. }
  59. public T InstantiateComponent<T>() where T : IComponent
  60. => (T)InstantiateComponent(typeof(T));
  61. protected override void HandleException(Exception exception)
  62. {
  63. if (!ShouldHandleExceptions)
  64. {
  65. throw exception;
  66. }
  67. HandledExceptions.Add(exception);
  68. }
  69. protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
  70. {
  71. OnUpdateDisplay?.Invoke(renderBatch);
  72. var capturedBatch = new CapturedBatch();
  73. Batches.Add(capturedBatch);
  74. for (var i = 0; i < renderBatch.UpdatedComponents.Count; i++)
  75. {
  76. ref var renderTreeDiff = ref renderBatch.UpdatedComponents.Array[i];
  77. capturedBatch.AddDiff(renderTreeDiff);
  78. }
  79. // Clone other data, as underlying storage will get reused by later batches
  80. capturedBatch.ReferenceFrames = renderBatch.ReferenceFrames.ToArray();
  81. capturedBatch.DisposedComponentIDs = renderBatch.DisposedComponentIDs.ToList();
  82. // This renderer updates the UI synchronously, like the WebAssembly one.
  83. // To test async UI updates, subclass TestRenderer and override UpdateDisplayAsync.
  84. return Task.CompletedTask;
  85. }
  86. }
  87. }