From d25e5d49444312214b43a2d9d9f659e7e131b275 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Mon, 8 Apr 2024 23:34:32 -0500 Subject: [PATCH] feat: add site header (#2) * tests: setup initial infra for end to end tests * tests: rework end to end host * tests: remove need for bang operator * tests: correct header component tests * docs: add README.md to blog directory with instructions on setting up local dev environment with and without docker * docs: update non-developer run command * feat: finish layout of header * feat: add favicon * tests: setup test coverage * chore: update workflow with missing playwright step * chore: add install command --- .github/workflows/pull_request.yml | 3 + .gitignore | 1 + .vscode/settings.json | 3 +- src/Blog/Blog.csproj | 5 ++ src/Blog/Components/App.razor | 1 + src/Blog/Components/Layout/Header.razor | 13 ++++ src/Blog/Components/Layout/Header.razor.css | 24 +++++++ src/Blog/Components/Layout/MainLayout.razor | 6 +- .../Components/Layout/MainLayout.razor.css | 10 +++ src/Blog/Components/Pages/Home.razor | 6 +- src/Blog/Program.cs | 4 +- src/Blog/README.md | 48 ++++++++++++++ src/Blog/appsettings.example.json | 3 + src/Blog/example.env.development | 15 +++++ src/Blog/wwwroot/app.css | 60 +++++++++++++++++ src/Blog/wwwroot/favicon.ico | Bin 0 -> 15406 bytes src/Blog/wwwroot/favicon.png | Bin 1148 -> 0 bytes test/Blog.Tests/.runsettings | 10 +++ test/Blog.Tests/Blog.Tests.csproj | 24 ++++++- test/Blog.Tests/EndToEnd/BlogHostFactory.cs | 62 ++++++++++++++++++ test/Blog.Tests/EndToEnd/BlogTest.cs | 17 +++++ test/Blog.Tests/EndToEnd/HomeTests.cs | 32 +++++++++ test/Blog.Tests/GlobalUsings.cs | 11 +++- test/Blog.Tests/Unit/Header.spec.razor | 32 +++++++++ test/Blog.Tests/UnitTest1.cs | 15 ----- test/Blog.Tests/_Imports.razor | 3 + 26 files changed, 387 insertions(+), 21 deletions(-) create mode 100644 src/Blog/Components/Layout/Header.razor create mode 100644 src/Blog/Components/Layout/Header.razor.css create mode 100644 src/Blog/README.md create mode 100644 src/Blog/appsettings.example.json create mode 100644 src/Blog/example.env.development create mode 100644 src/Blog/wwwroot/favicon.ico delete mode 100644 src/Blog/wwwroot/favicon.png create mode 100644 test/Blog.Tests/.runsettings create mode 100644 test/Blog.Tests/EndToEnd/BlogHostFactory.cs create mode 100644 test/Blog.Tests/EndToEnd/BlogTest.cs create mode 100644 test/Blog.Tests/EndToEnd/HomeTests.cs create mode 100644 test/Blog.Tests/Unit/Header.spec.razor delete mode 100644 test/Blog.Tests/UnitTest1.cs create mode 100644 test/Blog.Tests/_Imports.razor diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 09ce7e5..1cc6936 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -29,6 +29,9 @@ jobs: run: dotnet restore - name: Build project run: dotnet build --no-restore + - name: Install playwright dependencies + shell: pwsh + run: ./test/Blog.Tests/bin/Debug/net8.0/playwright.ps1 install - name: Test project run: dotnet test -e DOTNET_ENVIRONMENT=CI --no-build --verbosity normal - name: Upload test results diff --git a/.gitignore b/.gitignore index e2aa736..65b9f93 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # appsettings appsettings*.json +!appsettings.example.json # dotenv files .env* diff --git a/.vscode/settings.json b/.vscode/settings.json index 7301fa2..f2aafb8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "Antiforgery", "blazor", "Hsts" - ] + ], + "dotnet.unitTests.runSettingsPath": "./test/Blog.Tests/.runsettings" } \ No newline at end of file diff --git a/src/Blog/Blog.csproj b/src/Blog/Blog.csproj index 420ef61..50fdd4f 100644 --- a/src/Blog/Blog.csproj +++ b/src/Blog/Blog.csproj @@ -11,4 +11,9 @@ + + + + + diff --git a/src/Blog/Components/App.razor b/src/Blog/Components/App.razor index 1f3f9ab..a4ce524 100644 --- a/src/Blog/Components/App.razor +++ b/src/Blog/Components/App.razor @@ -7,6 +7,7 @@ + diff --git a/src/Blog/Components/Layout/Header.razor b/src/Blog/Components/Layout/Header.razor new file mode 100644 index 0000000..485121b --- /dev/null +++ b/src/Blog/Components/Layout/Header.razor @@ -0,0 +1,13 @@ +@namespace Blog.Components + +
+ +
+ by + Stevan Freeborn +
+
diff --git a/src/Blog/Components/Layout/Header.razor.css b/src/Blog/Components/Layout/Header.razor.css new file mode 100644 index 0000000..bf913a7 --- /dev/null +++ b/src/Blog/Components/Layout/Header.razor.css @@ -0,0 +1,24 @@ +.container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + gap: 1rem; + + & .title-container h1 { + font-size: 1.5rem; + font-weight: 700; + } + + & .author-container { + display: flex; + align-items: center; + gap: 0.5rem; + + & img { + width: 2rem; + height: 2rem; + border-radius: 50%; + } + } +} \ No newline at end of file diff --git a/src/Blog/Components/Layout/MainLayout.razor b/src/Blog/Components/Layout/MainLayout.razor index 0d3d3af..6af4224 100644 --- a/src/Blog/Components/Layout/MainLayout.razor +++ b/src/Blog/Components/Layout/MainLayout.razor @@ -1,6 +1,10 @@ @inherits LayoutComponentBase +@rendermode InteractiveServer -@Body +
+
+ @Body +
An unhandled error has occurred. diff --git a/src/Blog/Components/Layout/MainLayout.razor.css b/src/Blog/Components/Layout/MainLayout.razor.css index 21a8d24..0431dce 100644 --- a/src/Blog/Components/Layout/MainLayout.razor.css +++ b/src/Blog/Components/Layout/MainLayout.razor.css @@ -1,3 +1,13 @@ +.container { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + max-width: 42rem; + padding: 1.25rem; + width: 100%; +} + #blazor-error-ui { background: lightyellow; bottom: 0; diff --git a/src/Blog/Components/Pages/Home.razor b/src/Blog/Components/Pages/Home.razor index 9001e0b..1f8286f 100644 --- a/src/Blog/Components/Pages/Home.razor +++ b/src/Blog/Components/Pages/Home.razor @@ -1,6 +1,10 @@ @page "/" -Home +journal - A blog by Stevan Freeborn + + + +

