Skip to content

Commit f0b21cc

Browse files
committed
E2E test
1 parent 7c3df4b commit f0b21cc

File tree

14 files changed

+308
-43
lines changed

14 files changed

+308
-43
lines changed

src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<!--
44
Skip building and running the Components E2E tests in CI unless explicitly configured otherwise via
55
EXECUTE_COMPONENTS_E2E_TESTS. At least build the Components E2E tests locally unless SkipTestBuild is set.
66
-->
77
<_BuildAndTest>false</_BuildAndTest>
8-
<_BuildAndTest
9-
Condition=" '$(ContinuousIntegrationBuild)' == 'true' AND '$(EXECUTE_COMPONENTS_E2E_TESTS)' == 'true' ">true</_BuildAndTest>
10-
<_BuildAndTest
11-
Condition=" '$(ContinuousIntegrationBuild)' != 'true' AND '$(SkipTestBuild)' != 'true' ">true</_BuildAndTest>
8+
<_BuildAndTest Condition=" '$(ContinuousIntegrationBuild)' == 'true' AND '$(EXECUTE_COMPONENTS_E2E_TESTS)' == 'true' ">true</_BuildAndTest>
9+
<_BuildAndTest Condition=" '$(ContinuousIntegrationBuild)' != 'true' AND '$(SkipTestBuild)' != 'true' ">true</_BuildAndTest>
1210
<ExcludeFromBuild Condition=" !$(_BuildAndTest) ">true</ExcludeFromBuild>
1311
<SkipTests Condition=" !$(_BuildAndTest) ">true</SkipTests>
1412

@@ -67,39 +65,19 @@
6765
</ItemGroup>
6866

6967
<ItemGroup Condition="'$(TestTrimmedOrMultithreadingApps)' == 'true'">
70-
<ProjectReference Include="..\..\benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj"
71-
Targets="Build;Publish"
72-
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\Wasm.Performance.TestApp\" />
73-
74-
<ProjectReference
75-
Include="..\testassets\BasicTestApp\BasicTestApp.csproj"
76-
Targets="Build;Publish"
77-
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\BasicTestApp\" />
78-
79-
<ProjectReference
80-
Include="..\testassets\GlobalizationWasmApp\GlobalizationWasmApp.csproj"
81-
Targets="Build;Publish"
82-
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\GlobalizationWasmApp\;" />
83-
84-
<ProjectReference
85-
Include="..\..\WebAssembly\testassets\StandaloneApp\StandaloneApp.csproj"
86-
Targets="Build;Publish"
87-
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\StandaloneApp\;" />
88-
89-
<ProjectReference
90-
Include="..\..\WebAssembly\testassets\Wasm.Prerendered.Server\Wasm.Prerendered.Server.csproj"
91-
Targets="Build;Publish"
92-
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\Wasm.Prerendered.Server\;" />
93-
94-
<ProjectReference
95-
Include="..\..\WebAssembly\testassets\ThreadingApp\ThreadingApp.csproj"
96-
Targets="Build;Publish"
97-
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=false;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\ThreadingApp\;" />
98-
99-
<ProjectReference
100-
Include="..\testassets\Components.TestServer\Components.TestServer.csproj"
101-
Targets="Build;Publish"
102-
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\Components.TestServer\;" />
68+
<ProjectReference Include="..\..\benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj" Targets="Build;Publish" Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\Wasm.Performance.TestApp\" />
69+
70+
<ProjectReference Include="..\testassets\BasicTestApp\BasicTestApp.csproj" Targets="Build;Publish" Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\BasicTestApp\" />
71+
72+
<ProjectReference Include="..\testassets\GlobalizationWasmApp\GlobalizationWasmApp.csproj" Targets="Build;Publish" Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\GlobalizationWasmApp\;" />
73+
74+
<ProjectReference Include="..\..\WebAssembly\testassets\StandaloneApp\StandaloneApp.csproj" Targets="Build;Publish" Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\StandaloneApp\;" />
75+
76+
<ProjectReference Include="..\..\WebAssembly\testassets\Wasm.Prerendered.Server\Wasm.Prerendered.Server.csproj" Targets="Build;Publish" Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\Wasm.Prerendered.Server\;" />
77+
78+
<ProjectReference Include="..\..\WebAssembly\testassets\ThreadingApp\ThreadingApp.csproj" Targets="Build;Publish" Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=false;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\ThreadingApp\;" />
79+
80+
<ProjectReference Include="..\testassets\Components.TestServer\Components.TestServer.csproj" Targets="Build;Publish" Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\Components.TestServer\;" />
10381
</ItemGroup>
10482

