Implemented phase 0 for the POC prototype in the Website service.

This commit is contained in:
javier committed 2026-09-21 10:22:55 +02:00
1 parent 916df7e2f0
commit 717df5208c
31 files changed
+2351 -240

No files matched your search

+1 -1
View File
@@ -53,7 +53,7 @@ struct AppTests {
#expect(response.status == .ok)
#expect(response.headers[.contentType] == "text/html; charset=utf-8")
#expect(body.contains("Hello world!"))
#expect(body.contains("Back issues, from shops you can vet first."))
}
}
}
@@ -0,0 +1,30 @@
import Elementary
import Testing
@testable import WebsiteLibrary
@Suite(
"Icon component",
.tags(.component)
)
struct IconTests {
// MARK: Functional tests
@Test
func `renders a decorative icon at the given size`() {
let html = Icon(.search, size: 16).render()
#expect(html.hasPrefix("<svg"))
#expect(html.contains(#"width="16" height="16""#))
#expect(html.contains(#"aria-hidden="true""#))
#expect(html.contains(#"stroke="currentColor""#))
}
@Test
func `renders no inline styles`() {
// The Content-Security-Policy blocks `style` attributes, inside an SVG as anywhere else.
#expect(!Icon(.key).render().contains("style="))
}
}
@@ -0,0 +1,40 @@
import Elementary
import Testing
@testable import WebsiteLibrary
@Suite(
"SiteFooter component",
.tags(.component)
)
struct SiteFooterTests {
// MARK: Functional tests
@Test
func `renders its link columns`() {
let html = SiteFooter().render()
for title in ["Browse", "Sell", "Account", "Language"] {
#expect(html.contains(">\(title)</h2>"))
}
for path in [String.Route.series, .Route.openShop, .Route.gradingGuide, .Route.signIn, .Route.recover, .Route.terms] {
#expect(html.contains(#"href="\#(path)""#))
}
}
@Test
func `escapes the ampersands in its labels`() {
#expect(SiteFooter().render().contains("Want list &amp; alerts"))
}
@Test
func `presses the one language the prototype serves`() {
let html = SiteFooter().render()
#expect(html.components(separatedBy: #"aria-pressed="true""#).count - 1 == 1)
#expect(html.contains(#"aria-pressed="true">EN<"#))
}
}
@@ -0,0 +1,78 @@
import Elementary
import Testing
@testable import WebsiteLibrary
@Suite(
"SiteHeader component",
.tags(.component)
)
struct SiteHeaderTests {
// MARK: Functional tests
@Test
func `renders the signed-out actions without a viewer`() {
let html = SiteHeader().render()
#expect(html.contains(#"href="/sign-in""#))
#expect(html.contains("Open a shop"))
#expect(!html.contains(#"href="/account""#))
#expect(!html.contains(#"href="/messages""#))
}
@Test
func `renders the signed-in actions for a viewer`() {
let html = SiteHeader(viewer: .init(
displayName: "Mara Visser",
newWantMatches: 2,
unreadMessages: 3
)).render()
#expect(html.contains(#"aria-label="Want list, 2 new matches""#))
#expect(html.contains(#"aria-label="Messages, 3 unread""#))
#expect(html.contains(#"href="/sell""#))
#expect(html.contains(">MV<"))
#expect(!html.contains(#"href="/sign-in""#))
}
@Test
func `omits a badge that counts nothing`() {
let html = SiteHeader(viewer: .init(displayName: "Mara")).render()
#expect(html.contains(#"aria-label="Messages""#))
#expect(!html.contains("site-header__badge"))
}
@Test
func `links every format and marks none current by default`() {
let html = SiteHeader().render()
for format in Format.allCases {
#expect(html.contains(#"href="\#(format.path)""#))
}
#expect(!html.contains("aria-current"))
}
@Test(arguments: Format.allCases)
func `marks the active format alone as current`(
_ format: Format
) {
let html = SiteHeader(active: format).render()
#expect(html.components(separatedBy: #"aria-current="page""#).count - 1 == 1)
#expect(html.contains(#"href="\#(format.path)" aria-current="page""#))
}
@Test
func `searches through a plain GET form`() {
let html = SiteHeader().render()
#expect(html.contains(#"action="/search""#))
#expect(html.contains(#"method="get""#))
#expect(html.contains(#"name="q""#))
#expect(html.contains(#"for="site-search-query""#))
}
}
@@ -0,0 +1,29 @@
import Testing
@testable import WebsiteLibrary
@Suite(
"Format enumeration",
.tags(.enumeration)
)
struct FormatTests {
// MARK: Computed tests
@Test(arguments: zip(
Format.allCases,
[
"/search?format=single-issues",
"/search?format=manga",
"/search?format=trade-paperbacks",
"/search?format=hardcovers",
]
))
func `path`(
of format: Format,
expects path: String
) {
#expect(format.path == path)
}
}
@@ -27,16 +27,47 @@ struct IndexPageTests {
#expect(html.contains("/apple-touch-icon.png"))
#expect(html.contains("/site.webmanifest"))
#expect(html.contains(#"media="(prefers-color-scheme: dark)""#))
#expect(html.contains("Hello world!"))
#expect(html.contains("Back issues, from shops you can vet first."))
#expect(html.contains("/js/shared.js"))
#expect(html.contains("/js/index.js"))
}
@Test
func `renders no font preloads until fonts are declared`() {
// The template ships no fonts, so a preload link here would point at a file the service does not serve.
#expect(IndexPage.preloadedFonts.isEmpty)
#expect(!IndexPage(locale: .init(identifier: "en")).render().contains(#"rel="preload""#))
func `renders the site frame around its content`() {
let html = IndexPage(
locale: .init(identifier: "en")
).render()
#expect(html.contains(#"class="skip-link""#))
#expect(html.contains(#"class="site-header""#))
#expect(html.contains(#"<main id="main">"#))
#expect(html.contains(#"class="site-footer""#))
}
@Test
func `renders no inline styles`() {
// The Content-Security-Policy is `default-src 'self'`, which blocks `style` attributes and elements alike.
let html = IndexPage(
locale: .init(identifier: "en")
).render()
#expect(!html.contains("style="))
#expect(!html.contains("<style"))
}
@Test
func `preloads every font face unversioned`() {
// A preload URL must match the stylesheet's `@font-face` source exactly, or the browser fetches the font twice.
let html = IndexPage(
locale: .init(identifier: "en"),
assetVersion: "0123456789abcdef"
).render()
#expect(IndexPage.preloadedFonts.count == 3)
for font in IndexPage.preloadedFonts {
#expect(html.contains(#"<link rel="preload" href="\#(font.urlPath(for: .woff2))" as="font""#))
}
}
@Test
@@ -95,6 +95,9 @@ private extension StaticFileTests {
static let paths: [(File, [String])] = [
(.appleTouchIcon, ["apple-touch-icon.png"]),
(.favicon, ["favicon.ico"]),
(.fontAnton, ["font/anton-400.woff2"]),
(.fontArchivo, ["font/archivo-variable.woff2"]),
(.fontOswald, ["font/oswald-variable.woff2"]),
(.icon, ["icon.svg"]),
(.icon192, ["icon-192.png"]),
(.icon512, ["icon-512.png"]),
@@ -0,0 +1,24 @@
import Testing
@testable import WebsiteLibrary
@Suite(
"Viewer type",
.tags(.type)
)
struct ViewerTests {
// MARK: Computed tests
@Test(arguments: zip(
["Mara Visser", "Mara", "mara de visser", ""],
["MV", "M", "MD", ""]
))
func `initials`(
of displayName: String,
expects initials: String
) {
#expect(Viewer(displayName: displayName).initials == initials)
}
}
@@ -37,7 +37,7 @@ struct RootControllerTests {
#expect(response.headers[.contentType] == "text/html; charset=utf-8")
#expect(response.headers[.contentLanguage] == "en")
#expect(response.headers[.vary] == "Accept-Language")
#expect(body.contains("Hello world!"))
#expect(body.contains("Back issues, from shops you can vet first."))
}
}
}
@@ -90,7 +90,7 @@ struct RootControllerTests {
let body = String(buffer: response.body)
#expect(response.status == .ok)
#expect(body.contains("Hello world!"))
#expect(body.contains("Back issues, from shops you can vet first."))
}
}
}
@@ -1,6 +1,8 @@
import Testing
extension Tag {
/// Tests exercising a component of the Website library.
@Tag static var component: Tag
/// Tests exercising a controller of the Website library.
@Tag static var controller: Tag
/// Tests exercising an enumeration of the Website library.