Skip to content

Commit c3c2802

Browse files
committed
Moved rendering events to core
1 parent 6c7e3e8 commit c3c2802

43 files changed

Lines changed: 494 additions & 356 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using Bunit.Rendering.RenderEvents;
3+
4+
namespace Bunit
5+
{
6+
/// <summary>
7+
/// Represents a rendered fragment.
8+
/// </summary>
9+
public interface IRenderedFragment
10+
{
11+
/// <summary>
12+
/// Gets the id of the rendered component or fragment.
13+
/// </summary>
14+
int ComponentId { get; }
15+
16+
/// <summary>
17+
/// Gets an <see cref="IObservable{RenderEvent}"/> which will provide subscribers with <see cref="RenderEvent"/>s
18+
/// whenever the <see cref="IRenderedFragment"/> is rendered.
19+
/// </summary>
20+
IObservable<RenderEvent> RenderEvents { get; }
21+
22+
/// <summary>
23+
/// Gets the <see cref="IServiceProvider"/> used when rendering the component.
24+
/// </summary>
25+
IServiceProvider Services { get; }
26+
}
27+
}
File renamed without changes.

src/bunit.core/Rendering/RazorTestRenderer.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ public async Task<IReadOnlyList<RazorTest>> GetRazorTestsFromComponent(Type comp
3939
return GetRazorTests<RazorTest>(componentId);
4040
}
4141

42+
/// <summary>
43+
/// Renders the provided <paramref name="renderFragment"/>.
44+
/// </summary>
45+
/// <typeparam name="TComponent">The type of components to find in the render tree after renderinger.</typeparam>
46+
/// <param name="renderFragment">The <see cref="RenderFragment"/> to render.</param>
47+
/// <returns>A list of <typeparamref name="TComponent"/> found in the <paramref name="renderFragment"/>'s render tree.</returns>
48+
public async Task<IReadOnlyList<TComponent>> RenderAndGetTestComponents<TComponent>(RenderFragment renderFragment)
49+
where TComponent : FragmentBase
50+
{
51+
var componentId = await RenderFragmentInsideWrapper(renderFragment);
52+
return GetRazorTests<TComponent>(componentId);
53+
}
54+
4255
private async Task<int> RenderComponent(Type componentType)
4356
{
4457
var component = InstantiateComponent(componentType);
@@ -47,13 +60,6 @@ private async Task<int> RenderComponent(Type componentType)
4760
return componentId;
4861
}
4962