10583
<!-- Shared testing infrastructure for running E2E tests using selenium -->
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
using Components.TestServer.RazorComponents;
8+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
9+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
10+
using Microsoft.AspNetCore.E2ETesting;
11+
using OpenQA.Selenium;
12+
using TestServer;
13+
using Xunit.Abstractions;
14+
15+
namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests;
16+
17+
public class AddValidationIntegrationTest : ServerTestBase<BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<Root>>>
18+
{
19+
public AddValidationIntegrationTest(BrowserFixture browserFixture, BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<Root>> serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output)
20+
{
21+
}
22+
23+
protected override void InitializeAsyncCore()
24+
{
25+
Navigate("subdir/forms/add-validation-form");
26+
Browser.Exists(By.Id("is-interactive"));
27+
}
28+
29+
[Fact]
30+
public void FormWithNestedValidation_Works()
31+
{
32+
Browser.Exists(By.Id("submit-form")).Click();
33+
34+
// Validation summary
35+
var messages = Browser.FindElements(By.CssSelector(".validation-errors > .validation-message"))
36+
.Select(element => element.Text)
37+
.ToList();
38+
39+
var expected = new[] {"Order Name is required.",
40+
"Full Name is required.",
41+
"Email is required.",
42+
"Street is required.",
43+
"Zip Code is required.",
44+
"Product Name is required."
45+
};
46+
47+
Assert.Equal(expected, messages);
48+
49+
// Individual field messages
50+
var individual = Browser.FindElements(By.CssSelector(".mb-3 > .validation-message"))
51+
.Select(element => element.Text)
52+
.ToList();
53+
54+
Assert.Equal(expected, individual);
55+
}
56+
}

src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
1+
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -11,6 +11,9 @@
1111

1212
<!-- Project supports more than one language -->
1313
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
14+
15+
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
16+
1417
</PropertyGroup>
1518

1619
<PropertyGroup Condition="'$(TestTrimmedOrMultithreadingApps)' == 'true'">
@@ -47,10 +50,8 @@
4750
<ItemGroup>
4851
<ResolvedFileToPublish RelativePath="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('subdir\subdir', 'subdir').Replace('subdir/subdir', 'subdir'))" />
4952

50-
<ResolvedFileToPublish
51-
Include="@(ResolvedFileToPublish)"
52-
RelativePath="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('subdir\_content', '_content').Replace('subdir/subdir', '_content'))"
53-
Condition="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('subdir\_content', 'subdir/_content').Contains('subdir/_content'))" />
53+
<ResolvedFileToPublish Include="@(ResolvedFileToPublish)" RelativePath="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('subdir\_content', '_content').Replace('subdir/subdir', '_content'))" Condition="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('subdir\_content', 'subdir/_content').Contains('subdir/_content'))" />
54+
5455
</ItemGroup>
5556
</Target>
5657

src/Components/test/testassets/BasicTestApp/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public static async Task Main(string[] args)
2323
await SimulateErrorsIfNeededForTest();
2424

