From 8f253e7d20bb5483466de9a505e1ad9f7cb85867 Mon Sep 17 00:00:00 2001 From: Tony Arnold Date: Sat, 8 Jul 2023 19:40:33 +1000 Subject: [PATCH] Result builder fixes (#137) * Link elements are valid in HTML body if they use an itemprop attribute that is body-ok https://html.spec.whatwg.org/multipage/links.html#body-ok * Update ContentBuilder DSL to support partial blocks * Add new statement tests * Restore the comments * Remove unused methods from the result builder --------- Co-authored-by: Mattes Mohr --- .../Abstraction/Elements/HeadElements.swift | 2 +- .../Framework/Builders/ContentBuilder.swift | 88 +++++++++++-------- Tests/HTMLKitTests/StatementTests.swift | 67 ++++++++++++++ 3 files changed, 121 insertions(+), 36 deletions(-) diff --git a/Sources/HTMLKit/Abstraction/Elements/HeadElements.swift b/Sources/HTMLKit/Abstraction/Elements/HeadElements.swift index aa36b751..ccddc50d 100644 --- a/Sources/HTMLKit/Abstraction/Elements/HeadElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/HeadElements.swift @@ -711,7 +711,7 @@ extension Style: GlobalAttributes, GlobalEventAttributes, TypeAttribute, MediaAt /// ```html /// /// ``` -public struct Link: EmptyNode, HeadElement { +public struct Link: EmptyNode, HeadElement, BodyElement { internal var name: String { "link" } diff --git a/Sources/HTMLKit/Framework/Builders/ContentBuilder.swift b/Sources/HTMLKit/Framework/Builders/ContentBuilder.swift index 31f00d59..568b10a4 100644 --- a/Sources/HTMLKit/Framework/Builders/ContentBuilder.swift +++ b/Sources/HTMLKit/Framework/Builders/ContentBuilder.swift @@ -1,18 +1,20 @@ -/* - Abstract: - The file contains the builder to build up the result from a sequence of elements. - */ +// Abstract: +// The file contains the builder to build up the result from a sequence of elements. /// The builder builds up a result value from a sequence of elements. -@resultBuilder public class ContentBuilder { +@resultBuilder public enum ContentBuilder { + public typealias Component = [T] + + public typealias Expression = T + /// Builds an empty block /// /// ```swift /// Tag { /// } /// ``` - public static func buildBlock() -> [T] { + public static func buildBlock() -> Component { return [] } @@ -24,8 +26,8 @@ /// } /// } /// ``` - public static func buildBlock(_ component: T) -> [T] { - return [component] + public static func buildPartialBlock(first: Component) -> Component { + return first } /// Builds a block with more than one element. @@ -38,10 +40,40 @@ /// } /// } /// ``` - public static func buildBlock(_ components: T...) -> [T] { - return components.compactMap { $0 } + public static func buildPartialBlock(accumulated: Component, next: Component) -> Component { + return accumulated + next + } + + /// Builds a block + /// + /// ```swift + /// let content = "Content" + /// + /// Tag { + /// content + /// } + /// ``` + public static func buildExpression(_ element: Expression?) -> Component { + + guard let element else { + return [] + } + return [element] } + /// Builds a block + /// + /// ```swift + /// let content: [Type] + /// + /// Tag { + /// content + /// } + /// ``` + public static func buildExpression(_ element: Component) -> Component { + return element + } + /// Builds a block with one element. /// /// ```swift @@ -53,13 +85,12 @@ /// } /// } /// ``` - public static func buildOptional(_ component: T?) -> [T] { - - if let component = component { - return [component] + public static func buildOptional(_ component: Component?) -> Component { + + guard let component else { + return [] } - - return [] + return component } /// Builds a block, if the condition is true. @@ -72,8 +103,8 @@ /// } /// } /// ``` - public static func buildEither(first component: T) -> [T] { - return [component] + public static func buildEither(first: Component) -> Component { + return first } /// Builds a block, if the condition is false. @@ -90,10 +121,10 @@ /// } /// } /// ``` - public static func buildEither(second component: T) -> [T] { - return [component] + public static func buildEither(second: Component) -> Component { + return second } - + /// Builds blocks by running through a sequence of elements. /// /// ```swift @@ -105,20 +136,7 @@ /// } /// } /// ``` - public static func buildArray(_ components: [[T]]) -> [T] { + public static func buildArray(_ components: [Component]) -> Component { return components.flatMap { $0 } } - - /// Builds a block with embeded content. - /// - /// ```swift - /// Tag { - /// content - /// Tag { - /// } - /// } - /// ``` - public static func buildBlock(_ content: [T], _ components: T...) -> [T] { - return content + components.compactMap { $0 } - } } diff --git a/Tests/HTMLKitTests/StatementTests.swift b/Tests/HTMLKitTests/StatementTests.swift index ceef4195..d50158d5 100644 --- a/Tests/HTMLKitTests/StatementTests.swift +++ b/Tests/HTMLKitTests/StatementTests.swift @@ -96,4 +96,71 @@ final class StatementTests: XCTestCase { """ ) } + + func testOptionalBeforeElement() throws { + + let name: String? = "Tony" + + let view = TestView { + if let name = name { + Paragraph { + name + } + } + Division { + } + } + + XCTAssertEqual(try renderer.render(view: view), + """ +

Tony

\ +
+ """ + ) + } + + func testOptionalAfterElement() throws { + + let name: String? = "Tony" + + let view = TestView { + Division { + } + if let name = name { + Paragraph { + name + } + } + } + + XCTAssertEqual(try renderer.render(view: view), + """ +
\ +

Tony

+ """ + ) + } + + func testOptionalWithExpectedResult() throws { + + let name: String? = "Tony" + + let view = TestView { + Body { + if let name = name { + Paragraph { + name + } + } + } + } + + XCTAssertEqual(try renderer.render(view: view), + """ + \ +

Tony

\ + + """ + ) + } }