Implemented phase 0 for the POC prototype in the Website service.
This commit is contained in:
1 parent
916df7e2f0
commit
717df5208c
31 files changed
+2351
-240
No files matched your search
@@ -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 & 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."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user