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
+

+
+
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