//===----------------------------------------------------------------------===// // // This source file is part of the Hummingbird server framework project // // Copyright (c) 2021-2021 the Hummingbird authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information // See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors // // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// import Mustache import XCTest final class TemplateRendererTests: XCTestCase { func testText() throws { let template = try MustacheTemplate(string: "test text") XCTAssertEqual(template.render("test"), "test text") } func testStringVariable() throws { let template = try MustacheTemplate(string: "test {{.}}") XCTAssertEqual(template.render("text"), "test text") } func testIntegerVariable() throws { let template = try MustacheTemplate(string: "test {{.}}") XCTAssertEqual(template.render(101), "test 101") } func testDictionary() throws { let template = try MustacheTemplate(string: "test {{value}} {{bool}}") XCTAssertEqual(template.render(["value": "test2", "bool": true]), "test test2 true") } func testArraySection() throws { let template = try MustacheTemplate(string: "test {{#value}}*{{.}}{{/value}}") XCTAssertEqual(template.render(["value": ["test2", "bool"]]), "test *test2*bool") XCTAssertEqual(template.render(["value": ["test2"]]), "test *test2") XCTAssertEqual(template.render(["value": []]), "test ") } func testBooleanSection() throws { let template = try MustacheTemplate(string: "test {{#.}}Yep{{/.}}") XCTAssertEqual(template.render(true), "test Yep") XCTAssertEqual(template.render(false), "test ") } func testIntegerSection() throws { let template = try MustacheTemplate(string: "test {{#.}}{{.}}{{/.}}") XCTAssertEqual(template.render(23), "test 23") } func testStringSection() throws { let template = try MustacheTemplate(string: "test {{#.}}{{.}}{{/.}}") XCTAssertEqual(template.render("Hello"), "test Hello") } func testInvertedSection() throws { let template = try MustacheTemplate(string: "test {{^.}}Inverted{{/.}}") XCTAssertEqual(template.render(true), "test ") XCTAssertEqual(template.render(false), "test Inverted") } func testMirror() throws { struct Test { let string: String } let template = try MustacheTemplate(string: "test {{string}}") XCTAssertEqual(template.render(Test(string: "string")), "test string") } func testOptionalMirror() throws { struct Test { let string: String? } let template = try MustacheTemplate(string: "test {{string}}") XCTAssertEqual(template.render(Test(string: "string")), "test string") XCTAssertEqual(template.render(Test(string: nil)), "test ") } func testOptionalSection() throws { struct Test { let string: String? } let template = try MustacheTemplate(string: "test {{#string}}*{{.}}{{/string}}") XCTAssertEqual(template.render(Test(string: "string")), "test *string") XCTAssertEqual(template.render(Test(string: nil)), "test ") let template2 = try MustacheTemplate(string: "test {{^string}}*{{/string}}") XCTAssertEqual(template2.render(Test(string: "string")), "test ") XCTAssertEqual(template2.render(Test(string: nil)), "test *") } func testOptionalSequence() throws { struct Test { let string: String? } let template = try MustacheTemplate(string: "test {{#.}}{{string}}{{/.}}") XCTAssertEqual(template.render([Test(string: "string")]), "test string") } func testOptionalSequenceSection() throws { struct Test { let string: String? } let template = try MustacheTemplate(string: "test {{#.}}{{#string}}*{{.}}{{/string}}{{/.}}") XCTAssertEqual(template.render([Test(string: "string")]), "test *string") } func testStructureInStructure() throws { struct SubTest { let string: String? } struct Test { let test: SubTest } let template = try MustacheTemplate(string: "test {{test.string}}") XCTAssertEqual(template.render(Test(test: .init(string: "sub"))), "test sub") } func testTextEscaping() throws { let template1 = try MustacheTemplate(string: "{{% CONTENT_TYPE:TEXT}}{{.}}") XCTAssertEqual(template1.render("<>"), "<>") let template2 = try MustacheTemplate(string: "{{% CONTENT_TYPE:HTML}}{{.}}") XCTAssertEqual(template2.render("<>"), "<>") } func testStopClimbingStack() throws { let template1 = try MustacheTemplate(string: "{{#test}}{{name}}{{/test}}") let template2 = try MustacheTemplate(string: "{{#test}}{{.name}}{{/test}}") let object: [String: Any] = ["test": [:], "name": "John"] let object2: [String: Any] = ["test": ["name": "Jane"], "name": "John"] XCTAssertEqual(template1.render(object), "John") XCTAssertEqual(template2.render(object), "") XCTAssertEqual(template2.render(object2), "Jane") } /// variables func testMustacheManualVariables() throws { let template = try MustacheTemplate(string: """ Hello {{name}} You have just won {{value}} dollars! {{#in_ca}} Well, {{taxed_value}} dollars, after taxes. {{/in_ca}} """) let object: [String: Any] = ["name": "Chris", "value": 10000, "taxed_value": 10000 - (10000 * 0.4), "in_ca": true] XCTAssertEqual(template.render(object), """ Hello Chris You have just won 10000 dollars! Well, 6000.0 dollars, after taxes. """) } /// test escaped and unescaped text func testMustacheManualEscapedText() throws { let template = try MustacheTemplate(string: """ *{{name}} *{{age}} *{{company}} *{{{company}}} """) let object: [String: Any] = ["name": "Chris", "company": "GitHub"] XCTAssertEqual(template.render(object), """ *Chris * *<b>GitHub</b> *GitHub """) } /// test dotted names func test_MustacheManualDottedNames() throws { let template = try MustacheTemplate(string: """ * {{client.name}} * {{age}} * {{client.company.name}} * {{{company.name}}} """) let object: [String: Any] = [ "client": ( name: "Chris & Friends", age: 50 ), "company": [ "name": "GitHub", ], ] XCTAssertEqual(template.render(object), """ * Chris & Friends * * * GitHub """) } /// test implicit operator func testMustacheManualImplicitOperator() throws { let template = try MustacheTemplate(string: """ * {{.}} """) let object = "Hello!" XCTAssertEqual(template.render(object), """ * Hello! """) } /// test lambda func test_MustacheManualLambda() throws { let template = try MustacheTemplate(string: """ * {{time.hour}} * {{today}} """) let object: [String: Any] = [ "year": 1970, "month": 1, "day": 1, "time": MustacheLambda { _ in return ( hour: 0, minute: 0, second: 0 ) }, "today": MustacheLambda { _ in return "{{year}}-{{month}}-{{day}}" }, ] XCTAssertEqual(template.render(object), """ * 0 * 1970-1-1 """) } /// test boolean func testMustacheManualSectionFalse() throws { let template = try MustacheTemplate(string: """ Shown. {{#person}} Never shown! {{/person}} """) let object: [String: Any] = ["person": false] XCTAssertEqual(template.render(object), """ Shown. """) } /// test non-empty lists func testMustacheManualSectionList() throws { let template = try MustacheTemplate(string: """ {{#repo}} {{name}} {{/repo}} """) let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] XCTAssertEqual(template.render(object), """ resque hub rip """) } /// test non-empty lists func testMustacheManualSectionList2() throws { let template = try MustacheTemplate(string: """ {{#repo}} {{.}} {{/repo}} """) let object: [String: Any] = ["repo": ["resque", "hub", "rip"]] XCTAssertEqual(template.render(object), """ resque hub rip """) } /// test lambdas func testMustacheManualSectionLambda() throws { let template = try MustacheTemplate(string: """ {{#wrapped}}{{name}} is awesome.{{/wrapped}} """) func wrapped(_ s: String) -> Any? { return "\(s)" } let object: [String: Any] = ["name": "Willy", "wrapped": MustacheLambda(wrapped)] XCTAssertEqual(template.render(object), """ Willy is awesome. """) } /// test setting context object func testMustacheManualContextObject() throws { let template = try MustacheTemplate(string: """ {{#person?}} Hi {{name}}! {{/person?}} """) let object: [String: Any] = ["person?": ["name": "Jon"]] XCTAssertEqual(template.render(object), """ Hi Jon! """) } /// test inverted sections func testMustacheManualInvertedSection() throws { let template = try MustacheTemplate(string: """ {{#repo}} {{name}} {{/repo}} {{^repo}} No repos :( {{/repo}} """) let object: [String: Any] = ["repo": []] XCTAssertEqual(template.render(object), """ No repos :( """) } /// test comments func testMustacheManualComment() throws { let template = try MustacheTemplate(string: """

