From 74e81e1517da3b0355aeb28cff6ca1983edcf28e Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 24 Aug 2026 01:19:50 +0200 Subject: [PATCH] Refactored the StaticFile enumeration in the Website library target into a structure. --- .../Internal/Enumerations/StaticFile.swift | 72 -------------- .../Library/Internal/Types/StaticFile.swift | 93 +++++++++++++++++++ Services/Website/Tests/App/AppTests.swift | 2 +- .../Enumerations/StaticFileTests.swift | 82 ---------------- .../Internal/Types/StaticFileTests.swift | 57 ++++++++++++ .../Utils/Extensions/Tag+Constants.swift | 2 + 6 files changed, 153 insertions(+), 155 deletions(-) delete mode 100644 Services/Website/Sources/Library/Internal/Enumerations/StaticFile.swift create mode 100644 Services/Website/Sources/Library/Internal/Types/StaticFile.swift delete mode 100644 Services/Website/Tests/Library/Cases/Internal/Enumerations/StaticFileTests.swift create mode 100644 Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift diff --git a/Services/Website/Sources/Library/Internal/Enumerations/StaticFile.swift b/Services/Website/Sources/Library/Internal/Enumerations/StaticFile.swift deleted file mode 100644 index 945ca74..0000000 --- a/Services/Website/Sources/Library/Internal/Enumerations/StaticFile.swift +++ /dev/null @@ -1,72 +0,0 @@ -import Infrastructure - -/// A static file shipped with the website service. -/// -/// Each case identifies a file name stored under the static files root (the `Resources/Static` directory) and served by Hummingbird's -/// `FileMiddleware` middleware. A name can be available with more than one extension (see ``fileExtensions``), each resolving to its own file. -enum StaticFile: Asset, CaseIterable { - /// The `apple-touch-icon.png` icon. - case appleTouchIcon - /// The `favicon.ico` icon. - case favicon - /// The `icon.svg` icon. - case icon - /// The `icon-192.png` icon for the web manifest. - case icon192 - /// The `icon-512.png` icon for the web manifest. - case icon512 - /// The `css/index.css` stylesheet and `js/index.js` script for the landing page. - case index - /// The `css/not-found.css` stylesheet and `js/not-found.js` script for the not-found page. - case notFound - /// The `robots.txt` crawler directives. - case robots - /// The `css/shared.css` stylesheet and `js/shared.js` script shared across pages. - case shared - /// The `site.webmanifest` web application manifest. - case site - /// The `sitemap.xml` crawler sitemap. - case sitemap -} - -// MARK: - Extensions - -extension StaticFile { - - // MARK: Computed - - /// The file extensions the file is available with. - var fileExtensions: [AssetExtension] { - switch self { - case .appleTouchIcon, - .icon192, - .icon512: [.png] - case .index, - .notFound, - .shared: [.css, .js] - case .favicon: [.ico] - case .icon: [.svg] - case .robots: [.txt] - case .site: [.webmanifest] - case .sitemap: [.xml] - } - } - - /// The file's name, without extension. - var fileName: String { - switch self { - case .appleTouchIcon: "apple-touch-icon" - case .favicon: "favicon" - case .icon: "icon" - case .icon192: "icon-192" - case .icon512: "icon-512" - case .index: "index" - case .notFound: "not-found" - case .robots: "robots" - case .shared: "shared" - case .site: "site" - case .sitemap: "sitemap" - } - } - -} diff --git a/Services/Website/Sources/Library/Internal/Types/StaticFile.swift b/Services/Website/Sources/Library/Internal/Types/StaticFile.swift new file mode 100644 index 0000000..8a8a857 --- /dev/null +++ b/Services/Website/Sources/Library/Internal/Types/StaticFile.swift @@ -0,0 +1,93 @@ +import Infrastructure + +/// A static file shipped with the website service. +/// +/// Each constant declares one file stored under the static files root (the `Resources/Static` directory) and served by Hummingbird's +/// `FileMiddleware` middleware. A file can be available with more than one extension (see ``fileExtensions``), each resolving to its own file. +struct StaticFile: Asset { + + // MARK: Properties + + /// The file extensions the file is available with. + let fileExtensions: [AssetExtension] + + /// The file's name, without extension. + let fileName: String + + // MARK: Initializers + + /// Declares a static file. + /// - Parameters: + /// - fileName: the file's name, without extension. + /// - fileExtensions: the extensions the file is available with, one file each. + init( + _ fileName: String, + as fileExtensions: AssetExtension... + ) { + self.fileExtensions = fileExtensions + self.fileName = fileName + } + +} + +// MARK: - Extensions + +extension StaticFile { + + // MARK: - Constants + + /// Every file the service ships. + /// + /// Spelled out because Swift cannot enumerate a type's own constants: a file missing here is still served, but goes untested. + static let all: [Self] = [ + .appleTouchIcon, + .favicon, + .icon, + .icon192, + .icon512, + .index, + .notFound, + .robots, + .shared, + .site, + .sitemap, + ] + +} + +// MARK: - Constants + +extension StaticFile { + /// The `apple-touch-icon.png` icon. + static let appleTouchIcon = Self("apple-touch-icon", as: .png) + + /// The `favicon.ico` icon. + static let favicon = Self("favicon", as: .ico) + + /// The `icon.svg` icon. + static let icon = Self("icon", as: .svg) + + /// The `icon-192.png` icon for the web manifest. + static let icon192 = Self("icon-192", as: .png) + + /// The `icon-512.png` icon for the web manifest. + static let icon512 = Self("icon-512", as: .png) + + /// The `css/index.css` stylesheet and `js/index.js` script for the landing page. + static let index = Self("index", as: .css, .js) + + /// The `css/not-found.css` stylesheet and `js/not-found.js` script for the not-found page. + static let notFound = Self("not-found", as: .css, .js) + + /// The `robots.txt` crawler directives. + static let robots = Self("robots", as: .txt) + + /// The `css/shared.css` stylesheet and `js/shared.js` script shared across pages. + static let shared = Self("shared", as: .css, .js) + + /// The `site.webmanifest` web application manifest. + static let site = Self("site", as: .webmanifest) + + /// The `sitemap.xml` crawler sitemap. + static let sitemap = Self("sitemap", as: .xml) +} diff --git a/Services/Website/Tests/App/AppTests.swift b/Services/Website/Tests/App/AppTests.swift index a455a32..372d6d5 100644 --- a/Services/Website/Tests/App/AppTests.swift +++ b/Services/Website/Tests/App/AppTests.swift @@ -107,7 +107,7 @@ struct AppTests { } } - @Test(arguments: StaticFile.allCases) + @Test(arguments: StaticFile.all) func `static files to be served`( staticFile file: StaticFile ) async throws { diff --git a/Services/Website/Tests/Library/Cases/Internal/Enumerations/StaticFileTests.swift b/Services/Website/Tests/Library/Cases/Internal/Enumerations/StaticFileTests.swift deleted file mode 100644 index b675544..0000000 --- a/Services/Website/Tests/Library/Cases/Internal/Enumerations/StaticFileTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Infrastructure -import Testing - -@testable import WebsiteLibrary - -@Suite( - "StaticFile enumeration", - .tags(.enumeration) -) -struct StaticFileTests { - - // MARK: Type aliases - - typealias File = StaticFile - - // MARK: Computed tests - - @Test(arguments: zip( - File.allCases, - Self.fileExtensions - )) - func `file extensions`( - for file: File, - expects extensions: [AssetExtension] - ) { - #expect(file.fileExtensions == extensions) - } - - @Test(arguments: zip( - File.allCases, - Self.fileNames - )) - func `file name`( - for file: File, - expects fileName: String - ) { - #expect(file.fileName == fileName) - } - - // MARK: CaseIterable tests - - @Test - func `all cases`() { - #expect(File.allCases.count == 11) - } - -} - -// MARK: - Helpers - -private extension StaticFileTests { - - // MARK: Constants - - static let fileExtensions: [[AssetExtension]] = [ - [.png], - [.ico], - [.svg], - [.png], - [.png], - [.css, .js], - [.css, .js], - [.txt], - [.css, .js], - [.webmanifest], - [.xml] - ] - static let fileNames: [String] = [ - "apple-touch-icon", - "favicon", - "icon", - "icon-192", - "icon-512", - "index", - "not-found", - "robots", - "shared", - "site", - "sitemap" - ] - -} diff --git a/Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift b/Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift new file mode 100644 index 0000000..726c34f --- /dev/null +++ b/Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift @@ -0,0 +1,57 @@ +import Infrastructure +import Testing + +@testable import WebsiteLibrary + +@Suite( + "StaticFile type", + .tags(.type) +) +struct StaticFileTests { + + // MARK: Type aliases + + typealias File = StaticFile + + // MARK: Computed tests + + /// The path is asserted rather than the three facts behind it: it is the only form the rest of the service sees. + @Test(arguments: Self.paths) + func `relative paths`( + for file: File, + expects paths: [String] + ) { + #expect(file.fileExtensions.map(file.relativePath) == paths) + } + + // MARK: Constants tests + + @Test + func `all files`() { + #expect(File.all.count == Self.paths.count) + } + +} + +// MARK: - Helpers + +private extension StaticFileTests { + + // MARK: Constants + + /// Every file paired with the path it is served at, one per extension, in ``StaticFile/all`` order. + static let paths: [(File, [String])] = [ + (.appleTouchIcon, ["apple-touch-icon.png"]), + (.favicon, ["favicon.ico"]), + (.icon, ["icon.svg"]), + (.icon192, ["icon-192.png"]), + (.icon512, ["icon-512.png"]), + (.index, ["css/index.css", "js/index.js"]), + (.notFound, ["css/not-found.css", "js/not-found.js"]), + (.robots, ["robots.txt"]), + (.shared, ["css/shared.css", "js/shared.js"]), + (.site, ["site.webmanifest"]), + (.sitemap, ["sitemap.xml"]), + ] + +} diff --git a/Services/Website/Tests/Library/Utils/Extensions/Tag+Constants.swift b/Services/Website/Tests/Library/Utils/Extensions/Tag+Constants.swift index 63cb88f..7fe73f7 100644 --- a/Services/Website/Tests/Library/Utils/Extensions/Tag+Constants.swift +++ b/Services/Website/Tests/Library/Utils/Extensions/Tag+Constants.swift @@ -7,4 +7,6 @@ extension Tag { @Tag static var enumeration: Tag /// Tests exercising a page of the Website library. @Tag static var page: Tag + /// Tests exercising a type of the Website library. + @Tag static var type: Tag }