50-
public async Task<IReadOnlyList<TComponent>> RenderAndGetTestComponents<TComponent>(RenderFragment renderFragment)
51-
where TComponent : FragmentBase
52-
{
53-
var componentId = await RenderFragmentInsideWrapper(renderFragment);
54-
return GetRazorTests<TComponent>(componentId);
55-
}
56-
5763
private async Task<int> RenderFragmentInsideWrapper(RenderFragment renderFragment)
5864
{
5965
var wrapper = new WrapperComponent();

src/bunit.core/Rendering/RenderEventPublisher.cs

Lines changed: 0 additions & 108 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace Bunit.Rendering.RenderEvents
4+
{
5+
/// <inheritdoc/>
6+
public sealed class ComponentChangeEventSubscriber : ConcurrentRenderEventSubscriber
7+
{
8+
private readonly int _targetComponentId;
9+
10+
/// <summary>
11+
/// Creates an instance of the <see cref="ComponentChangeEventSubscriber"/>.
12+
/// </summary>
13+
public ComponentChangeEventSubscriber(IRenderedFragment testTarget, Action<RenderEvent>? onChange = null, Action? onCompleted = null)
14+
: base((testTarget ?? throw new ArgumentNullException(nameof(testTarget))).RenderEvents, onChange, onCompleted)
15+
{
16+
_targetComponentId = testTarget.ComponentId;
17+
}
18+
19+
/// <inheritdoc/>
20+
public override void OnNext(RenderEvent value)
21+
{
22+
if (value.HasChangesTo(_targetComponentId))
23+
base.OnNext(value);
24+
}
25+
}
26+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace Bunit.Rendering.RenderEvents
5+
{
6+
/// <summary>
7+
/// Represents a subscriber to <see cref="RenderEvent"/>s, published by
8+
/// the <see cref="TestRenderer"/>.
9+
/// </summary>
10+
public class ConcurrentRenderEventSubscriber : IObserver<RenderEvent>
11+
{
12+
private readonly IDisposable _unsubscriber;
13+
private readonly Action<RenderEvent>? _onRender;
14+
private readonly Action? _onCompleted;
15+
private int _renderCount;
16+
private bool _isCompleted;
17+
private RenderEvent? _latestRenderEvent;
18+
19+
/// <summary>
20+
/// Gets the number of renders that have occurred since subscribing.
21+
/// </summary>
22+
public int RenderCount => Volatile.Read(ref _renderCount);
23+
24+
/// <summary>
25+
/// Gets whether the <see cref="TestRenderer"/> is disposed an no more
26+
/// renders will happen.
27+
/// </summary>
28+
public bool IsCompleted => Volatile.Read(ref _isCompleted);
29+
30+
/// <summary>
31+
/// Gets the latests <see cref="RenderEvent"/> received by the <see cref="TestRenderer"/>.
32+
/// </summary>
33+
public RenderEvent? LatestRenderEvent => Volatile.Read(ref _latestRenderEvent);
34+
35+
/// <summary>
36+
/// Creates an instance of the <see cref="ConcurrentRenderEventSubscriber"/>, and
37+
/// subscribes to the provided <paramref name="observable"/>.
38+
/// </summary>
39+
/// <param name="observable">The observable to observe.</param>
40+
/// <param name="onRender">A callback to invoke when a <see cref="RenderEvent"/> is received.</param>
41+
/// <param name="onCompleted">A callback to invoke when no more renders will happen.</param>
42+
public ConcurrentRenderEventSubscriber(IObservable<RenderEvent> observable, Action<RenderEvent>? onRender = null, Action? onCompleted = null)
43+
{
44+
if (observable is null)
45+
throw new ArgumentNullException(nameof(observable));
46+
_onRender = onRender;
47+
_onCompleted = onCompleted;
48+
_unsubscriber = observable.Subscribe(this);
49+
}
50+
51+
/// <summary>
52+
/// Unsubscribes from the observable.
53+
/// </summary>
54+
public void Unsubscribe()
55+
{
56+
_unsubscriber.Dispose();
57+
}
58+
59+
/// <inheritdoc/>
60+
public virtual void OnNext(RenderEvent value)
61+
{
62+
Interlocked.Increment(ref _renderCount);
63+
Volatile.Write(ref _latestRenderEvent, value);
64+
_onRender?.Invoke(value);
65+
}
66+
67+
/// <inheritdoc/>
68+
public virtual void OnCompleted()
69+
{
70+
Volatile.Write(ref _isCompleted, true);
71+
_onCompleted?.Invoke();
72+
}
73+
74+
/// <inheritdoc/>
75+
public virtual void OnError(Exception exception)
76+
=> throw new AggregateException("The renderer throw an error", exception);
77+
}
78+
}

src/bunit.core/Rendering/RenderEvent.cs renamed to src/bunit.core/Rendering/RenderEvents/RenderEvent.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System;
22
using Microsoft.AspNetCore.Components.RenderTree;
33

4-
namespace Bunit.Rendering
4+
namespace Bunit.Rendering.RenderEvents
55
{
66
/// <summary>
7-
/// Represents a render event for a <see cref="IRenderedFragmentCore"/> or generally from the <see cref="TestRendererOld"/>.
7+
/// Represents a render event from a <see cref="TestRenderer"/>.
88
/// </summary>
99
public sealed class RenderEvent
1010
{
@@ -26,18 +26,18 @@ public RenderEvent(in RenderBatch renderBatch, TestRenderer renderer)
2626
}
2727

2828
/// <summary>
29-
/// Checks whether the <paramref name="renderedFragment"/> or one or more of
29+
/// Checks whether the a component with <paramref name="componentId"/> or one or more of
3030
/// its sub components was changed during the <see cref="RenderEvent"/>.
3131
/// </summary>
32-
/// <param name="renderedFragment">Component to check for updates to.</param>
32+
/// <param name="componentId">Id of component to check for updates to.</param>
3333
/// <returns>True if <see cref="RenderEvent"/> contains updates to component, false otherwise.</returns>
3434
public bool HasChangesTo(int componentId) => HasChangesToRoot(componentId);
3535

3636
/// <summary>
37-
/// Checks whether the <paramref name="renderedFragment"/> or one or more of
37+
/// Checks whether the a component with <paramref name="componentId"/> or one or more of
3838
/// its sub components was rendered during the <see cref="RenderEvent"/>.
3939
/// </summary>
40-
/// <param name="renderedFragment">Component to check if rendered.</param>
40+
/// <param name="componentId">Id of component to check if rendered.</param>
4141
/// <returns>True if the component or a sub component rendered, false otherwise.</returns>
4242
public bool DidComponentRender(int componentId) => DidComponentRenderRoot(componentId);
4343

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
3+
namespace Bunit.Rendering.RenderEvents
4+
{
5+
/// <summary>
6+
/// Represents a simple filter for <see cref="RenderEventObservable"/>.
7+
/// </summary>
8+
public sealed class RenderEventFilter : RenderEventObservable, IObservable<RenderEvent>, IObserver<RenderEvent>
9+
{
10+
private readonly IObservable<RenderEvent> _source;
11+
private readonly Func<RenderEvent, bool> _forwardEvent;
12+
private IDisposable? _subscription;
13+
14+
/// <summary>
15+
/// Creates an instance of the <see cref="RenderEventFilter"/>,
16+
/// that filters events from <paramref name="source"/> based on the
17+
/// <paramref name="forwardEvent"/> predicate.
18+
/// </summary>
19+
/// <param name="source">Source to filter.</param>
20+
/// <param name="forwardEvent">Filter to apply to <see cref="RenderEvent"/>.</param>
21+
public RenderEventFilter(IObservable<RenderEvent> source, Func<RenderEvent, bool> forwardEvent)
22+
{
23+
_source = source;
24+
_forwardEvent = forwardEvent;
25+
}
26+
27+
/// <inheritdoc/>
28+
public override IDisposable Subscribe(IObserver<RenderEvent> observer)
29+
{
30+
if (_subscription is null)
31+
_subscription = _source.Subscribe(this);
32+
return base.Subscribe(observer);
33+
}
34+
35+
/// <inheritdoc/>
36+
protected override void RemoveSubscription(IObserver<RenderEvent> observer)
37+
{
38+
base.RemoveSubscription(observer);
39+
if (Observers.Count == 0 && _subscription is { })
40+
{
41+
_subscription.Dispose();
42+
_subscription = null;
43+
}
44+
}
45+
46+
/// <inheritdoc/>
47+
void IObserver<RenderEvent>.OnCompleted()
48+
{
49+
foreach (var observer in Observers)
50+
observer.OnCompleted();
51+
}
52+
53+
/// <inheritdoc/>
54+
void IObserver<RenderEvent>.OnError(Exception error)
55+
{
56+
foreach (var observer in Observers)
57+
observer.OnError(error);
58+
}
59+
60+
/// <inheritdoc/>
61+
void IObserver<RenderEvent>.OnNext(RenderEvent renderEvent)
62+
{
63+
if (!_forwardEvent(renderEvent))
64+
return;
65+
foreach (var observer in Observers)
66+
observer.OnNext(renderEvent);
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)