Today{{! ignore me }}.

""") let object: [String: Any] = ["repo": []] XCTAssertEqual(template.render(object), """

Today.

""") } /// test dynamic names func testMustacheManualDynamicNames() throws { var library = MustacheLibrary() try library.register( "Hello {{>*dynamic}}", named: "main" ) try library.register( "everyone!", named: "world" ) let object = ["dynamic": "world"] XCTAssertEqual(library.render(object, withTemplate: "main"), "Hello everyone!") } /// test block with defaults func testMustacheManualBlocksWithDefaults() throws { let template = try MustacheTemplate(string: """

{{$title}}The News of Today{{/title}}

{{$body}}

Nothing special happened.

{{/body}} """) XCTAssertEqual(template.render([]), """

The News of Today

Nothing special happened.

""") } func testMustacheManualParents() throws { var library = MustacheLibrary() try library.register( """ {{{{.}}

{{/headlines}} {{/body}} {{/article}} {{{{$title}}The News of Today{{/title}} {{$body}}

Nothing special happened.

{{/body}} """, named: "article" ) let object = [ "headlines": [ "A pug's handler grew mustaches.", "What an exciting day!", ], ] XCTAssertEqual( library.render(object, withTemplate: "main"), """

The News of Today

A pug's handler grew mustaches.

What an exciting day!

Yesterday

Nothing special happened.

""" ) } func testMustacheManualDynamicNameParents() throws { var library = MustacheLibrary() try library.register( """ {{<*dynamic}} {{$text}}Hello World!{{/text}} {{/*dynamic}} """, named: "dynamic" ) try library.register( """ {{$text}}Here goes nothing.{{/text}} """, named: "normal" ) try library.register( """ {{$text}}Here also goes nothing but it's bold.{{/text}} """, named: "bold" ) let object = ["dynamic": "bold"] XCTAssertEqual( library.render(object, withTemplate: "dynamic"), """ Hello World! """ ) } /// test MustacheCustomRenderable func testCustomRenderable() throws { let template = try MustacheTemplate(string: "{{.}}") let template1 = try MustacheTemplate(string: "{{#.}}not null{{/.}}") let template2 = try MustacheTemplate(string: "{{^.}}null{{/.}}") struct Object: MustacheCustomRenderable { let value: String var renderText: String { self.value.uppercased() } var isNull: Bool { self.value == "null" } } let testObject = Object(value: "test") let nullObject = Object(value: "null") XCTAssertEqual(template.render(testObject), "TEST") XCTAssertEqual(template1.render(testObject), "not null") XCTAssertEqual(template1.render(nullObject), "") XCTAssertEqual(template2.render(testObject), "") XCTAssertEqual(template2.render(nullObject), "null") } func testPerformance() throws { let template = try MustacheTemplate(string: """ {{#repo}} {{name}} {{/repo}} """) let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] let date = Date() for _ in 1...10000 { _ = template.render(object) } print(-date.timeIntervalSinceNow) } }