Skip to content

Commit

Permalink
Fix conditional statement (#109)
Browse files Browse the repository at this point in the history
* Add tests to identify the bug

* Fix the rendering bug

The else branch got pre-rendered, despite the condition.
  • Loading branch information
mattesmohr authored Nov 27, 2022
1 parent 160d18e commit 9811d5e
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,16 @@
/// The statement is for
public struct IF: GlobalElement {

public enum IFPrerenderErrors: Error {

case dynamiclyEvaluatedCondition
}

internal class Condition {

internal let condition: Conditionable

internal var formula: Formula

internal var view: AnyContent
internal var content: AnyContent

internal init(condition: Conditionable) {

self.condition = condition
self.view = ""
self.formula = Formula()
self.content = ""
}
}

Expand All @@ -39,39 +31,39 @@ public struct IF: GlobalElement {
public init(_ condition: Conditionable, @ContentBuilder<AnyContent> content: () -> AnyContent) {

let condition = Condition(condition: condition)
condition.view = content()
condition.content = content()

self.conditions = [condition]
}

public func elseIf(_ condition: Conditionable, @ContentBuilder<AnyContent> content: () -> AnyContent) -> IF {

let condition = Condition(condition: condition)
condition.view = content()
condition.content = content()

return .init(conditions: conditions + [condition])
}

public func elseIf<T>(isNil path: TemplateValue<T?>, @ContentBuilder<AnyContent> content: () -> AnyContent) -> IF {

let condition = Condition(condition: IsNullCondition<T>(lhs: path))
condition.view = content()
condition.content = content()

return .init(conditions: conditions + [condition])
}

public func elseIf<T>(isNotNil path: TemplateValue<T?>, @ContentBuilder<AnyContent> content: () -> AnyContent) -> IF {

let condition = Condition(condition: NotNullCondition<T>(lhs: path))
condition.view = content()
condition.content = content()

return .init(conditions: conditions + [condition])
}

public func `else`(@ContentBuilder<AnyContent> content: () -> AnyContent) -> IF {

let condition = Condition(condition: AlwaysTrueCondition())
condition.view = content()
condition.content = content()

return .init(conditions: conditions + [condition])
}
Expand All @@ -84,7 +76,7 @@ extension IF: AnyContent {
IF(conditions: conditions.map { htmlCondition in

let scriptCondition = IF.Condition(condition: htmlCondition.condition)
scriptCondition.view = htmlCondition.view.scripts
scriptCondition.content = htmlCondition.content.scripts

return scriptCondition
})
Expand All @@ -95,31 +87,22 @@ extension IF: AnyContent {
var isStaticallyEvaluated = true

for condition in conditions {

condition.formula.calendar = formula.calendar

condition.formula.timeZone = formula.timeZone

do {

guard isStaticallyEvaluated else {
throw IFPrerenderErrors.dynamiclyEvaluatedCondition
break
}

let testContext = ContextManager<Void>(contexts: [:])

if try condition.condition.evaluate(with: testContext) {
if try condition.condition.evaluate(with: ContextManager<Void>(contexts: [:])) {

try condition.view.prerender(formula)
try condition.content.prerender(formula)

return // Returning as the first true condition should be the only one that is rendered
break
}

} catch {

isStaticallyEvaluated = false

try condition.prerender(formula)
}
}

Expand All @@ -144,11 +127,11 @@ extension IF: AnyContent {
extension IF.Condition: AnyContent {

public func prerender(_ formula: Formula) throws {
try view.prerender(formula)
try content.prerender(formula)
}

public func render<T>(with manager: ContextManager<T>) throws -> String {
return try formula.render(with: manager)
return try content.render(with: manager)
}
}

Expand Down
64 changes: 62 additions & 2 deletions Tests/HTMLKitTests/StatementsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class StatementsTests: XCTestCase {

var renderer = Renderer()

func testIfStatement() throws {
func testIfStatementWithConstantValue() throws {

let isValid: TemplateValue<Bool> = .constant(true)

Expand All @@ -40,7 +40,37 @@ final class StatementsTests: XCTestCase {
)
}

func testElseStatement() throws {
func testIfStatementWithVariableValue() throws {

struct TestContext {
var isValid: Bool
}

@TemplateValue(TestContext.self)
var item

let page = TestPage {
IF(item.isValid) {
Paragraph {
"true"
}
}.else {
Paragraph {
"false"
}
}
}

try renderer.add(layout: page)

XCTAssertEqual(try renderer.render(layout: TestPage.self, with: TestContext(isValid: true)),
"""
<p>true</p>
"""
)
}

func testElseStatementWithConstantValue() throws {

let isValid: TemplateValue<Bool> = .constant(false)

Expand All @@ -65,6 +95,36 @@ final class StatementsTests: XCTestCase {
)
}

func testElseStatementWithVariableValue() throws {

struct TestContext {
var isValid: Bool
}

@TemplateValue(TestContext.self)
var item

let page = TestPage {
IF(item.isValid) {
Paragraph {
"true"
}
}.else {
Paragraph {
"false"
}
}
}

try renderer.add(layout: page)

XCTAssertEqual(try renderer.render(layout: TestPage.self, with: TestContext(isValid: false)),
"""
<p>false</p>
"""
)
}

func testElseIfStatement() throws {

let age: TemplateValue<Int> = .constant(16)
Expand Down

0 comments on commit 9811d5e

Please sign in to comment.