Hello, world!

diff --git a/src/Blog/Program.cs b/src/Blog/Program.cs index a2d5151..bc42976 100644 --- a/src/Blog/Program.cs +++ b/src/Blog/Program.cs @@ -23,4 +23,6 @@ app .MapRazorComponents() .AddInteractiveServerRenderMode(); -app.Run(); \ No newline at end of file +app.Run(); + +public partial class Program {} \ No newline at end of file diff --git a/src/Blog/README.md b/src/Blog/README.md new file mode 100644 index 0000000..63851e3 --- /dev/null +++ b/src/Blog/README.md @@ -0,0 +1,48 @@ +# Blog + +## Setup for development + +### Clone the repository + +```sh +git clone https://github.com/StevanFreeborn/blog.stevanfreeborn.com.git +``` + +### Generate development SSL certificates + +```sh +dotnet dev-certs https -ep ${HOME}/.aspnet/https/Blog.pfx -p +dotnet dev-certs https --trust +``` + +```powershell +dotnet dev-certs https -ep $env:USERPROFILE\.aspnet\https\Blog.pfx -p --trust +``` + +### Developing with Docker + +#### Create `.env.development` file + +```sh +cp src/Blog/example.env.development src/Blog/.env.development +``` + +#### Build and run the Docker container + +```sh +docker-compose -f docker-compose.dev.yml up --build +``` + +### Developing without Docker + +#### Create `appsettings.Development.json` file + +```sh +cp src/Blog/appsettings.example.json src/Blog/appsettings.Development.json +``` + +#### Run the application + +```sh +dotnet run --project src/Blog/Blog.csproj +``` diff --git a/src/Blog/appsettings.example.json b/src/Blog/appsettings.example.json new file mode 100644 index 0000000..acae0aa --- /dev/null +++ b/src/Blog/appsettings.example.json @@ -0,0 +1,3 @@ +{ + "Urls": "https://localhost:4500;http://localhost:4501" +} \ No newline at end of file diff --git a/src/Blog/example.env.development b/src/Blog/example.env.development new file mode 100644 index 0000000..fb80ac5 --- /dev/null +++ b/src/Blog/example.env.development @@ -0,0 +1,15 @@ +URLS=https://0.0.0.0:4500;http://0.0.0.0:4501 + +# Path needs to match the path where the cert file +# is mounted in the container +ASPNETCORE_Kestrel__Certificates__Default__Path= + +# Password needs to match the password for the cert file +# you generated +ASPNETCORE_Kestrel__Certificates__Default__Password= + +# This is the hostname that the dotnet watch process will use to connect to the browser +# The first url in the list is the port that the browser will attempt to connect to +# The second url in the list is the port that the dotnet watch process will be running +# on in the container. This is necessary for hot reload to work. +DOTNET_WATCH_AUTO_RELOAD_WS_HOSTNAME=localhost:1234;http://0.0.0.0:5555;http://127.0.0.1 \ No newline at end of file diff --git a/src/Blog/wwwroot/app.css b/src/Blog/wwwroot/app.css index 795e36b..16a6d49 100644 --- a/src/Blog/wwwroot/app.css +++ b/src/Blog/wwwroot/app.css @@ -1,3 +1,63 @@ +@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700;1,900&display=swap'); + +/* Resets */ + +* { + margin: 0; + padding: 0; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +button { + background: none; + border: none; + outline: none; + cursor: pointer; +} + +a { + text-decoration: none; + color: inherit; +} + +ul { + list-style-type: none; +} + +img { + max-width: 100%; + display: block; +} + +table { + width: 100%; + border-collapse: collapse; +} + +/* Site */ + +html, +body { + height: 100%; +} + +body { + display: flex; + flex-direction: column; + align-items: center; + font-family: 'Merriweather', serif; + font-size: 16px; + line-height: 1.5; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + h1:focus { outline: none; } diff --git a/src/Blog/wwwroot/favicon.ico b/src/Blog/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ec4c0e35b7f4980ee4b2feb2adc454246421fb86 GIT binary patch literal 15406 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 + + + chromium + + false + msedge + + + \ No newline at end of file diff --git a/test/Blog.Tests/Blog.Tests.csproj b/test/Blog.Tests/Blog.Tests.csproj index 636746e..61db240 100644 --- a/test/Blog.Tests/Blog.Tests.csproj +++ b/test/Blog.Tests/Blog.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -11,6 +11,12 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + @@ -19,6 +25,22 @@ + + true + ./TestResults/coverage/ + cobertura + [Blog]* + **/Blog/Program.cs + + + + + + + + + + diff --git a/test/Blog.Tests/EndToEnd/BlogHostFactory.cs b/test/Blog.Tests/EndToEnd/BlogHostFactory.cs new file mode 100644 index 0000000..c138951 --- /dev/null +++ b/test/Blog.Tests/EndToEnd/BlogHostFactory.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Blog.Tests.EndToEnd; + +public class BlogHostFactory : WebApplicationFactory where TProgram : class +{ + private IHost? _host; + + private void EnsureServer() + { + if (_host is null) + { + using var _ = CreateDefaultClient(); + } + } + + protected override void Dispose(bool disposing) + { + _host?.Dispose(); + } + + public string ServerAddress + { + get + { + EnsureServer(); + return ClientOptions.BaseAddress.ToString(); + } + } + + protected override IHost CreateHost(IHostBuilder builder) + { + var testHost = builder + .ConfigureWebHost( + webHostBuilder => webHostBuilder.ConfigureLogging( + config => config.ClearProviders() + ) + ) + .Build(); + + builder.ConfigureWebHost( + webHostBuilder => webHostBuilder.UseKestrel( + o => o.Listen(IPAddress.Loopback, 0) + ) + ); + + _host = builder.Build(); + _host.Start(); + + var server = _host.Services.GetRequiredService(); + var addresses = server.Features.GetRequiredFeature(); + + ClientOptions.BaseAddress = addresses.Addresses + .Select(x => new Uri(x)) + .Last(); + + testHost.Start(); + return testHost; + } +} \ No newline at end of file diff --git a/test/Blog.Tests/EndToEnd/BlogTest.cs b/test/Blog.Tests/EndToEnd/BlogTest.cs new file mode 100644 index 0000000..27ec44a --- /dev/null +++ b/test/Blog.Tests/EndToEnd/BlogTest.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http.Features; + +namespace Blog.Tests.EndToEnd; + +[TestFixture] +public class BlogTest : PageTest +{ + private readonly BlogHostFactory _factory = new(); + + public override BrowserNewContextOptions ContextOptions() + { + var options = base.ContextOptions(); + options.BaseURL = _factory.ServerAddress; + return options; + } +} \ No newline at end of file diff --git a/test/Blog.Tests/EndToEnd/HomeTests.cs b/test/Blog.Tests/EndToEnd/HomeTests.cs new file mode 100644 index 0000000..e0ba4e5 --- /dev/null +++ b/test/Blog.Tests/EndToEnd/HomeTests.cs @@ -0,0 +1,32 @@ +namespace Blog.Tests.EndToEnd; + +[TestFixture] +public class HomeTests : BlogTest +{ + [Test] + public async Task Home_WhenNavigatedTo_ItShouldDisplayCorrectPageTitle() + { + await Page.GotoAsync("/"); + var title = await Page.TitleAsync(); + title.Should().Be("journal - A blog by Stevan Freeborn"); + } + + [Test] + public async Task Home_WhenNavigatedTo_ItShouldCorrectSiteTitle() + { + await Page.GotoAsync("/"); + var message = Page.GetByRole(AriaRole.Heading, new () { Name = "journal" }); + await Expect(message).ToBeVisibleAsync(); + } + + [Test] + public async Task Home_WhenNavigatedTo_ItShouldDisplayWrittenByAttribution() + { + await Page.GotoAsync("/"); + var message = Page.GetByText("by"); + var image = Page.GetByAltText("Stevan Freeborn"); + + await Expect(message).ToBeVisibleAsync(); + await Expect(image).ToBeVisibleAsync(); + } +} \ No newline at end of file diff --git a/test/Blog.Tests/GlobalUsings.cs b/test/Blog.Tests/GlobalUsings.cs index cefced4..7892f58 100644 --- a/test/Blog.Tests/GlobalUsings.cs +++ b/test/Blog.Tests/GlobalUsings.cs @@ -1 +1,10 @@ -global using NUnit.Framework; \ No newline at end of file +global using NUnit.Framework; +global using Microsoft.AspNetCore.Mvc.Testing; +global using Microsoft.Extensions.Hosting; +global using Microsoft.AspNetCore.Hosting; +global using Microsoft.Playwright.NUnit; +global using Microsoft.Playwright; +global using FluentAssertions; +global using Microsoft.AspNetCore.Hosting.Server; +global using Microsoft.Extensions.DependencyInjection; +global using System.Net; \ No newline at end of file diff --git a/test/Blog.Tests/Unit/Header.spec.razor b/test/Blog.Tests/Unit/Header.spec.razor new file mode 100644 index 0000000..7963c28 --- /dev/null +++ b/test/Blog.Tests/Unit/Header.spec.razor @@ -0,0 +1,32 @@ +@inherits Bunit.TestContext + +@code +{ + [Test] + public void Header_WhenRendered_ItShouldContainSiteTitle() + { + var cut = Render(@
); + + var heading = cut.Find("h1"); + + heading.MarkupMatches(@

journal

); + } + + [Test] + public void Header_WhenRendered_ItShouldContainAuthorInfo() + { + var cut = Render(@
); + + var author = cut.Find(".author-container > span"); + + author.MarkupMatches(@by); + } + + [Test] + public void Header_WhenRendered_ItShouldContainAuthorImage() + { + var cut = Render(@
); + var image = cut.Find("img[alt='Stevan Freeborn']"); + image.Should().NotBeNull(); + } +} diff --git a/test/Blog.Tests/UnitTest1.cs b/test/Blog.Tests/UnitTest1.cs deleted file mode 100644 index cd61a57..0000000 --- a/test/Blog.Tests/UnitTest1.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Blog.Tests; - -public class Tests -{ - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } -} \ No newline at end of file diff --git a/test/Blog.Tests/_Imports.razor b/test/Blog.Tests/_Imports.razor new file mode 100644 index 0000000..99e4f3d --- /dev/null +++ b/test/Blog.Tests/_Imports.razor @@ -0,0 +1,3 @@ +@using Bunit +@using Blog.Components +@using FluentAssertions \ No newline at end of file