From f98464daf507cbcd2fa131c9481c209f7142a403 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Wed, 2 Nov 2022 22:08:45 +0100 Subject: [PATCH] Fix some logic and logistics (#103) * Revise some of the vapor provider * Rename the vapor provider The target within the package might get in a naming conflict with the other vapor provider package. As long the other package is not archived, renaming it, prevents the conflict and secures the back compatibility. * Add support for localization to the provider again * Revise the vapor provider * Add a test for the environment modifier * Update the readme file * Fix the components plugin --- Package.swift | 14 +- Plugins/ComponentsPlugin/plugin.swift | 4 +- README.md | 127 ++-------- .../Commands/Components/DeployCommand.swift | 3 +- .../EnvironmentModifier.swift | 2 +- .../Extensions/Vapor+HTMLKit.swift | 91 ++++++++ Sources/HTMLKitVapor/LingoConfiguration.swift | 47 ++++ Sources/HTMLKitVapor/ViewCache.swift | 50 ++++ .../ViewRenderer.swift} | 63 +++-- .../Extensions/Vapor+HTMLKit.swift | 64 ----- Sources/HTMLKitVaporProvider/VaporCache.swift | 38 --- Tests/HTMLKitTests/Localization/fr.json | 3 + Tests/HTMLKitTests/LocalizationTests.swift | 16 ++ .../ProviderTests.swift | 136 ----------- Tests/HTMLKitVaporTests/Localization/en.json | 3 + Tests/HTMLKitVaporTests/Localization/fr.json | 3 + Tests/HTMLKitVaporTests/ProviderTests.swift | 220 ++++++++++++++++++ 17 files changed, 500 insertions(+), 384 deletions(-) rename Sources/HTMLKit/Framework/Environment/{ => Localization}/EnvironmentModifier.swift (93%) create mode 100644 Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift create mode 100644 Sources/HTMLKitVapor/LingoConfiguration.swift create mode 100644 Sources/HTMLKitVapor/ViewCache.swift rename Sources/{HTMLKitVaporProvider/VaporRenderer.swift => HTMLKitVapor/ViewRenderer.swift} (50%) delete mode 100644 Sources/HTMLKitVaporProvider/Extensions/Vapor+HTMLKit.swift delete mode 100644 Sources/HTMLKitVaporProvider/VaporCache.swift create mode 100644 Tests/HTMLKitTests/Localization/fr.json delete mode 100644 Tests/HTMLKitVaporProviderTests/ProviderTests.swift create mode 100644 Tests/HTMLKitVaporTests/Localization/en.json create mode 100644 Tests/HTMLKitVaporTests/Localization/fr.json create mode 100644 Tests/HTMLKitVaporTests/ProviderTests.swift diff --git a/Package.swift b/Package.swift index 4e6795b1..03c2285e 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,7 @@ let package = Package( products: [ .library( name: "HTMLKit", - targets: ["HTMLKit", "HTMLKitComponents", "HTMLKitVaporProvider"] + targets: ["HTMLKit", "HTMLKitComponents", "HTMLKitVapor"] ), .plugin( name: "ComponentsPlugin", @@ -51,10 +51,11 @@ let package = Package( ] ), .target( - name: "HTMLKitVaporProvider", + name: "HTMLKitVapor", dependencies: [ .target(name: "HTMLKit"), - .product(name: "Vapor", package: "vapor") + .product(name: "Vapor", package: "vapor"), + .product(name: "Lingo", package: "lingo") ] ), .testTarget( @@ -83,11 +84,14 @@ let package = Package( ] ), .testTarget( - name: "HTMLKitVaporProviderTests", + name: "HTMLKitVaporTests", dependencies: [ - .target(name: "HTMLKitVaporProvider"), + .target(name: "HTMLKitVapor"), .target(name: "HTMLKit"), .product(name: "XCTVapor", package: "vapor") + ], + resources: [ + .process("Localization") ] ), .executableTarget( diff --git a/Plugins/ComponentsPlugin/plugin.swift b/Plugins/ComponentsPlugin/plugin.swift index 4d7f0c16..20b6f2cb 100644 --- a/Plugins/ComponentsPlugin/plugin.swift +++ b/Plugins/ComponentsPlugin/plugin.swift @@ -2,7 +2,7 @@ import PackagePlugin import Foundation @main -struct ConverterPlugin: CommandPlugin { +struct ComponentsPlugin: CommandPlugin { func performCommand(context: PluginContext, arguments: [String]) async throws { @@ -16,6 +16,8 @@ struct ConverterPlugin: CommandPlugin { let explanation = """ USAGE: deploy + + ARGUMENTS: """ print(explanation) diff --git a/README.md b/README.md index 21306df0..a481c4e4 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,14 @@ -# HTMLKit - -Render dynamic HTML templates in a typesafe and performant way! By using Swift's powerful language features and a pre-rendering algorithm, HTMLKit will render insanely fast templates but also catch bugs that otherwise might occur with other templating options. - -## Getting Started - -### Installation - -Add the packages as dependecies and targets to your package file. - -```swift -/// [Package.swift] - -... -dependencies: [ - ... - ///1. Add the packages - .package(name: "HTMLKit", url: "https://github.com/vapor-community/HTMLKit.git", from: "2.4.5"), - .package(name: "HTMLKitVaporProvider", url: "https://github.com/vapor-community/htmlkit-vapor-provider.git", from: "1.2.1") -], -targets: [ - .target( - ... - dependencies: [ - ... - /// 2. Add the products - .product(name: "HTMLKit", package: "HTMLKit"), - .product(name: "HTMLKitVaporProvider", package: "HTMLKitVaporProvider") - ] - ), - ... -``` - -Read the [installation instructions](/Instructions/Installation.md) for more information. - -### Definition - -Create a new file in your project. Add the import at the top of your file and declare a new structure. Extend your structure by adding a [layout definition](/Instructions/Essential/Layouts.md) to adopt the required properties and methods. Add your content to the `body` property. - -```swift -/// [SimplePage.swift] - -/// 1. Add the import -import HTMLKit - -/// 2. Define a new structure -struct SimplePage: Page { - - /// 3. Add some content - var body: AnyContent { - Document(type: .html5) - Html { - Head { - Title { - "SimplePage" - } - } - Body { - Paragraph { - "Hello World!" - } - } - } - } -} -``` - -### Implementation - -Call the structure you have created in your controller handler and use the render method to render the view for the request. - -```swift -/// [SimpleController.swift] - -... -final class SimpleController { - ... - func getPage(req: Request) throws -> EventLoopFuture { - /// 1. Call the structure - return SimplePage().render(for: req) - } - ... -} -``` - -## Features - -### Localization - -HTMLKit offers localization by the help of the [Lingo framework](https://github.com/miroslavkovac/Lingo.git). See the [instructions](/Instructions/Features/Localization.md) for more information. - -### Conversion - -You dont have to rewrite your whole codebase to use HTMLKit. HTMLKit offers a converter to translate HTML into HTMLKit. See the [instructions](/Instructions/Features/Conversion.md) for more information. - -### Validation - -HTMLKit is build with the intention to lead you writing valid code. So you don't need to think about it in particular. - -## Resources - -### Instructions - -See the [instructions](/Instructions/Overview.md) to learn more about the package and the features. - -### Components - -See the [package](https://github.com/vapor-community/HTMLKit-Components) to extend your experience with HTMLKit. The package is still work in progress, but -it will be available soon. - -### Live Example - -See the [package](https://github.com/mattesmohr/Website) of our contributor, how he uses HTMLKit in his website. +
+ avatar +

HTMLKit

+

Render dynamic HTML templates in a typesafe and performant way! By using Swift's powerful language features and a pre-rendering algorithm, HTMLKit will render insanely fast templates but also catch bugs that otherwise might occur with other templating options.

+ + documentation + + + versions + + + platforms + +
diff --git a/Sources/Commands/Components/DeployCommand.swift b/Sources/Commands/Components/DeployCommand.swift index 1493f64c..f2c9bb6a 100644 --- a/Sources/Commands/Components/DeployCommand.swift +++ b/Sources/Commands/Components/DeployCommand.swift @@ -24,7 +24,8 @@ internal struct DeployCommand { .appendingPathComponent("Resources", isDirectory: true) let distributionFile = URL(fileURLWithPath: targetPath) - .appendingPathComponent("Public") + .appendingPathComponent("Public", isDirectory: true) + .appendingPathComponent("HtmlKit", isDirectory: true) .appendingPathComponent("css") .appendingPathComponent("all") .appendingPathExtension("css") diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift b/Sources/HTMLKit/Framework/Environment/Localization/EnvironmentModifier.swift similarity index 93% rename from Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift rename to Sources/HTMLKit/Framework/Environment/Localization/EnvironmentModifier.swift index ae90f39d..eda3f1ca 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift +++ b/Sources/HTMLKit/Framework/Environment/Localization/EnvironmentModifier.swift @@ -6,7 +6,7 @@ /// The modifier is for /// /// -public struct EnvironmentModifier: AnyContent { +public struct EnvironmentModifier: GlobalElement { public let view: AnyContent diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift new file mode 100644 index 00000000..0c25113a --- /dev/null +++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift @@ -0,0 +1,91 @@ +/* + Abstract: + The file contains the extensions of some Vapor directives. + */ + +import HTMLKit +import Vapor + +extension Application.Views.Provider { + + /// Access to the view renderer + public static var htmlkit: Self { + return .init { + $0.views.use { + $0.htmlkit.renderer + } + } + } +} + +extension Application { + + /// Access to the vapor provider + public var htmlkit: HtmlKit { + return .init(application: self) + } + + /// The vapor provider + public struct HtmlKit { + + internal struct CacheStorageKey: StorageKey { + + public typealias Value = ViewCache + } + + /// The view cache + public var views: ViewCache { + + if let cache = self.application.storage[CacheStorageKey.self] { + return cache + } + + let cache = ViewCache() + + self.application.storage[CacheStorageKey.self] = cache + + return cache + } + + internal struct LingoStorageKey: StorageKey { + + public typealias Value = LingoConfiguration + } + + /// The view localization + public var lingo: LingoConfiguration { + + if let configuration = self.application.storage[LingoStorageKey.self] { + return configuration + } + + let configuration = LingoConfiguration() + + self.application.storage[LingoStorageKey.self] = configuration + + return configuration + } + + /// The view renderer + internal var renderer: ViewRenderer { + return .init(eventLoop: self.application.eventLoopGroup.next(), cache: self.views, lingo: lingo) + } + + /// The application dependency + public let application: Application + + /// Creates the provider + public init(application: Application) { + + self.application = application + } + } +} + +extension Request { + + /// Access to the view renderer + public var htmlkit: ViewRenderer { + return .init(eventLoop: self.eventLoop, cache: self.application.htmlkit.views, lingo: self.application.htmlkit.lingo) + } +} diff --git a/Sources/HTMLKitVapor/LingoConfiguration.swift b/Sources/HTMLKitVapor/LingoConfiguration.swift new file mode 100644 index 00000000..7b54a7f8 --- /dev/null +++ b/Sources/HTMLKitVapor/LingoConfiguration.swift @@ -0,0 +1,47 @@ +/* + Abstract: + The file contains the configuration for Lingo. + */ + +import Foundation + +/// The localization +public class LingoConfiguration { + + /// A enumeration of possible locale identifier + public enum Locale: String { + + case arabic = "ar" + case english = "en" + case french = "fr" + case german = "de" + case hindi = "es" + case bengali = "bn" + case russian = "ru" + case portuguese = "pt" + case indonesian = "id" + } + + /// The root path + internal var defaultDirectory: String + + /// The locale indentifier + internal var defaultLocale: String + + /// Creates a configuration + internal init() { + + self.defaultDirectory = "Resources/Localization" + self.defaultLocale = "en" + } + + /// Sets the root path + public func set(directory: URL) { + self.defaultDirectory = directory.path + } + + /// Sets the default locale indentifier + public func set(locale: Locale) { + self.defaultLocale = locale.rawValue + } +} diff --git a/Sources/HTMLKitVapor/ViewCache.swift b/Sources/HTMLKitVapor/ViewCache.swift new file mode 100644 index 00000000..b50a43d8 --- /dev/null +++ b/Sources/HTMLKitVapor/ViewCache.swift @@ -0,0 +1,50 @@ +/* + Abstract: + The file contains the cache of the Vapor renderer. + */ + +import HTMLKit +import Vapor + +/// The cache +public class ViewCache { + + /// The cache storage + internal var storage: [String: HTMLKit.Formula] + + /// Creates the cache + internal init() { + self.storage = [:] + } + + /// Retrieves a formula from the storage + internal func retrieve(name: String, on loop: EventLoop) -> EventLoopFuture { + + if let cache = self.storage[name] { + return loop.makeSucceededFuture(cache) + + } else { + return loop.makeSucceededFuture(nil) + } + } + + /// Sets or updates a formula at the storage + internal func upsert(name: String, formula: HTMLKit.Formula) { + self.storage.updateValue(formula, forKey: name) + } + + /// Removes a formula from the storage + internal func remove(name: String) { + self.storage.removeValue(forKey: name) + } + + /// Adds a view to the storage + public func add(view: T) { + + let formula = HTMLKit.Formula() + + try? view.prerender(formula) + + self.storage.updateValue(formula, forKey: String(reflecting: T.self)) + } +} diff --git a/Sources/HTMLKitVaporProvider/VaporRenderer.swift b/Sources/HTMLKitVapor/ViewRenderer.swift similarity index 50% rename from Sources/HTMLKitVaporProvider/VaporRenderer.swift rename to Sources/HTMLKitVapor/ViewRenderer.swift index df172ceb..2b5e7b30 100644 --- a/Sources/HTMLKitVaporProvider/VaporRenderer.swift +++ b/Sources/HTMLKitVapor/ViewRenderer.swift @@ -1,50 +1,58 @@ /* Abstract: - The file contains the Vapor renderer. + The file contains the Vapor view renderer. */ import HTMLKit import Vapor +import Lingo -public class VaporRenderer { +/// The view renderer +public class ViewRenderer { + /// A enumeration of possible errors of the view renderer public enum RendererError: Error { - case unkownTemplate(String) + case unkownLayout(String) public var description: String { switch self { - case .unkownTemplate(let name): - return "Template '\(name)' not found." + case .unkownLayout(let name): + return "Layout with the name '\(name)' could not be found." } } } - private var renderer: HTMLKit.Renderer { - return .init() - } + /// The event loop the view renderer is running on + internal var eventLoop: EventLoop - private var eventLoop: EventLoop + /// The cache of the view renderer + internal var cache: ViewCache - private var cache: VaporCache + /// The localiatzion of the view renderer + internal var lingo: Lingo? - public init(eventLoop: EventLoop, cache: VaporCache) { + /// Creates the view renderer + public init(eventLoop: EventLoop, cache: ViewCache, lingo: LingoConfiguration) { + self.eventLoop = eventLoop self.cache = cache + self.lingo = try? Lingo(rootPath: lingo.defaultDirectory, defaultLocale: lingo.defaultLocale) } + /// Renders a layout and its context public func render(name: String, context: Encodable) -> EventLoopFuture { return self.cache.retrieve(name: name, on: self.eventLoop).flatMap { formula in guard let formula = formula else { - return self.eventLoop.makeFailedFuture(RendererError.unkownTemplate(name)) + return self.eventLoop.makeFailedFuture(RendererError.unkownLayout(name)) } var buffer = ByteBufferAllocator().buffer(capacity: 4096) - - let manager = HTMLKit.ContextManager(context: context) + + let manager = HTMLKit.ContextManager(context: context, lingo: self.lingo) for ingredient in formula.ingredients { @@ -56,20 +64,11 @@ public class VaporRenderer { return self.eventLoop.makeSucceededFuture(buffer) } } - - public func add(layout: T) { - - let formula = HTMLKit.Formula() - - try? layout.prerender(formula) - - self.cache.upsert(name: String(describing: type(of: layout)), formula: formula) - } } -extension VaporRenderer: ViewRenderer { +extension ViewRenderer: Vapor.ViewRenderer { - public func `for`(_ request: Request) -> ViewRenderer { + public func `for`(_ request: Request) -> Vapor.ViewRenderer { return request.htmlkit } @@ -79,3 +78,17 @@ extension VaporRenderer: ViewRenderer { } } } + +extension ViewRenderer.RendererError: AbortError { + + @_implements(AbortError, reason) + public var abortReason: String { self.description } + + public var status: HTTPResponseStatus { .internalServerError } +} + +extension ViewRenderer.RendererError: DebuggableError { + + @_implements(DebuggableError, reason) + public var debuggableReason: String { self.description } +} diff --git a/Sources/HTMLKitVaporProvider/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVaporProvider/Extensions/Vapor+HTMLKit.swift deleted file mode 100644 index 4141db50..00000000 --- a/Sources/HTMLKitVaporProvider/Extensions/Vapor+HTMLKit.swift +++ /dev/null @@ -1,64 +0,0 @@ -/* - Abstract: - The file contains the extensions of some Vapor directives. - */ - -import HTMLKit -import Vapor - -extension Application.Views.Provider { - - public static var htmlkit: Self { - return .init { - $0.views.use { - $0.htmlkit.renderer - } - } - } -} - -extension Application { - - public var htmlkit: HtmlKit { - return .init(application: self) - } - - public struct HtmlKit { - - public let application: Application - - public var renderer: VaporRenderer { - return .init(eventLoop: self.application.eventLoopGroup.next(), cache: self.cache) - } - - public struct VariablesStorageKey: StorageKey { - public typealias Value = VaporCache - } - - public var cache: VaporCache { - - if let cache = self.application.storage[VariablesStorageKey.self] { - return cache - - } else { - - let cache = VaporCache() - - self.application.storage[VariablesStorageKey.self] = cache - - return cache - } - } - - public func add(layout: T) { - self.renderer.add(layout: layout) - } - } -} - -extension Request { - - public var htmlkit: VaporRenderer { - return .init(eventLoop: self.eventLoop, cache: self.application.htmlkit.cache) - } -} diff --git a/Sources/HTMLKitVaporProvider/VaporCache.swift b/Sources/HTMLKitVaporProvider/VaporCache.swift deleted file mode 100644 index 200fe92c..00000000 --- a/Sources/HTMLKitVaporProvider/VaporCache.swift +++ /dev/null @@ -1,38 +0,0 @@ -/* - Abstract: - The file contains the cache of the Vapor renderer. - */ - -import HTMLKit -import Vapor - -public class VaporCache { - - private var storage: [String: HTMLKit.Formula] - - public var count: Int { - return self.storage.keys.count - } - - public init() { - self.storage = [:] - } - - public func retrieve(name: String, on loop: EventLoop) -> EventLoopFuture { - - if let cache = self.storage[name] { - return loop.makeSucceededFuture(cache) - - } else { - return loop.makeSucceededFuture(nil) - } - } - - public func upsert(name: String, formula: HTMLKit.Formula) { - self.storage.updateValue(formula, forKey: name) - } - - public func remove(name: String) { - self.storage.removeValue(forKey: name) - } -} diff --git a/Tests/HTMLKitTests/Localization/fr.json b/Tests/HTMLKitTests/Localization/fr.json new file mode 100644 index 00000000..66141827 --- /dev/null +++ b/Tests/HTMLKitTests/Localization/fr.json @@ -0,0 +1,3 @@ +{ + "Hallo Welt": "Bonjour le monde", +} diff --git a/Tests/HTMLKitTests/LocalizationTests.swift b/Tests/HTMLKitTests/LocalizationTests.swift index 4fb61a6f..247698fc 100644 --- a/Tests/HTMLKitTests/LocalizationTests.swift +++ b/Tests/HTMLKitTests/LocalizationTests.swift @@ -35,6 +35,22 @@ final class LocalizationTests: XCTestCase { """ ) } + + func testEnvironmentLocale() throws { + + let page = TestPage { + Heading1("Hallo Welt") + .environment(locale: "fr") + } + + try renderer.add(layout: page) + + XCTAssertEqual(try renderer.render(layout: TestPage.self), + """ +

Bonjour le monde

+ """ + ) + } } extension LocalizationTests { diff --git a/Tests/HTMLKitVaporProviderTests/ProviderTests.swift b/Tests/HTMLKitVaporProviderTests/ProviderTests.swift deleted file mode 100644 index 68eac500..00000000 --- a/Tests/HTMLKitVaporProviderTests/ProviderTests.swift +++ /dev/null @@ -1,136 +0,0 @@ -/* - Abstract: - The file tests the provider. - */ - -import XCTVapor -import HTMLKit -import HTMLKitVaporProvider - -final class ProviderTests: XCTestCase { - - struct TestContext: Vapor.Content { - let greeting: String - } - - struct TestView: HTMLKit.View { - - @TemplateValue(TestContext.self) - var context - - var body: AnyContent { - Document(.html5) - Html { - Head { - Title { - "title" - } - } - Body { - Paragraph { - context.greeting - } - } - } - } - } - - func testEventLoopIntegrationWithViewRenderer() throws { - - let app = Application(.testing) - - defer { app.shutdown() } - - app.views.use(.htmlkit) - - app.htmlkit.add(layout: TestView()) - - app.get("test") { request -> EventLoopFuture in - return request.view.render("TestView", TestContext(greeting: "hello world")) - } - - try app.test(.GET, "test") { response in - XCTAssertEqual(response.status, .ok) - XCTAssertEqual(response.body.string, - """ - \ - \ - \ - title\ - \ - \ -

\ - hello world\ -

\ - \ - - """ - ) - } - } - - func testEventLoopIntegration() throws { - - let app = Application(.testing) - - defer { app.shutdown() } - - app.htmlkit.add(layout: TestView()) - - app.get("test") { request -> EventLoopFuture in - return request.htmlkit.render("TestView", TestContext(greeting: "hello world")) - } - - try app.test(.GET, "test") { response in - XCTAssertEqual(response.status, .ok) - XCTAssertEqual(response.body.string, - """ - \ - \ - \ - title\ - \ - \ -

\ - hello world\ -

\ - \ - - """ - ) - } - } - - @available(macOS 12, *) - func testConcurrencyIntegration() throws { - - let app = Application(.testing) - - defer { app.shutdown() } - - app.htmlkit.add(layout: TestView()) - - app.get("test") { request async throws -> Vapor.View in - return try await request.htmlkit.render("TestView", TestContext(greeting: "hello world")) - } - - try app.test(.GET, "test") { response in - XCTAssertEqual(response.status, .ok) - XCTAssertEqual(response.body.string, - """ - \ - \ - \ - title\ - \ - \ -

\ - hello world\ -

\ - \ - - """ - ) - } - } -} diff --git a/Tests/HTMLKitVaporTests/Localization/en.json b/Tests/HTMLKitVaporTests/Localization/en.json new file mode 100644 index 00000000..db488087 --- /dev/null +++ b/Tests/HTMLKitVaporTests/Localization/en.json @@ -0,0 +1,3 @@ +{ + "Hallo Welt": "Hello World", +} diff --git a/Tests/HTMLKitVaporTests/Localization/fr.json b/Tests/HTMLKitVaporTests/Localization/fr.json new file mode 100644 index 00000000..66141827 --- /dev/null +++ b/Tests/HTMLKitVaporTests/Localization/fr.json @@ -0,0 +1,3 @@ +{ + "Hallo Welt": "Bonjour le monde", +} diff --git a/Tests/HTMLKitVaporTests/ProviderTests.swift b/Tests/HTMLKitVaporTests/ProviderTests.swift new file mode 100644 index 00000000..c4028f69 --- /dev/null +++ b/Tests/HTMLKitVaporTests/ProviderTests.swift @@ -0,0 +1,220 @@ +/* + Abstract: + The file tests the provider. + */ + +import XCTVapor +import HTMLKit +import HTMLKitVapor + +final class ProviderTests: XCTestCase { + + struct TestContext: Vapor.Content { + let greeting: String + } + + enum Visitor { + + struct TestView: HTMLKit.View { + + var body: AnyContent { + Document(.html5) + Html { + Head { + Title { + "Visitor.TestView" + } + } + Body { + Paragraph("Hallo Welt") + } + } + } + } + } + + enum User { + + struct TestView: HTMLKit.View { + + var body: AnyContent { + Document(.html5) + Html { + Head { + Title { + "User.TestView" + } + } + Body { + Paragraph { + "Hello World" + } + } + } + } + } + } + + enum Admin { + + struct TestView: HTMLKit.View { + + @TemplateValue(TestContext.self) + var context + + var body: AnyContent { + Document(.html5) + Html { + Head { + Title { + "Admin.TestView" + } + } + Body { + Paragraph { + context.greeting + } + } + } + } + } + } + + func testEventLoopIntegrationWithViewRenderer() throws { + + let app = Application(.testing) + + defer { app.shutdown() } + + app.views.use(.htmlkit) + + app.htmlkit.views.add(view: User.TestView()) + app.htmlkit.views.add(view: Admin.TestView()) + + app.get("test") { request -> EventLoopFuture in + return request.view.render("HTMLKitVaporTests.ProviderTests.User.TestView") + } + + try app.test(.GET, "test") { response in + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.body.string, + """ + \ + \ + \ + User.TestView\ + \ + \ +

Hello World

\ + \ + + """ + ) + } + } + + func testEventLoopIntegration() throws { + + let app = Application(.testing) + + defer { app.shutdown() } + + app.htmlkit.views.add(view: User.TestView()) + app.htmlkit.views.add(view: Admin.TestView()) + + app.get("test") { request -> EventLoopFuture in + return request.htmlkit.render("HTMLKitVaporTests.ProviderTests.Admin.TestView", TestContext(greeting: "Hello World")) + } + + try app.test(.GET, "test") { response in + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.body.string, + """ + \ + \ + \ + Admin.TestView\ + \ + \ +

Hello World

\ + \ + + """ + ) + } + } + + @available(macOS 12, *) + func testConcurrencyIntegration() throws { + + let app = Application(.testing) + + defer { app.shutdown() } + + app.htmlkit.views.add(view: User.TestView()) + app.htmlkit.views.add(view: Admin.TestView()) + + app.get("test") { request async throws -> Vapor.View in + return try await request.htmlkit.render("HTMLKitVaporTests.ProviderTests.User.TestView") + } + + try app.test(.GET, "test") { response in + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.body.string, + """ + \ + \ + \ + User.TestView\ + \ + \ +

Hello World

\ + \ + + """ + ) + } + } + + @available(macOS 12, *) + func testConcurrencyIntegrationWithLocalization() throws { + + let currentFile = URL(fileURLWithPath: #file).deletingLastPathComponent() + + let currentDirectory = currentFile.appendingPathComponent("Localization") + + let app = Application(.testing) + + defer { app.shutdown() } + + app.views.use(.htmlkit) + + app.htmlkit.views.add(view: Visitor.TestView()) + app.htmlkit.views.add(view: User.TestView()) + app.htmlkit.views.add(view: Admin.TestView()) + + app.htmlkit.lingo.set(directory: currentDirectory) + app.htmlkit.lingo.set(locale: .french) + + app.get("test") { request async throws -> Vapor.View in + return try await request.view.render("HTMLKitVaporTests.ProviderTests.Visitor.TestView") + } + + try app.test(.GET, "test") { response in + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.body.string, + """ + \ + \ + \ + Visitor.TestView\ + \ + \ +

Bonjour le monde

\ + \ + + """ + ) + } + } +}