Scaffold v1.0.0
This commit is contained in:
32
Tests/EmbedderToolTests/FileExtensionAllowListTests.swift
Normal file
32
Tests/EmbedderToolTests/FileExtensionAllowListTests.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
import Testing
|
||||
@testable import EmbedderTool
|
||||
|
||||
@Suite("FileExtensionAllowList") struct FileExtensionAllowListTests {
|
||||
|
||||
@Test("permits common textual formats")
|
||||
func permitsTextual() {
|
||||
let textual = ["json", "yaml", "yml", "html", "eml", "txt", "md", "xml", "csv", "svg"]
|
||||
for fileExtension in textual {
|
||||
#expect(FileExtensionAllowList.permits(fileExtension))
|
||||
}
|
||||
}
|
||||
|
||||
@Test("ignores letter casing")
|
||||
func caseInsensitive() {
|
||||
#expect(FileExtensionAllowList.permits("JSON"))
|
||||
#expect(FileExtensionAllowList.permits("Html"))
|
||||
}
|
||||
|
||||
@Test("rejects known binary extensions")
|
||||
func rejectsBinary() {
|
||||
let binary = ["png", "jpg", "jpeg", "pdf", "zip", "gif", "ttf", "woff", "mp3", "mp4"]
|
||||
for fileExtension in binary {
|
||||
#expect(!FileExtensionAllowList.permits(fileExtension))
|
||||
}
|
||||
}
|
||||
|
||||
@Test("rejects files without an extension")
|
||||
func rejectsMissingExtension() {
|
||||
#expect(!FileExtensionAllowList.permits(""))
|
||||
}
|
||||
}
|
||||
53
Tests/EmbedderToolTests/IdentifierSanitizerTests.swift
Normal file
53
Tests/EmbedderToolTests/IdentifierSanitizerTests.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
import Testing
|
||||
@testable import EmbedderTool
|
||||
|
||||
@Suite("IdentifierSanitizer") struct IdentifierSanitizerTests {
|
||||
|
||||
@Test("converts a simple filename with extension to lowerCamelCase")
|
||||
func simpleFilename() {
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "welcome.html") == "welcomeHtml")
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "config.json") == "configJson")
|
||||
}
|
||||
|
||||
@Test("normalizes uppercase and mixed case extensions")
|
||||
func uppercaseExtension() {
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "welcome.HTML") == "welcomeHtml")
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "Data.Yaml") == "dataYaml")
|
||||
}
|
||||
|
||||
@Test("splits snake_case, kebab-case and camelCase into words")
|
||||
func splitWords() {
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "user_profile.json") == "userProfileJson")
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "email-template.eml") == "emailTemplateEml")
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "fooBar.json") == "fooBarJson")
|
||||
}
|
||||
|
||||
@Test("prefixes an underscore when a filename starts with a digit")
|
||||
func digitPrefix() {
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "404.html") == "_404Html")
|
||||
}
|
||||
|
||||
@Test("escapes Swift reserved keywords with backticks")
|
||||
func reservedKeyword() {
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "class") == "`class`")
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "return") == "`return`")
|
||||
}
|
||||
|
||||
@Test("collapses runs of non-alphanumeric characters")
|
||||
func multipleSeparators() {
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "user--profile__v2.json") == "userProfileV2Json")
|
||||
}
|
||||
|
||||
@Test("produces UpperCamelCase type names for directories")
|
||||
func typeNames() {
|
||||
#expect(IdentifierSanitizer.typeName(from: "emails") == "Emails")
|
||||
#expect(IdentifierSanitizer.typeName(from: "user-templates") == "UserTemplates")
|
||||
#expect(IdentifierSanitizer.typeName(from: "api_v2") == "ApiV2")
|
||||
}
|
||||
|
||||
@Test("returns an underscore fallback when the identifier would be empty")
|
||||
func emptyFallback() {
|
||||
#expect(IdentifierSanitizer.propertyName(fromFilename: "---") == "_")
|
||||
#expect(IdentifierSanitizer.typeName(from: "") == "_")
|
||||
}
|
||||
}
|
||||
67
Tests/EmbedderToolTests/NamespaceTreeBuilderTests.swift
Normal file
67
Tests/EmbedderToolTests/NamespaceTreeBuilderTests.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import EmbedderTool
|
||||
|
||||
@Suite("NamespaceTreeBuilder") struct NamespaceTreeBuilderTests {
|
||||
|
||||
@Test("places top-level files directly inside the root namespace")
|
||||
func topLevelFiles() {
|
||||
let files = [
|
||||
makeFile(relativePath: "config.json"),
|
||||
makeFile(relativePath: "welcome.html")
|
||||
]
|
||||
let tree = NamespaceTreeBuilder().buildTree(from: files)
|
||||
|
||||
#expect(tree.name == "Embedded")
|
||||
#expect(tree.files.map(\.filename) == ["config.json", "welcome.html"])
|
||||
#expect(tree.subNamespaces.isEmpty)
|
||||
}
|
||||
|
||||
@Test("nests subdirectories as child enums")
|
||||
func nestedDirectories() {
|
||||
let files = [
|
||||
makeFile(relativePath: "emails/welcome.html"),
|
||||
makeFile(relativePath: "emails/receipt.eml"),
|
||||
makeFile(relativePath: "root.json")
|
||||
]
|
||||
let tree = NamespaceTreeBuilder().buildTree(from: files)
|
||||
|
||||
#expect(tree.files.map(\.filename) == ["root.json"])
|
||||
#expect(tree.subNamespaces.count == 1)
|
||||
#expect(tree.subNamespaces[0].name == "Emails")
|
||||
#expect(tree.subNamespaces[0].files.map(\.filename) == ["receipt.eml", "welcome.html"])
|
||||
}
|
||||
|
||||
@Test("merges multiple files under the same sanitized directory name")
|
||||
func mergesSiblingNamespaces() {
|
||||
let files = [
|
||||
makeFile(relativePath: "user-templates/a.json"),
|
||||
makeFile(relativePath: "user-templates/b.json")
|
||||
]
|
||||
let tree = NamespaceTreeBuilder().buildTree(from: files)
|
||||
|
||||
#expect(tree.subNamespaces.count == 1)
|
||||
#expect(tree.subNamespaces[0].name == "UserTemplates")
|
||||
#expect(tree.subNamespaces[0].files.count == 2)
|
||||
}
|
||||
|
||||
@Test("preserves deep hierarchies")
|
||||
func deepHierarchy() {
|
||||
let files = [
|
||||
makeFile(relativePath: "api/v2/users/list.json")
|
||||
]
|
||||
let tree = NamespaceTreeBuilder().buildTree(from: files)
|
||||
|
||||
#expect(tree.subNamespaces.count == 1)
|
||||
#expect(tree.subNamespaces[0].name == "Api")
|
||||
#expect(tree.subNamespaces[0].subNamespaces[0].name == "V2")
|
||||
#expect(tree.subNamespaces[0].subNamespaces[0].subNamespaces[0].name == "Users")
|
||||
#expect(tree.subNamespaces[0].subNamespaces[0].subNamespaces[0].files.map(\.filename) == ["list.json"])
|
||||
}
|
||||
|
||||
private func makeFile(relativePath: String) -> EmbeddableFile {
|
||||
let components = relativePath.split(separator: "/").map(String.init)
|
||||
let absoluteURL = URL(fileURLWithPath: "/fake/Static Inline").appending(path: relativePath)
|
||||
return EmbeddableFile(absoluteURL: absoluteURL, relativePathComponents: components)
|
||||
}
|
||||
}
|
||||
37
Tests/EmbedderToolTests/StringLiteralEscaperTests.swift
Normal file
37
Tests/EmbedderToolTests/StringLiteralEscaperTests.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
import Testing
|
||||
@testable import EmbedderTool
|
||||
|
||||
@Suite("StringLiteralEscaper") struct StringLiteralEscaperTests {
|
||||
|
||||
@Test("wraps plain content with a single hash delimiter")
|
||||
func plainContent() {
|
||||
let literal = StringLiteralEscaper.rawTripleQuotedLiteral(from: "hello")
|
||||
#expect(literal == "#\"\"\"\nhello\n\"\"\"#")
|
||||
}
|
||||
|
||||
@Test("uses two hashes when content contains a triple-quote followed by one hash")
|
||||
func escalatesForSingleHashCollision() {
|
||||
let literal = StringLiteralEscaper.rawTripleQuotedLiteral(from: "before\"\"\"#after")
|
||||
#expect(literal == "##\"\"\"\nbefore\"\"\"#after\n\"\"\"##")
|
||||
}
|
||||
|
||||
@Test("escalates hash count past the longest run seen in content")
|
||||
func escalatesForLongerHashRun() {
|
||||
let literal = StringLiteralEscaper.rawTripleQuotedLiteral(from: "x\"\"\"####y")
|
||||
#expect(literal.hasPrefix("#####\"\"\""))
|
||||
#expect(literal.hasSuffix("\"\"\"#####"))
|
||||
}
|
||||
|
||||
@Test("accepts empty content")
|
||||
func emptyContent() {
|
||||
let literal = StringLiteralEscaper.rawTripleQuotedLiteral(from: "")
|
||||
#expect(literal == "#\"\"\"\n\n\"\"\"#")
|
||||
}
|
||||
|
||||
@Test("leaves triple-quotes without trailing hashes untouched")
|
||||
func tripleQuotesWithoutHashes() {
|
||||
let literal = StringLiteralEscaper.rawTripleQuotedLiteral(from: "a\"\"\"b")
|
||||
#expect(literal.hasPrefix("#\"\"\""))
|
||||
#expect(literal.hasSuffix("\"\"\"#"))
|
||||
}
|
||||
}
|
||||
79
Tests/EmbedderToolTests/SwiftCodeGeneratorTests.swift
Normal file
79
Tests/EmbedderToolTests/SwiftCodeGeneratorTests.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import EmbedderTool
|
||||
|
||||
@Suite("SwiftCodeGenerator") struct SwiftCodeGeneratorTests {
|
||||
|
||||
@Test("generates a compilable file for top-level files")
|
||||
func topLevelFiles() throws {
|
||||
let directory = try TemporaryDirectory()
|
||||
let configFile = try directory.write(contents: #"{"key":"value"}"#, toRelativePath: "config.json")
|
||||
|
||||
let root = NamespaceNode(
|
||||
name: "Embedded",
|
||||
files: [
|
||||
EmbeddableFile(
|
||||
absoluteURL: configFile,
|
||||
relativePathComponents: ["config.json"]
|
||||
)
|
||||
]
|
||||
)
|
||||
let output = try SwiftCodeGenerator().generate(from: root)
|
||||
|
||||
#expect(output.contains("enum Embedded {"))
|
||||
#expect(output.contains("static let configJson: String = #\"\"\""))
|
||||
#expect(output.contains(#"{"key":"value"}"#))
|
||||
#expect(output.contains("\"\"\"#"))
|
||||
}
|
||||
|
||||
@Test("nests child enums for subdirectories")
|
||||
func nestedNamespaces() throws {
|
||||
let directory = try TemporaryDirectory()
|
||||
let welcomeFile = try directory.write(contents: "<html></html>", toRelativePath: "emails/welcome.html")
|
||||
|
||||
let root = NamespaceNode(
|
||||
name: "Embedded",
|
||||
subNamespaces: [
|
||||
NamespaceNode(
|
||||
name: "Emails",
|
||||
files: [
|
||||
EmbeddableFile(
|
||||
absoluteURL: welcomeFile,
|
||||
relativePathComponents: ["emails", "welcome.html"]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
let output = try SwiftCodeGenerator().generate(from: root)
|
||||
|
||||
#expect(output.contains("enum Embedded {"))
|
||||
#expect(output.contains("enum Emails {"))
|
||||
#expect(output.contains("static let welcomeHtml: String = #\"\"\""))
|
||||
#expect(output.contains("<html></html>"))
|
||||
}
|
||||
|
||||
@Test("includes a generated-file header")
|
||||
func header() throws {
|
||||
let output = try SwiftCodeGenerator().generate(from: NamespaceNode(name: "Embedded"))
|
||||
#expect(output.hasPrefix("// Generated by the Embedder plugin"))
|
||||
}
|
||||
}
|
||||
|
||||
private struct TemporaryDirectory {
|
||||
let url: URL
|
||||
|
||||
init() throws {
|
||||
let baseURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
self.url = baseURL.appending(path: "EmbedderToolTests-\(UUID().uuidString)")
|
||||
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
func write(contents: String, toRelativePath relativePath: String) throws -> URL {
|
||||
let fileURL = url.appending(path: relativePath)
|
||||
let parent = fileURL.deletingLastPathComponent()
|
||||
try FileManager.default.createDirectory(at: parent, withIntermediateDirectories: true)
|
||||
try contents.write(to: fileURL, atomically: true, encoding: .utf8)
|
||||
return fileURL
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user