Skip to content

Commit 020d3f7

Browse files
committed
TestHost and Rerendering done
1 parent e7700ef commit 020d3f7

19 files changed

+324
-271
lines changed

src/AssertExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ string GetUserMessage() => userMessage is null
5353
: $"{userMessage}.{Environment.NewLine}";
5454
}
5555

56+
public static void ShouldBe(this IRenderedFragment actual, string expected, string? userMessage = null)
57+
{
58+
if (actual is null) throw new ArgumentNullException(nameof(actual));
59+
if (expected is null) throw new ArgumentNullException(nameof(expected));
60+
61+
var actualNodes = actual.GetNodes();
62+
var expectedNodes = actual.TestContext.HtmlParser.Parse(expected);
63+
64+
actualNodes.ShouldBe(expectedNodes, userMessage);
65+
}
66+
5667
public static void ShouldBe(this IRenderedFragment actual, IRenderedFragment expected, string? userMessage = null)
5768
{
5869
if (actual is null) throw new ArgumentNullException(nameof(actual));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.AspNetCore.Components;
3+
4+
namespace Egil.RazorComponents.Testing
5+
{
6+
public class ComponentUnderTest : FragmentBase
7+
{
8+
public override Task SetParametersAsync(ParameterView parameters)
9+
{
10+
var result = base.SetParametersAsync(parameters);
11+
return result;
12+
}
13+
}
14+
}

src/Components/ContainerComponent.cs

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -67,32 +67,11 @@ public Task SetParametersAsync(ParameterView parameters)
6767
return result;
6868
}
6969

70-
public void RenderComponentUnderTest(Type componentType, ParameterView parameters)
71-
{
72-
_renderer.DispatchAndAssertNoSynchronousErrors(() =>
73-
{
74-
_renderHandle.Render(builder =>
75-
{
76-
builder.OpenComponent(0, componentType);
77-
78-
foreach (var parameterValue in parameters)
79-
{
80-
builder.AddAttribute(1, parameterValue.Name, parameterValue.Value);
81-
}
82-
83-
builder.CloseComponent();
84-
});
85-
});
86-
}
87-
8870
public void RenderComponentUnderTest(RenderFragment renderFragment)
8971
{
9072
_renderer.DispatchAndAssertNoSynchronousErrors(() =>
9173
{
92-
_renderHandle.Render(builder =>
93-
{
94-
builder.AddContent(0, renderFragment);
95-
});
74+
_renderHandle.Render(renderFragment);
9675
});
9776
}
9877
}

src/Components/Fixture.cs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Threading.Tasks;
43
using Microsoft.AspNetCore.Components;
54

65
namespace Egil.RazorComponents.Testing
76
{
8-
public delegate void Test(TestContext context);
9-
107
public class Fixture : FragmentBase
118
{
129
private Test _setup = NoopTestMethod;
@@ -19,32 +16,4 @@ public class Fixture : FragmentBase
1916

2017
private static void NoopTestMethod(TestContext context) { }
2118
}
22-
23-
public abstract class FragmentBase : IComponent
24-
{
25-
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
26-
27-
public void Attach(RenderHandle renderHandle) { }
28-
29-
public virtual Task SetParametersAsync(ParameterView parameters)
30-
{
31-
parameters.SetParameterProperties(this);
32-
if (ChildContent is null) throw new InvalidOperationException($"No {nameof(ChildContent)} specified.");
33-
return Task.CompletedTask;
34-
}
35-
}
36-
37-
public class ComponentUnderTest : FragmentBase
38-
{
39-
public override Task SetParametersAsync(ParameterView parameters)
40-
{
41-
var result = base.SetParametersAsync(parameters);
42-
return result;
43-
}
44-
}
45-
46-
public class Fragment : FragmentBase
47-
{
48-
[Parameter] public string Id { get; set; } = string.Empty;
49-
}
5019
}

src/Components/Fragment.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.AspNetCore.Components;
2+
3+
namespace Egil.RazorComponents.Testing
4+
{
5+
public class Fragment : FragmentBase
6+
{
7+
[Parameter] public string Id { get; set; } = string.Empty;
8+
}
9+
}

