Skip to content

Commit 8e34b42

Browse files
committed
Initial migration of code to separat testing library
1 parent 40e2958 commit 8e34b42

9 files changed

+671
-1
lines changed

ComponentBuilder.cs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Components;
5+
using Microsoft.AspNetCore.Components.Rendering;
6+
using System.Runtime.ExceptionServices;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using System.Text.Encodings.Web;
9+
using Moq;
10+
using System.Linq;
11+
using Microsoft.AspNetCore.Components.RenderTree;
12+
using Microsoft.JSInterop;
13+
using Shouldly;
14+
15+
namespace Egil.RazorComponents.Testing
16+
{
17+
public class ComponentBuilder<TComponent> where TComponent : ComponentBase
18+
{
19+
private readonly Func<string, string> _encoder = (t) => HtmlEncoder.Default.Encode(t);
20+
private readonly IDispatcher _dispatcher = Renderer.CreateDefaultDispatcher();
21+
22+
protected ServiceCollection Services { get; } = new ServiceCollection();
23+
protected Dictionary<string, object> Parameters { get; } = new Dictionary<string, object>();
24+
25+
public ComponentBuilder()
26+
{
27+
Services.AddSingleton(x => Mock.Of<IUriHelper>());
28+
Services.AddSingleton(x => Mock.Of<IJSRuntime>());
29+
Services.AddSingleton<TComponent>();
30+
}
31+
32+
public ComponentBuilder<TComponent> WithServices(Action<IServiceCollection> addServices)
33+
{
34+
addServices(Services);
35+
return this;
36+
}
37+
38+
public ComponentBuilder<TComponent> WithParams(params (string name, object value)[] paramValues)
39+
{
40+
foreach (var (name, value) in paramValues) Parameters[name] = value;
41+
return this;
42+
}
43+
44+
public ComponentBuilder<TComponent> WithChildContent(RenderFragment renderFragment)
45+
{
46+
Parameters[RenderTreeBuilder.ChildContent] = renderFragment;
47+
return this;
48+
}
49+
50+
public ComponentBuilder<TComponent> WithChildContent(params RenderFragment[] childContents)
51+
{
52+
Parameters[RenderTreeBuilder.ChildContent] = ToFragment(childContents);
53+
return this;
54+
}
55+
56+
public ComponentBuilder<TComponent> WithChildContent(string childMarkupContent)
57+
{
58+
return WithChildContent(b => b.AddMarkupContent(0, childMarkupContent));
59+
}
60+
61+
public ComponentRenderedText Render()
62+
{
63+
var serviceProvider = Services.BuildServiceProvider();
64+
var paramCollection = Parameters.Count > 0 ? ParameterCollection.FromDictionary(Parameters) : ParameterCollection.Empty;
65+
using var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder, _dispatcher);
66+
return GetResult(_dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TComponent>(paramCollection)));
67+
}
68+
69+
private ComponentRenderedText GetResult(Task<ComponentRenderedText> task)
70+
{
71+
task.IsCompleted.ShouldBeTrue();
72+
if (task.IsCompletedSuccessfully)
73+
{
74+
return task.Result;
75+
}
76+
else
77+
{
78+
ExceptionDispatchInfo.Capture(task.Exception!).Throw();
79+
throw new InvalidOperationException("We will never hit this line");
80+
}
81+
}
82+
83+
private static RenderFragment ToFragment(RenderFragment[] childContents)
84+
{
85+
return builder =>
86+
{
87+
for (int i = 0; i < childContents.Length; i++)
88+
{
89+
builder.AddContent(i, childContents[i]);
90+
}
91+
};
92+
}
93+
}
94+
95+
public class ComponentBuilder<TComponent, TItem> : ComponentBuilder<TComponent>
96+
where TComponent : ComponentBase
97+
{
98+
public new ComponentBuilder<TComponent, TItem> WithServices(Action<IServiceCollection> addServices)
99+
{
100+
addServices(Services);
101+
return this;
102+
}
103+
104+
105+
public new ComponentBuilder<TComponent, TItem> WithParams(params (string name, object value)[] paramValues)
106+
{
107+
foreach (var (name, value) in paramValues) Parameters[name] = value;
108+
return this;
109+
}
110+
111+
public ComponentBuilder<TComponent, TItem> WithItems(IEnumerable<TItem> items)
112+
{
113+
Parameters["Items"] = items;
114+
return this;
115+
}
116+
117+
public ComponentBuilder<TComponent, TItem> WithItems(params TItem[] items)
118+
{
119+
Parameters["Items"] = items;
120+
return this;
121+
}
122+
123+
public ComponentBuilder<TComponent, TItem> WithItems(int itemCount, Func<int, TItem> itemGenerator)
124+
{
125+
Parameters["Items"] = Enumerable.Range(1, itemCount).Select(itemGenerator).ToArray();
126+
return this;
127+
}
128+
129+
public ComponentBuilder<TComponent, TItem> WithDefaultItems(int itemCount)
130+
{
131+
return WithItems(itemCount, _ => default!);
132+
}
133+
134+
public ComponentBuilder<TComponent, TItem> WithTemplate(RenderFragment<TItem> renderTemplate)
135+
{
136+
Parameters[RenderTreeBuilder.ChildContent] = renderTemplate;
137+
return this;
138+
}
139+
140+
public ComponentBuilder<TComponent, TItem> WithTemplate(Func<TemplateBuilder<TItem>, RenderFragment<TItem>> templateBuilder)
141+
{
142+
Parameters[RenderTreeBuilder.ChildContent] = templateBuilder(new TemplateBuilder<TItem>());
143+
return this;
144+
}
145+
146+
public ComponentBuilder<TComponent, TItem> WithTemplate(params RenderFragment[] renderFragments)
147+
{
148+
Parameters[RenderTreeBuilder.ChildContent] = ToFragment<TItem>(renderFragments);
149+
return this;
150+
}
151+
152+
private static RenderFragment<TItem> ToFragment<ITtem>(RenderFragment[] childContents)
153+
{
154+
return (item) => builder =>
155+
{
156+
for (int i = 0; i < childContents.Length; i++)
157+
{
158+
builder.AddContent(i, childContents[i]);
159+
}
160+
};
161+
}
162+
163+
}
164+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Linq;
3+
using Org.XmlUnit.Diff;
4+
5+
namespace Egil.RazorComponents.Testing
6+
{
7+
internal class CssClassAttributeDifferenceEvaluator
8+
{
9+
private readonly static char[] Space = new char[] { ' ' };
10+
public ComparisonResult Evaluate(Comparison comparison, ComparisonResult outcome)
11+
{
12+
if (outcome == ComparisonResult.EQUAL) return outcome;
13+
if (comparison.Type != ComparisonType.ATTR_VALUE) return outcome;
14+
if (!comparison.TestDetails.Target.Name.Equals("class", StringComparison.OrdinalIgnoreCase)) return outcome;
15+
16+
var expected = comparison.ControlDetails.Value.ToString().Split(Space, StringSplitOptions.RemoveEmptyEntries).ToHashSet();
17+
var actual = comparison.TestDetails.Value.ToString().Split(Space, StringSplitOptions.RemoveEmptyEntries).ToHashSet();
18+
19+
if (expected.SetEquals(actual))
20+
return ComparisonResult.EQUAL;
21+
else return outcome;
22+
}
23+
24+
internal static readonly DifferenceEvaluator Default = new CssClassAttributeDifferenceEvaluator().Evaluate;
25+
}
26+
}

