This commit contains the latest updates from the generic Website template, which move the toolchain to Swift 6.4, gate analytics and structured data behind traits, and rework page routing: - Updated the Swift toolchain for all the package dependencies and the Website service to version 6.4. (345b305) - Adapted all the existing middlewares in the Infrastructure package to Swift 6.4. (26008df) - Moved test resource teardown in the Persistence and the Website library into defer blocks. (27984a9) - Gated analytics behind a trait and reworked structured data in the Infrastructure package. (c7d4769) - Enabled the Analytics trait in the Website service. (f6a6bbd) - Reworked page routing in the Website service. (d1cb2c1) - Gated the structured data features in the Infrastructure package behind a trait. (f0a8791) - Enabled the StructuredData trait of the Infrastructure package in the Website service. (bbfdeb3) - Fixed the asset pin and a build warning in the Dockerfile of the Website service. (6ed0f9b) The root README change in 345b305 was left out, as this project no longer carries that file. From d1cb2c1, the RootController rework was left out, as the prototype serves a session-aware landing page and routes its other pages through their own controllers; the view-event reporter was appended to the prototype's shared script instead. The prototype code was adapted to the stricter Swift 6.4 settings: `String(format:)` was swapped for safe formatting, the NoIndex middleware takes a `@concurrent` next handler, and the tests flagged by warnings-as-errors were tightened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
3.9 KiB
Swift
140 lines
3.9 KiB
Swift
import Elementary
|
|
import Foundation
|
|
import Infrastructure
|
|
import Testing
|
|
|
|
@testable import WebsiteLibrary
|
|
|
|
@Suite(
|
|
"Page+Defaults extension",
|
|
.tags(.extensionTests)
|
|
)
|
|
struct PageDefaultsTests {
|
|
|
|
// MARK: Computed tests
|
|
|
|
@Test
|
|
func `pairs english with a territory for its social card locale`() {
|
|
#expect(StubPage().ogLocale == "en_US")
|
|
}
|
|
|
|
/// Open Graph prefers `language_TERRITORY`, but a scraper accepts the bare code — better than pinning a territory the site never named.
|
|
@Test
|
|
func `leaves an unpaired language as a bare code`() {
|
|
#expect(StubPage(locale: .init(identifier: "nl")).ogLocale == "nl")
|
|
}
|
|
|
|
// MARK: Method tests
|
|
|
|
@Test
|
|
func `lists no alternate social card locales for a single language`() {
|
|
#expect(StubPage().ogAlternateLocales(languages: [.default]).isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `lists the social card locale of every other language`() {
|
|
#expect(StubPage().ogAlternateLocales(languages: Self.languages) == ["nl"])
|
|
#expect(StubPage(locale: .init(identifier: "nl")).ogAlternateLocales(languages: Self.languages) == ["en_US"])
|
|
}
|
|
|
|
@Test
|
|
func `renders no language alternates without an origin`() {
|
|
let html = StubPage().languageAlternates(
|
|
origin: nil,
|
|
path: "/",
|
|
languages: Self.languages
|
|
).render()
|
|
|
|
#expect(html.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `renders no language alternates for a single language`() {
|
|
let html = StubPage().languageAlternates(
|
|
origin: "https://example.com",
|
|
path: "/",
|
|
languages: [.default]
|
|
).render()
|
|
|
|
#expect(html.isEmpty)
|
|
}
|
|
|
|
/// The default language keeps the bare root; the prefixed edition collapses onto its prefix rather than carrying a trailing slash.
|
|
@Test
|
|
func `renders an alternate per language and an x-default at the root`() {
|
|
let html = StubPage().languageAlternates(
|
|
origin: "https://example.com",
|
|
path: "/",
|
|
languages: Self.languages
|
|
).render()
|
|
|
|
#expect(html.contains(#"<link rel="alternate" hreflang="en" href="https://example.com">"#))
|
|
#expect(html.contains(#"<link rel="alternate" hreflang="nl" href="https://example.com/nl">"#))
|
|
#expect(html.contains(#"<link rel="alternate" hreflang="x-default" href="https://example.com">"#))
|
|
}
|
|
|
|
@Test
|
|
func `prefixes the alternates of a nested page`() {
|
|
let html = StubPage().languageAlternates(
|
|
origin: "https://example.com",
|
|
path: "/privacy",
|
|
languages: Self.languages
|
|
).render()
|
|
|
|
#expect(html.contains(#"<link rel="alternate" hreflang="en" href="https://example.com/privacy">"#))
|
|
#expect(html.contains(#"<link rel="alternate" hreflang="nl" href="https://example.com/nl/privacy">"#))
|
|
#expect(html.contains(#"<link rel="alternate" hreflang="x-default" href="https://example.com/privacy">"#))
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private extension PageDefaultsTests {
|
|
|
|
// MARK: Constants
|
|
|
|
/// A two-language set, standing in for the multi-language catalog the template itself does not ship.
|
|
static let languages: [Language] = [
|
|
.default,
|
|
.init(identifier: "nl")
|
|
]
|
|
|
|
// MARK: Types
|
|
|
|
/// The barest ``Page`` conformance, so the shared defaults can be exercised without a real page's content.
|
|
struct StubPage: Page {
|
|
|
|
// MARK: Properties
|
|
|
|
let assetVersion: String? = nil
|
|
let locale: Locale
|
|
|
|
// MARK: Initializers
|
|
|
|
init(locale: Locale = .init(identifier: "en")) {
|
|
self.locale = locale
|
|
}
|
|
|
|
// MARK: Properties
|
|
|
|
var content: some HTML {
|
|
HTMLRaw("")
|
|
}
|
|
|
|
var scripts: [any Asset] {
|
|
[]
|
|
}
|
|
|
|
var stylesheets: [any Asset] {
|
|
[]
|
|
}
|
|
|
|
var title: String {
|
|
"Stub"
|
|
}
|
|
|
|
}
|
|
|
|
}
|