Added the ImageWidth enumeration to the Website library target.
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
/// A rendition an image asset is available at, from the narrowest up to the full-size file.
|
||||||
|
///
|
||||||
|
/// ``StaticFile`` names one file per rendition, and a page's `srcset` offers them all so the browser picks by rendered width.
|
||||||
|
/// The declaration order is the `srcset` order: narrowest first.
|
||||||
|
enum ImageWidth: CaseIterable {
|
||||||
|
/// The small rendition, 480 pixels wide.
|
||||||
|
case small
|
||||||
|
/// The medium rendition, 800 pixels wide.
|
||||||
|
case medium
|
||||||
|
/// The large rendition: the full-size file, 1200 pixels wide.
|
||||||
|
case large
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Extensions
|
||||||
|
|
||||||
|
extension ImageWidth {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
/// The rendition's width in pixels: what the file is resampled to, and what its `srcset` descriptor states.
|
||||||
|
var width: Int {
|
||||||
|
switch self {
|
||||||
|
case .small: 480
|
||||||
|
case .medium: 800
|
||||||
|
case .large: 1200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,18 +14,26 @@ struct StaticFile: Asset {
|
|||||||
/// The file's name, without extension.
|
/// The file's name, without extension.
|
||||||
let fileName: String
|
let fileName: String
|
||||||
|
|
||||||
|
/// The folder holding the file, or `nil` when it sits in its extensions' own folders.
|
||||||
|
///
|
||||||
|
/// Imagery sits in a folder per page — `img/index`, `img/about` — rather than in its extension's own.
|
||||||
|
let folder: String?
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
/// Declares a static file.
|
/// Declares a static file.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - fileName: the file's name, without extension.
|
/// - fileName: the file's name, without extension.
|
||||||
|
/// - folder: the folder holding the file, or `nil` (the default) to use each extension's own folder.
|
||||||
/// - fileExtensions: the extensions the file is available with, one file each.
|
/// - fileExtensions: the extensions the file is available with, one file each.
|
||||||
init(
|
init(
|
||||||
_ fileName: String,
|
_ fileName: String,
|
||||||
|
in folder: String? = nil,
|
||||||
as fileExtensions: AssetExtension...
|
as fileExtensions: AssetExtension...
|
||||||
) {
|
) {
|
||||||
self.fileExtensions = fileExtensions
|
self.fileExtensions = fileExtensions
|
||||||
self.fileName = fileName
|
self.fileName = fileName
|
||||||
|
self.folder = folder
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -55,6 +63,37 @@ extension StaticFile {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
extension StaticFile {
|
||||||
|
|
||||||
|
/// The `srcset` value offering every rendition of a responsive image, narrowest first, ending on the full-size file.
|
||||||
|
///
|
||||||
|
/// The renditions are named by the given closure, which conventionally gives the `large` one the bare file name and the narrower ones their
|
||||||
|
/// width — as `srcset` files are usually named:
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// static func portrait(_ width: ImageWidth) -> Self {
|
||||||
|
/// Self(width == .large ? "portrait" : "portrait-\(width.width)", in: "img/about", as: .jpg, .webp)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// - Parameters:
|
||||||
|
/// - fileExtension: the format the renditions are named in.
|
||||||
|
/// - version: the version token appended to each URL, or `nil` (the default) to leave them unversioned.
|
||||||
|
/// - rendition: the file naming a given width.
|
||||||
|
/// - Returns: the `srcset` value for that format.
|
||||||
|
static func srcSet(
|
||||||
|
for fileExtension: AssetExtension,
|
||||||
|
version: String? = nil,
|
||||||
|
rendition: (ImageWidth) -> Self
|
||||||
|
) -> String {
|
||||||
|
ImageWidth.allCases
|
||||||
|
.map { "\(rendition($0).urlPath(for: fileExtension, version: version)) \($0.width)w" }
|
||||||
|
.joined(separator: ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Constants
|
// MARK: - Constants
|
||||||
|
|
||||||
extension StaticFile {
|
extension StaticFile {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import Testing
|
||||||
|
|
||||||
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
|
@Suite(
|
||||||
|
"ImageWidth enumeration",
|
||||||
|
.tags(.enumeration)
|
||||||
|
)
|
||||||
|
struct ImageWidthTests {
|
||||||
|
|
||||||
|
// MARK: Computed tests
|
||||||
|
|
||||||
|
@Test(arguments: zip(
|
||||||
|
ImageWidth.allCases,
|
||||||
|
[480, 800, 1200]
|
||||||
|
))
|
||||||
|
func `width`(
|
||||||
|
for imageWidth: ImageWidth,
|
||||||
|
expects width: Int
|
||||||
|
) {
|
||||||
|
#expect(imageWidth.width == width)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -33,6 +33,49 @@ struct StaticFileTests {
|
|||||||
#expect(font.urlPath(for: .woff2) == "/font/a-font-400.woff2")
|
#expect(font.urlPath(for: .woff2) == "/font/a-font-400.woff2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Imagery sits in a folder per page, so a declared folder replaces the extension's own for every extension the file has.
|
||||||
|
@Test
|
||||||
|
func `declared folder paths`() {
|
||||||
|
let portrait = File("portrait", in: "img/about", as: .jpg, .webp)
|
||||||
|
|
||||||
|
#expect(portrait.relativePath(for: .jpg) == "img/about/portrait.jpg")
|
||||||
|
#expect(portrait.relativePath(for: .webp) == "img/about/portrait.webp")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Methods tests
|
||||||
|
|
||||||
|
@Test(arguments: zip(
|
||||||
|
[AssetExtension.jpg, .webp],
|
||||||
|
["jpg", "webp"]
|
||||||
|
))
|
||||||
|
func `srcset offers every rendition narrowest first`(
|
||||||
|
for fileExtension: AssetExtension,
|
||||||
|
expects suffix: String
|
||||||
|
) {
|
||||||
|
let srcSet = File.srcSet(for: fileExtension) { width in
|
||||||
|
File(
|
||||||
|
width == .large ? "portrait" : "portrait-\(width.width)",
|
||||||
|
in: "img/about",
|
||||||
|
as: .jpg, .webp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#expect(srcSet == [
|
||||||
|
"/img/about/portrait-480.\(suffix) 480w",
|
||||||
|
"/img/about/portrait-800.\(suffix) 800w",
|
||||||
|
"/img/about/portrait.\(suffix) 1200w"
|
||||||
|
].joined(separator: ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `srcset versions every rendition when given a version`() {
|
||||||
|
let srcSet = File.srcSet(for: .jpg, version: "0123456789abcdef") { _ in
|
||||||
|
File("portrait", in: "img/about", as: .jpg)
|
||||||
|
}
|
||||||
|
|
||||||
|
#expect(srcSet.components(separatedBy: "?v=0123456789abcdef").count - 1 == ImageWidth.allCases.count)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Constants tests
|
// MARK: Constants tests
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user