src/Components/FragmentBase.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNetCore.Components;
4+
5+
namespace Egil.RazorComponents.Testing
6+
{
7+
public abstract class FragmentBase : IComponent
8+
{
9+
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
10+
11+
public void Attach(RenderHandle renderHandle) { }
12+
13+
public virtual Task SetParametersAsync(ParameterView parameters)
14+
{
15+
parameters.SetParameterProperties(this);
16+
if (ChildContent is null) throw new InvalidOperationException($"No {nameof(ChildContent)} specified.");
17+
return Task.CompletedTask;
18+
}
19+
}
20+
}

src/Components/Test.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Egil.RazorComponents.Testing
8+
{
9+
public delegate void Test(TestContext context);
10+
}

src/Components/TestContext.cs

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,74 +9,16 @@
99

1010
namespace Egil.RazorComponents.Testing
1111
{
12-
public sealed class TestContext : IDisposable
12+
public sealed class TestContext : TestHost
1313
{
14-
private readonly ServiceCollection _serviceCollection = new ServiceCollection();
15-
private readonly Lazy<TestRenderer> _renderer;
1614
private readonly IReadOnlyList<FragmentBase> _testData;
17-
private readonly Lazy<HtmlParser> _htmlParser;
1815
private readonly Dictionary<string, IRenderedFragment> _renderedFragments = new Dictionary<string, IRenderedFragment>();
1916

20-
private TestRenderer Renderer => _renderer.Value;
21-
private HtmlParser HtmlParser => _htmlParser.Value;
22-
2317
public TestContext(IReadOnlyList<FragmentBase> testData)
2418
{
25-
_renderer = new Lazy<TestRenderer>(() =>
26-
{
27-
var serviceProvider = _serviceCollection.BuildServiceProvider();
28-
var loggerFactory = serviceProvider.GetService<ILoggerFactory>() ?? new NullLoggerFactory();
29-
return new TestRenderer(serviceProvider, loggerFactory);
30-
});
31-
_htmlParser = new Lazy<HtmlParser>(() =>
32-
{
33-
return new HtmlParser(Renderer, new HtmlComparer());
34-
});
3519
_testData = testData;
3620
}
3721

38-
public void AddService<T>(T implementation) => AddService<T, T>(implementation);
39-
40-
public void AddService<TService, TImplementation>(TImplementation implementation) where TImplementation : TService
41-
{
42-
if (_renderer.IsValueCreated)
43-
throw new InvalidOperationException("Cannot configure services after the host has started operation");
44-
45-
_serviceCollection.AddSingleton(typeof(TService), implementation);
46-
}
47-
48-
public void WaitForNextRender(Action trigger)
49-
{
50-
if (trigger is null) throw new ArgumentNullException(nameof(trigger));
51-
var task = Renderer.NextRender;
52-
trigger();
53-
task.Wait(millisecondsTimeout: 1000);
54-
55-
if (!task.IsCompleted)
56-
{
57-
throw new TimeoutException("No render occurred within the timeout period.");
58-
}
59-
}
60-
61-
public RenderedComponent<TComponent> AddComponent<TComponent>() where TComponent : class, IComponent
62-
{
63-
return AddComponent<TComponent>(ParameterView.Empty);
64-
}
65-
66-
public RenderedComponent<TComponent> AddComponent<TComponent>(params (string paramName, object valueValue)[] parameters) where TComponent : class, IComponent
67-
{
68-
var paramDict = parameters.ToDictionary(x => x.paramName, x => x.valueValue);
69-
var parameterView = ParameterView.FromDictionary(paramDict);
70-
return AddComponent<TComponent>(parameterView);
71-
}
72-
73-
public RenderedComponent<TComponent> AddComponent<TComponent>(ParameterView parameters) where TComponent : class, IComponent
74-
{
75-
var result = new RenderedComponent<TComponent>(Renderer, HtmlParser);
76-
result.SetParametersAndRender(parameters);
77-
return result;
78-
}
79-
8022
public RenderedComponent<TComponent> GetComponentUnderTest<TComponent>() where TComponent : class, IComponent
8123
{
8224
var fragmentKey = nameof(GetComponentUnderTest);
@@ -88,58 +30,47 @@ public RenderedComponent<TComponent> GetComponentUnderTest<TComponent>() where T
8830
else
8931
{
9032
var componentUnderTest = _testData.OfType<ComponentUnderTest>().Single();
91-
var result = new RenderedComponent<TComponent>(Renderer, HtmlParser);
92-
result.Render(componentUnderTest.ChildContent);
33+
var result = new RenderedComponent<TComponent>(this, componentUnderTest.ChildContent);
9334
_renderedFragments.Add(fragmentKey, result);
9435

9536
return result;
9637
}
9738
}
9839

99-
public RenderedFragment GetFragment(string id)
40+
public RenderedComponent<TComponent> GetFragment<TComponent>(string id) where TComponent : class, IComponent
10041
{
10142
var fragmentKey = nameof(GetFragment) + id;
10243
if (_renderedFragments.TryGetValue(fragmentKey, out var renderedFragment))
10344
{
104-
return (RenderedFragment)renderedFragment;
45+
return (RenderedComponent<TComponent>)renderedFragment;
10546
}
10647
else
10748
{
10849
var fragment = _testData.OfType<Fragment>().Single(x => x.Id.Equals(id, StringComparison.Ordinal));
10950

110-
var result = new RenderedFragment(Renderer, HtmlParser);
111-
result.Render(fragment.ChildContent);
51+
var result = new RenderedComponent<TComponent>(this, fragment.ChildContent);
11252
_renderedFragments.Add(fragmentKey, result);
11353

11454
return result;
11555
}
11656
}
11757

118-
public RenderedComponent<TComponent> GetFragment<TComponent>(string id) where TComponent : class, IComponent
58+
public RenderedFragment GetFragment(string id)
11959
{
12060
var fragmentKey = nameof(GetFragment) + id;
12161
if (_renderedFragments.TryGetValue(fragmentKey, out var renderedFragment))
12262
{
123-
return (RenderedComponent<TComponent>)renderedFragment;
63+
return (RenderedFragment)renderedFragment;
12464
}
12565
else
12666
{
12767
var fragment = _testData.OfType<Fragment>().Single(x => x.Id.Equals(id, StringComparison.Ordinal));
12868

129-
var result = new RenderedComponent<TComponent>(Renderer, HtmlParser);
130-
result.Render(fragment.ChildContent);
69+
var result = new RenderedFragment(this, fragment.ChildContent);
13170
_renderedFragments.Add(fragmentKey, result);
13271

13372
return result;
13473
}
13574
}
136-
137-
public void Dispose()
138-
{
139-
if (_renderer.IsValueCreated)
140-
_renderer.Value.Dispose();
141-
if (_htmlParser.IsValueCreated)
142-
_htmlParser.Value.Dispose();
143-
}
14475
}
14576
}

src/Diffing/HtmlParser.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ public sealed class HtmlParser : IDisposable
1414

1515
public HtmlParser(TestRenderer testRenderer, HtmlComparer comparer)
1616
{
17-
var config = Configuration.Default.WithCss().With(testRenderer).With(comparer);
17+
var config = Configuration.Default
18+
.WithCss()
19+
.With(testRenderer)
20+
.With(comparer)
21+
.With(this);
22+
1823
_context = BrowsingContext.New(config);
1924
_htmlParser = _context.GetService<IHtmlParser>();
2025
_document = _context.OpenNewAsync().Result;

src/IRenderedFragment.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ public interface IRenderedFragment
1010
string GetMarkup();
1111

1212
INodeList GetNodes();
13+
14+
TestHost TestContext { get; }
1315
}
1416

1517
public static class RenderedFragmentExtensions
1618
{
1719
public static IElement Find(this IRenderedFragment renderedFragment, string selector)
1820
{
1921
if (renderedFragment is null) throw new ArgumentNullException(nameof(renderedFragment));
20-
var nodes = renderedFragment.GetNodes();
22+
var nodes = renderedFragment.GetNodes();
2123
return nodes.QuerySelector(selector);
2224
}
2325

0 commit comments

Comments
 (0)