2525
var builder = WebAssemblyHostBuilder.CreateDefault(args);
26+
27+
builder.Services.AddValidation();
28+
2629
builder.RootComponents.Add<HeadOutlet>("head::after");
2730
builder.RootComponents.Add<Index>("root");
2831
builder.RootComponents.RegisterForJavaScript<DynamicallyAddedRootComponent>("my-dynamic-root-component");

src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
<Nullable>annotations</Nullable>
1111
<RazorLangVersion>latest</RazorLangVersion>
1212
<BlazorRoutingEnableRegexConstraint>true</BlazorRoutingEnableRegexConstraint>
13+
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Validation.Generated;Microsoft.Extensions.Validation.Generated</InterceptorsNamespaces>
1314
</PropertyGroup>
1415

16+
<ItemGroup>
17+
<ProjectReference Include="$(RepoRoot)/src/Validation/gen/Microsoft.Extensions.Validation.ValidationsGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
18+
</ItemGroup>
19+
1520
<ItemGroup>
1621
<Reference Include="Microsoft.AspNetCore" />
1722
<Reference Include="Microsoft.AspNetCore.Authentication.Cookies" />

src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public RazorComponentEndpointsStartup(IConfiguration configuration)
2828
// This method gets called by the runtime. Use this method to add services to the container.
2929
public void ConfigureServices(IServiceCollection services)
3030
{
31+
services.AddValidation();
32+
3133
services.AddRazorComponents(options =>
3234
{
3335
options.MaxFormMappingErrorCount = 10;
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
@using BasicTestApp.ValidationModels
2+
@using System.ComponentModel.DataAnnotations
3+
@using Microsoft.AspNetCore.Components.Forms
4+
5+
@if(RendererInfo.IsInteractive) {
6+
<p id="is-interactive"></p>
7+
}
8+
9+
<EditForm id="add-validation-form" Model="@order" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit">
10+
<DataAnnotationsValidator />
11+
12+
<div class="container mt-4">
13+
<h4>Order Details</h4>
14+
<div class="mb-3">
15+
<label for="orderName" class="form-label">Order Name</label>
16+
<InputText id="orderName" @bind-Value="order.OrderName" class="form-control" />
17+
<ValidationMessage For="@(() => order.OrderName)" />
18+
</div>
19+
20+
<hr />
21+
22+
<h4>Customer Details</h4>
23+
<div class="card mb-3">
24+
<div class="card-body">
25+
<div class="mb-3">
26+
<label for="customerFullName" class="form-label">Full Name</label>
27+
<InputText id="customerFullName" @bind-Value="order.CustomerDetails.FullName" class="form-control" />
28+
<ValidationMessage For="@(() => order.CustomerDetails.FullName)" />
29+
</div>
30+
<div class="mb-3">
31+
<label for="customerEmail" class="form-label">Email</label>
32+
<InputText id="customerEmail" @bind-Value="order.CustomerDetails.Email" class="form-control" />
33+
<ValidationMessage For="@(() => order.CustomerDetails.Email)" />
34+
</div>
35+
36+
<h5>Shipping Address</h5>
37+
<div class="card mb-3">
38+
<div class="card-body">
39+
<div class="row">
40+
<div class="mb-3 col-sm-8">
41+
<label for="shippingStreet" class="form-label">Street</label>
42+
<InputText id="shippingStreet" @bind-Value="order.CustomerDetails.ShippingAddress.Street" class="form-control" />
43+
<ValidationMessage For="@(() => order.CustomerDetails.ShippingAddress.Street)" />
44+
</div>
45+
<div class="mb-3 col-sm">
46+
<label for="shippingZipCode" class="form-label">Zip Code</label>
47+
<InputText id="shippingZipCode" @bind-Value="order.CustomerDetails.ShippingAddress.ZipCode" class="form-control" />
48+
<ValidationMessage For="@(() => order.CustomerDetails.ShippingAddress.ZipCode)" />
49+
</div>
50+
</div>
51+
</div>
52+
</div>
53+
</div>
54+
</div>
55+
56+
<hr />
57+
58+
<h4>Order Items</h4>
59+
@if (order.OrderItems.Any())
60+
{
61+
for (int i = 0; i < order.OrderItems.Count; i++)
62+
{
63+
var itemIndex = i;
64+
<div class="card mb-3">
65+
<div class="card-header d-flex justify-content-between align-items-center">
66+
<span>Item @(itemIndex + 1)</span>
67+
<button type="button" class="btn btn-sm btn-danger" @onclick="() => RemoveOrderItem(itemIndex)">Remove</button>
68+
</div>
69+
<div class="card-body">
70+
<div class="row">
71+
<div class="mb-3 col-sm-8">
72+
<label for="@($"productName_{itemIndex}")" class="form-label">Product Name</label>
73+
<InputText id="@($"productName_{itemIndex}")" @bind-Value="order.OrderItems[itemIndex].ProductName" class="form-control" />
74+
<ValidationMessage For="@(() => order.OrderItems[itemIndex].ProductName)" />
75+
</div>
76+
<div class="mb-3 col-sm">
77+
<label for="@($"quantity_{itemIndex}")" class="form-label">Quantity</label>
78+
<InputNumber id="@($"quantity_{itemIndex}")" @bind-Value="order.OrderItems[itemIndex].Quantity" class="form-control" />
79+
<ValidationMessage For="@(() => order.OrderItems[itemIndex].Quantity)" />
80+
</div>
81+
</div>
82+
</div>
83+
</div>
84+
}
85+
}
86+
else
87+
{
88+
<p>No order items. Add one below.</p>
89+
}
90+
91+
<button type="button" id="submit-form" class="btn btn-success mb-3" @onclick="AddOrderItem">Add Order Item</button>
92+
93+
<hr />
94+
95+
<div class="mb-3">
96+
<button type="submit" class="btn btn-primary">Submit Order</button>
97+
</div>
98+
99+
<ValidationSummary />
100+
</div>
101+
</EditForm>
102+
103+
@code {
104+
private OrderModel order = new OrderModel();
105+
106+
private void HandleValidSubmit()
107+
{
108+
}
109+
110+
private void HandleInvalidSubmit()
111+
{
112+
Console.WriteLine("Form submission failed due to validation errors.");
113+
}
114+
115+
private void AddOrderItem()
116+
{
117+
order.OrderItems.Add(new OrderItemModel());
118+
}
119+
120+
private void RemoveOrderItem(int index)
121+
{
122+
if (index >= 0 && index < order.OrderItems.Count)
123+
{
124+
order.OrderItems.RemoveAt(index);
125+
}
126+
}
127+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@page "/forms/add-validation-form"
2+
@rendermode RenderMode.InteractiveServer
3+
4+
<h3>This form demostrates using `AddValidation` from Microsoft.Extensions.Validation</h3>
5+
6+
<ComplexValidationComponent></ComplexValidationComponent>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace BasicTestApp.ValidationModels;
7+
8+
public class AddressModel
9+
{
10+
[Required(ErrorMessage = "Street is required.")]
11+
public string Street { get; set; }
12+
13+
[Required(ErrorMessage = "Zip Code is required.")]
14+
[StringLength(10, MinimumLength = 5, ErrorMessage = "Zip Code must be between 5 and 10 characters.")]
15+
public string ZipCode { get; set; }
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace BasicTestApp.ValidationModels;
7+
8+
public class CustomerModel
9+
{
10+
[Required(ErrorMessage = "Full Name is required.")]
11+
public string FullName { get; set; }
12+
13+
[Required(ErrorMessage = "Email is required.")]
14+
[EmailAddress(ErrorMessage = "Invalid Email Address.")]
15+
public string Email { get; set; }
16+
17+
public AddressModel ShippingAddress { get; set; } = new AddressModel();
18+
}

0 commit comments

Comments
 (0)