FragmentBuilder.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.AspNetCore.Components;
4+
using Microsoft.AspNetCore.Components.RenderTree;
5+
6+
namespace Egil.RazorComponents.Testing
7+
{
8+
public class FragmentBuilder
9+
{
10+
private readonly Type _componentType;
11+
private readonly Dictionary<string, object> _parameters = new Dictionary<string, object>();
12+
private RenderFragment? _childContent;
13+
14+
public FragmentBuilder(Type componentType)
15+
{
16+
_componentType = componentType;
17+
}
18+
19+
public FragmentBuilder WithChildContent(string markupContent)
20+
{
21+
_childContent = (builder) => builder.AddMarkupContent(1, markupContent);
22+
return this;
23+
}
24+
25+
public FragmentBuilder WithChildContent(RenderFragment childContent)
26+
{
27+
_childContent = childContent;
28+
return this;
29+
}
30+
31+
public FragmentBuilder WithChildContent(params RenderFragment[] childContents)
32+
{
33+
_childContent = ToFragment(childContents);
34+
return this;
35+
}
36+
37+
public FragmentBuilder WithParams(params (string name, object value)[] paramValues)
38+
{
39+
foreach (var (name, value) in paramValues) _parameters[name] = value;
40+
return this;
41+
}
42+
43+
public RenderFragment Build()
44+
{
45+
return builder =>
46+
{
47+
builder.OpenComponent(1, _componentType);
48+
if (!(_childContent is null)) builder.AddAttribute(2, RenderTreeBuilder.ChildContent, _childContent);
49+
foreach (var (name, value) in _parameters)
50+
{
51+
builder.AddAttribute(3, name, value);
52+
}
53+
builder.CloseComponent();
54+
};
55+
}
56+
57+
private static RenderFragment ToFragment(RenderFragment[] childContents)
58+
{
59+
return builder =>
60+
{
61+
for (int i = 0; i < childContents.Length; i++)
62+
{
63+
builder.AddContent(i, childContents[i]);
64+
}
65+
};
66+
}
67+
68+
public static implicit operator RenderFragment(FragmentBuilder builder)
69+
{
70+
return builder.Build();
71+
}
72+
}
73+
74+
public class FragmentBuilder<TComponent> : FragmentBuilder
75+
where TComponent : ComponentBase
76+
{
77+
public FragmentBuilder() : base(typeof(TComponent))
78+
{
79+
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)