Rename package to swift-mustache (#27)
* Rename package to swift-mustache * Update CI
This commit is contained in:
93
Tests/MustacheTests/ErrorTests.swift
Normal file
93
Tests/MustacheTests/ErrorTests.swift
Normal file
@@ -0,0 +1,93 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 ErrorTests: XCTestCase {
|
||||
func testSectionCloseNameIncorrect() {
|
||||
XCTAssertThrowsError(try MustacheTemplate(string: """
|
||||
{{#test}}
|
||||
{{.}}
|
||||
{{/test2}}
|
||||
""")) { error in
|
||||
switch error {
|
||||
case let error as MustacheTemplate.ParserError:
|
||||
XCTAssertEqual(error.error as? MustacheTemplate.Error, .sectionCloseNameIncorrect)
|
||||
XCTAssertEqual(error.context.line, "{{/test2}}")
|
||||
XCTAssertEqual(error.context.lineNumber, 3)
|
||||
XCTAssertEqual(error.context.columnNumber, 4)
|
||||
|
||||
default:
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testUnfinishedName() {
|
||||
XCTAssertThrowsError(try MustacheTemplate(string: """
|
||||
{{#test}}
|
||||
{{name}
|
||||
{{/test2}}
|
||||
""")) { error in
|
||||
switch error {
|
||||
case let error as MustacheTemplate.ParserError:
|
||||
XCTAssertEqual(error.error as? MustacheTemplate.Error, .unfinishedName)
|
||||
XCTAssertEqual(error.context.line, "{{name}")
|
||||
XCTAssertEqual(error.context.lineNumber, 2)
|
||||
XCTAssertEqual(error.context.columnNumber, 7)
|
||||
|
||||
default:
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testExpectedSectionEnd() {
|
||||
XCTAssertThrowsError(try MustacheTemplate(string: """
|
||||
{{#test}}
|
||||
{{.}}
|
||||
""")) { error in
|
||||
switch error {
|
||||
case let error as MustacheTemplate.ParserError:
|
||||
XCTAssertEqual(error.error as? MustacheTemplate.Error, .expectedSectionEnd)
|
||||
XCTAssertEqual(error.context.line, "{{.}}")
|
||||
XCTAssertEqual(error.context.lineNumber, 2)
|
||||
XCTAssertEqual(error.context.columnNumber, 6)
|
||||
|
||||
default:
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testInvalidSetDelimiter() {
|
||||
XCTAssertThrowsError(try MustacheTemplate(string: """
|
||||
{{=<% %>=}}
|
||||
<%.%>
|
||||
<%={{}}=%>
|
||||
""")) { error in
|
||||
switch error {
|
||||
case let error as MustacheTemplate.ParserError:
|
||||
XCTAssertEqual(error.error as? MustacheTemplate.Error, .invalidSetDelimiter)
|
||||
XCTAssertEqual(error.context.line, "<%={{}}=%>")
|
||||
XCTAssertEqual(error.context.lineNumber, 3)
|
||||
XCTAssertEqual(error.context.columnNumber, 4)
|
||||
|
||||
default:
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Tests/MustacheTests/LibraryTests.swift
Normal file
74
Tests/MustacheTests/LibraryTests.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import Mustache
|
||||
import XCTest
|
||||
|
||||
final class LibraryTests: XCTestCase {
|
||||
func testDirectoryLoad() async throws {
|
||||
let fs = FileManager()
|
||||
try? fs.createDirectory(atPath: "templates", withIntermediateDirectories: false)
|
||||
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates")) }
|
||||
let mustache = Data("<test>{{#value}}<value>{{.}}</value>{{/value}}</test>".utf8)
|
||||
try mustache.write(to: URL(fileURLWithPath: "templates/test.mustache"))
|
||||
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test.mustache")) }
|
||||
|
||||
let library = try await MustacheLibrary(directory: "./templates")
|
||||
let object = ["value": ["value1", "value2"]]
|
||||
XCTAssertEqual(library.render(object, withTemplate: "test"), "<test><value>value1</value><value>value2</value></test>")
|
||||
}
|
||||
|
||||
func testPartial() async throws {
|
||||
let fs = FileManager()
|
||||
try? fs.createDirectory(atPath: "templates", withIntermediateDirectories: false)
|
||||
let mustache = Data("<test>{{#value}}<value>{{.}}</value>{{/value}}</test>".utf8)
|
||||
try mustache.write(to: URL(fileURLWithPath: "templates/test-partial.mustache"))
|
||||
let mustache2 = Data("{{>test-partial}}".utf8)
|
||||
try mustache2.write(to: URL(fileURLWithPath: "templates/test.mustache"))
|
||||
defer {
|
||||
XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test-partial.mustache"))
|
||||
XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test.mustache"))
|
||||
XCTAssertNoThrow(try fs.removeItem(atPath: "templates"))
|
||||
}
|
||||
|
||||
let library = try await MustacheLibrary(directory: "./templates")
|
||||
let object = ["value": ["value1", "value2"]]
|
||||
XCTAssertEqual(library.render(object, withTemplate: "test"), "<test><value>value1</value><value>value2</value></test>")
|
||||
}
|
||||
|
||||
func testLibraryParserError() async throws {
|
||||
let fs = FileManager()
|
||||
try? fs.createDirectory(atPath: "templates", withIntermediateDirectories: false)
|
||||
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates")) }
|
||||
let mustache = Data("<test>{{#value}}<value>{{.}}</value>{{/value}}</test>".utf8)
|
||||
try mustache.write(to: URL(fileURLWithPath: "templates/test.mustache"))
|
||||
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test.mustache")) }
|
||||
let mustache2 = Data("""
|
||||
{{#test}}
|
||||
{{{name}}
|
||||
{{/test2}}
|
||||
""".utf8)
|
||||
try mustache2.write(to: URL(fileURLWithPath: "templates/error.mustache"))
|
||||
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates/error.mustache")) }
|
||||
|
||||
do {
|
||||
_ = try await MustacheLibrary(directory: "./templates")
|
||||
} catch let parserError as MustacheLibrary.ParserError {
|
||||
XCTAssertEqual(parserError.filename, "error.mustache")
|
||||
XCTAssertEqual(parserError.context.line, "{{{name}}")
|
||||
XCTAssertEqual(parserError.context.lineNumber, 2)
|
||||
XCTAssertEqual(parserError.context.columnNumber, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
147
Tests/MustacheTests/PartialTests.swift
Normal file
147
Tests/MustacheTests/PartialTests.swift
Normal file
@@ -0,0 +1,147 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import Mustache
|
||||
import XCTest
|
||||
|
||||
final class PartialTests: XCTestCase {
|
||||
/// Testing partials
|
||||
func testMustacheManualExample9() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
<h2>Names</h2>
|
||||
{{#names}}
|
||||
{{> user}}
|
||||
{{/names}}
|
||||
""")
|
||||
let template2 = try MustacheTemplate(string: """
|
||||
<strong>{{.}}</strong>
|
||||
|
||||
""")
|
||||
let library = MustacheLibrary(templates: ["base": template, "user": template2])
|
||||
|
||||
let object: [String: Any] = ["names": ["john", "adam", "claire"]]
|
||||
XCTAssertEqual(library.render(object, withTemplate: "base"), """
|
||||
<h2>Names</h2>
|
||||
<strong>john</strong>
|
||||
<strong>adam</strong>
|
||||
<strong>claire</strong>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
/// Test where last line of partial generates no content. It should not add a
|
||||
/// tab either
|
||||
func testPartialEmptyLineTabbing() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
<h2>Names</h2>
|
||||
{{#names}}
|
||||
{{> user}}
|
||||
{{/names}}
|
||||
Text after
|
||||
|
||||
""")
|
||||
let template2 = try MustacheTemplate(string: """
|
||||
{{^empty(.)}}
|
||||
<strong>{{.}}</strong>
|
||||
{{/empty(.)}}
|
||||
{{#empty(.)}}
|
||||
<strong>empty</strong>
|
||||
{{/empty(.)}}
|
||||
|
||||
""")
|
||||
var library = MustacheLibrary()
|
||||
library.register(template, named: "base")
|
||||
library.register(template2, named: "user") // , withTemplate: String)// = MustacheLibrary(templates: ["base": template, "user": template2])
|
||||
|
||||
let object: [String: Any] = ["names": ["john", "adam", "claire"]]
|
||||
XCTAssertEqual(library.render(object, withTemplate: "base"), """
|
||||
<h2>Names</h2>
|
||||
<strong>john</strong>
|
||||
<strong>adam</strong>
|
||||
<strong>claire</strong>
|
||||
Text after
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
/// Testing dynamic partials
|
||||
func testDynamicPartials() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
<h2>Names</h2>
|
||||
{{partial}}
|
||||
""")
|
||||
let template2 = try MustacheTemplate(string: """
|
||||
{{#names}}
|
||||
<strong>{{.}}</strong>
|
||||
{{/names}}
|
||||
""")
|
||||
let library = MustacheLibrary(templates: ["base": template])
|
||||
|
||||
let object: [String: Any] = ["names": ["john", "adam", "claire"], "partial": template2]
|
||||
XCTAssertEqual(library.render(object, withTemplate: "base"), """
|
||||
<h2>Names</h2>
|
||||
<strong>john</strong>
|
||||
<strong>adam</strong>
|
||||
<strong>claire</strong>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
/// test inheritance
|
||||
func testInheritance() throws {
|
||||
var library = MustacheLibrary()
|
||||
try library.register(
|
||||
"""
|
||||
<head>
|
||||
<title>{{$title}}Default title{{/title}}</title>
|
||||
</head>
|
||||
|
||||
""",
|
||||
named: "header"
|
||||
)
|
||||
try library.register(
|
||||
"""
|
||||
<html>
|
||||
{{$header}}{{/header}}
|
||||
{{$content}}{{/content}}
|
||||
</html>
|
||||
|
||||
""",
|
||||
named: "base"
|
||||
)
|
||||
try library.register(
|
||||
"""
|
||||
{{<base}}
|
||||
{{$header}}
|
||||
{{<header}}
|
||||
{{$title}}My page title{{/title}}
|
||||
{{/header}}
|
||||
{{/header}}
|
||||
{{$content}}<h1>Hello world</h1>{{/content}}
|
||||
{{/base}}
|
||||
|
||||
""",
|
||||
named: "mypage"
|
||||
)
|
||||
XCTAssertEqual(library.render({}, withTemplate: "mypage")!, """
|
||||
<html>
|
||||
<head>
|
||||
<title>My page title</title>
|
||||
</head>
|
||||
<h1>Hello world</h1>
|
||||
</html>
|
||||
|
||||
""")
|
||||
}
|
||||
}
|
||||
144
Tests/MustacheTests/SpecTests.swift
Normal file
144
Tests/MustacheTests/SpecTests.swift
Normal file
@@ -0,0 +1,144 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 Foundation
|
||||
#if os(Linux)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
import Mustache
|
||||
import XCTest
|
||||
|
||||
public struct AnyDecodable: Decodable {
|
||||
public let value: Any
|
||||
|
||||
public init(_ value: some Any) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
public extension AnyDecodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
if container.decodeNil() {
|
||||
self.init(NSNull())
|
||||
} else if let bool = try? container.decode(Bool.self) {
|
||||
self.init(bool)
|
||||
} else if let int = try? container.decode(Int.self) {
|
||||
self.init(int)
|
||||
} else if let uint = try? container.decode(UInt.self) {
|
||||
self.init(uint)
|
||||
} else if let double = try? container.decode(Double.self) {
|
||||
self.init(double)
|
||||
} else if let string = try? container.decode(String.self) {
|
||||
self.init(string)
|
||||
} else if let array = try? container.decode([AnyDecodable].self) {
|
||||
self.init(array.map(\.value))
|
||||
} else if let dictionary = try? container.decode([String: AnyDecodable].self) {
|
||||
self.init(dictionary.mapValues { $0.value })
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyDecodable value cannot be decoded")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify implementation against formal standard for Mustache.
|
||||
/// https://github.com/mustache/spec
|
||||
final class MustacheSpecTests: XCTestCase {
|
||||
struct Spec: Decodable {
|
||||
struct Test: Decodable {
|
||||
let name: String
|
||||
let desc: String
|
||||
let data: AnyDecodable
|
||||
let partials: [String: String]?
|
||||
let template: String
|
||||
let expected: String
|
||||
|
||||
func run() throws {
|
||||
// print("Test: \(self.name)")
|
||||
if let partials = self.partials {
|
||||
let template = try MustacheTemplate(string: self.template)
|
||||
var templates: [String: MustacheTemplate] = ["__test__": template]
|
||||
for (key, value) in partials {
|
||||
let template = try MustacheTemplate(string: value)
|
||||
templates[key] = template
|
||||
}
|
||||
let library = MustacheLibrary(templates: templates)
|
||||
let result = library.render(self.data.value, withTemplate: "__test__")
|
||||
self.XCTAssertSpecEqual(result, self)
|
||||
} else {
|
||||
let template = try MustacheTemplate(string: self.template)
|
||||
let result = template.render(self.data.value)
|
||||
self.XCTAssertSpecEqual(result, self)
|
||||
}
|
||||
}
|
||||
|
||||
func XCTAssertSpecEqual(_ result: String?, _ test: Spec.Test) {
|
||||
if result != test.expected {
|
||||
XCTFail("\n\(test.desc)result:\n\(result ?? "nil")\nexpected:\n\(test.expected)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let overview: String
|
||||
let tests: [Test]
|
||||
}
|
||||
|
||||
func testSpec(name: String, ignoring: [String] = []) throws {
|
||||
let url = URL(string: "https://raw.githubusercontent.com/mustache/spec/master/specs/\(name).json")!
|
||||
try testSpec(url: url, ignoring: ignoring)
|
||||
}
|
||||
|
||||
func testSpec(url: URL, ignoring: [String] = []) throws {
|
||||
let data = try Data(contentsOf: url)
|
||||
let spec = try JSONDecoder().decode(Spec.self, from: data)
|
||||
|
||||
print(spec.overview)
|
||||
let date = Date()
|
||||
for test in spec.tests {
|
||||
guard !ignoring.contains(test.name) else { continue }
|
||||
XCTAssertNoThrow(try test.run())
|
||||
}
|
||||
print(-date.timeIntervalSinceNow)
|
||||
}
|
||||
|
||||
func testCommentsSpec() throws {
|
||||
try self.testSpec(name: "comments")
|
||||
}
|
||||
|
||||
func testDelimitersSpec() throws {
|
||||
try self.testSpec(name: "delimiters")
|
||||
}
|
||||
|
||||
func testInterpolationSpec() throws {
|
||||
try self.testSpec(name: "interpolation")
|
||||
}
|
||||
|
||||
func testInvertedSpec() throws {
|
||||
try self.testSpec(name: "inverted")
|
||||
}
|
||||
|
||||
func testPartialsSpec() throws {
|
||||
try self.testSpec(name: "partials")
|
||||
}
|
||||
|
||||
func testSectionsSpec() throws {
|
||||
try self.testSpec(name: "sections")
|
||||
}
|
||||
|
||||
func testInheritanceSpec() throws {
|
||||
try XCTSkipIf(true) // inheritance spec has been updated and has added requirements, we don't yet support
|
||||
try self.testSpec(name: "~inheritance")
|
||||
}
|
||||
}
|
||||
86
Tests/MustacheTests/TemplateParserTests.swift
Normal file
86
Tests/MustacheTests/TemplateParserTests.swift
Normal file
@@ -0,0 +1,86 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import Mustache
|
||||
import XCTest
|
||||
|
||||
final class TemplateParserTests: XCTestCase {
|
||||
func testText() throws {
|
||||
let template = try MustacheTemplate(string: "test template")
|
||||
XCTAssertEqual(template.tokens, [.text("test template")])
|
||||
}
|
||||
|
||||
func testVariable() throws {
|
||||
let template = try MustacheTemplate(string: "test {{variable}}")
|
||||
XCTAssertEqual(template.tokens, [.text("test "), .variable(name: "variable")])
|
||||
}
|
||||
|
||||
func testSection() throws {
|
||||
let template = try MustacheTemplate(string: "test {{#section}}text{{/section}}")
|
||||
XCTAssertEqual(template.tokens, [.text("test "), .section(name: "section", template: .init([.text("text")]))])
|
||||
}
|
||||
|
||||
func testInvertedSection() throws {
|
||||
let template = try MustacheTemplate(string: "test {{^section}}text{{/section}}")
|
||||
XCTAssertEqual(template.tokens, [.text("test "), .invertedSection(name: "section", template: .init([.text("text")]))])
|
||||
}
|
||||
|
||||
func testComment() throws {
|
||||
let template = try MustacheTemplate(string: "test {{!section}}")
|
||||
XCTAssertEqual(template.tokens, [.text("test ")])
|
||||
}
|
||||
|
||||
func testWhitespace() throws {
|
||||
let template = try MustacheTemplate(string: "{{ section }}")
|
||||
XCTAssertEqual(template.tokens, [.variable(name: "section")])
|
||||
}
|
||||
|
||||
func testContentType() throws {
|
||||
let template = try MustacheTemplate(string: "{{% CONTENT_TYPE:TEXT}}")
|
||||
let template1 = try MustacheTemplate(string: "{{% CONTENT_TYPE:TEXT }}")
|
||||
let template2 = try MustacheTemplate(string: "{{% CONTENT_TYPE: TEXT}}")
|
||||
let template3 = try MustacheTemplate(string: "{{%CONTENT_TYPE:TEXT}}")
|
||||
XCTAssertEqual(template.tokens, [.contentType(TextContentType())])
|
||||
XCTAssertEqual(template1.tokens, [.contentType(TextContentType())])
|
||||
XCTAssertEqual(template2.tokens, [.contentType(TextContentType())])
|
||||
XCTAssertEqual(template3.tokens, [.contentType(TextContentType())])
|
||||
}
|
||||
}
|
||||
|
||||
extension MustacheTemplate: Equatable {
|
||||
public static func == (lhs: MustacheTemplate, rhs: MustacheTemplate) -> Bool {
|
||||
lhs.tokens == rhs.tokens
|
||||
}
|
||||
}
|
||||
|
||||
extension MustacheTemplate.Token: Equatable {
|
||||
public static func == (lhs: MustacheTemplate.Token, rhs: MustacheTemplate.Token) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.text(let lhs), .text(let rhs)):
|
||||
return lhs == rhs
|
||||
case (.variable(let lhs, let lhs2), .variable(let rhs, let rhs2)):
|
||||
return lhs == rhs && lhs2 == rhs2
|
||||
case (.section(let lhs1, let lhs2, let lhs3), .section(let rhs1, let rhs2, let rhs3)):
|
||||
return lhs1 == rhs1 && lhs2 == rhs2 && lhs3 == rhs3
|
||||
case (.invertedSection(let lhs1, let lhs2, let lhs3), .invertedSection(let rhs1, let rhs2, let rhs3)):
|
||||
return lhs1 == rhs1 && lhs2 == rhs2 && lhs3 == rhs3
|
||||
case (.partial(let name1, let indent1, _), .partial(let name2, let indent2, _)):
|
||||
return name1 == name2 && indent1 == indent2
|
||||
case (.contentType(let contentType), .contentType(let contentType2)):
|
||||
return type(of: contentType) == type(of: contentType2)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
297
Tests/MustacheTests/TemplateRendererTests.swift
Normal file
297
Tests/MustacheTests/TemplateRendererTests.swift
Normal file
@@ -0,0 +1,297 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 testMustacheManualExample1() 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 esacped and unescaped text
|
||||
func testMustacheManualExample2() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
*{{name}}
|
||||
*{{age}}
|
||||
*{{company}}
|
||||
*{{{company}}}
|
||||
""")
|
||||
let object: [String: Any] = ["name": "Chris", "company": "<b>GitHub</b>"]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
*Chris
|
||||
*
|
||||
*<b>GitHub</b>
|
||||
*<b>GitHub</b>
|
||||
""")
|
||||
}
|
||||
|
||||
/// test boolean
|
||||
func testMustacheManualExample3() 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 testMustacheManualExample4() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#repo}}
|
||||
<b>{{name}}</b>
|
||||
{{/repo}}
|
||||
""")
|
||||
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>resque</b>
|
||||
<b>hub</b>
|
||||
<b>rip</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
/// test lambdas
|
||||
func testMustacheManualExample5() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#wrapped}}{{name}} is awesome.{{/wrapped}}
|
||||
""")
|
||||
func wrapped(object: Any, template: MustacheTemplate) -> String {
|
||||
return "<b>\(template.render(object))</b>"
|
||||
}
|
||||
let object: [String: Any] = ["name": "Willy", "wrapped": MustacheLambda(wrapped)]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>Willy is awesome.</b>
|
||||
""")
|
||||
}
|
||||
|
||||
/// test setting context object
|
||||
func testMustacheManualExample6() 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 testMustacheManualExample7() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#repo}}
|
||||
<b>{{name}}</b>
|
||||
{{/repo}}
|
||||
{{^repo}}
|
||||
No repos :(
|
||||
{{/repo}}
|
||||
""")
|
||||
let object: [String: Any] = ["repo": []]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
No repos :(
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
/// test comments
|
||||
func testMustacheManualExample8() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
<h1>Today{{! ignore me }}.</h1>
|
||||
""")
|
||||
let object: [String: Any] = ["repo": []]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<h1>Today.</h1>
|
||||
""")
|
||||
}
|
||||
|
||||
/// 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}}
|
||||
<b>{{name}}</b>
|
||||
{{/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)
|
||||
}
|
||||
}
|
||||
176
Tests/MustacheTests/TransformTests.swift
Normal file
176
Tests/MustacheTests/TransformTests.swift
Normal file
@@ -0,0 +1,176 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 TransformTests: XCTestCase {
|
||||
func testLowercased() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{ lowercased(name) }}
|
||||
""")
|
||||
let object: [String: Any] = ["name": "Test"]
|
||||
XCTAssertEqual(template.render(object), "test")
|
||||
}
|
||||
|
||||
func testUppercased() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{ uppercased(name) }}
|
||||
""")
|
||||
let object: [String: Any] = ["name": "Test"]
|
||||
XCTAssertEqual(template.render(object), "TEST")
|
||||
}
|
||||
|
||||
func testNewline() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#repo}}
|
||||
<b>{{name}}</b>
|
||||
{{/repo}}
|
||||
|
||||
""")
|
||||
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>resque</b>
|
||||
<b>hub</b>
|
||||
<b>rip</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testFirstLast() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#repo}}
|
||||
<b>{{#first()}}first: {{/first()}}{{#last()}}last: {{/last()}}{{ name }}</b>
|
||||
{{/repo}}
|
||||
|
||||
""")
|
||||
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>first: resque</b>
|
||||
<b>hub</b>
|
||||
<b>last: rip</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testIndex() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#repo}}
|
||||
<b>{{#index()}}{{plusone(.)}}{{/index()}}) {{ name }}</b>
|
||||
{{/repo}}
|
||||
|
||||
""")
|
||||
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>1) resque</b>
|
||||
<b>2) hub</b>
|
||||
<b>3) rip</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testEvenOdd() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#repo}}
|
||||
<b>{{index()}}) {{#even()}}even {{/even()}}{{#odd()}}odd {{/odd()}}{{ name }}</b>
|
||||
{{/repo}}
|
||||
|
||||
""")
|
||||
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>0) even resque</b>
|
||||
<b>1) odd hub</b>
|
||||
<b>2) even rip</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testReversed() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#reversed(repo)}}
|
||||
<b>{{ name }}</b>
|
||||
{{/reversed(repo)}}
|
||||
|
||||
""")
|
||||
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>rip</b>
|
||||
<b>hub</b>
|
||||
<b>resque</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testArrayIndex() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#repo}}
|
||||
<b>{{ index() }}) {{ name }}</b>
|
||||
{{/repo}}
|
||||
""")
|
||||
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>0) resque</b>
|
||||
<b>1) hub</b>
|
||||
<b>2) rip</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testArraySorted() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#sorted(repo)}}
|
||||
<b>{{ index() }}) {{ . }}</b>
|
||||
{{/sorted(repo)}}
|
||||
""")
|
||||
let object: [String: Any] = ["repo": ["resque", "hub", "rip"]]
|
||||
XCTAssertEqual(template.render(object), """
|
||||
<b>0) hub</b>
|
||||
<b>1) resque</b>
|
||||
<b>2) rip</b>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testDictionaryEmpty() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#empty(array)}}Array{{/empty(array)}}{{#empty(dictionary)}}Dictionary{{/empty(dictionary)}}
|
||||
""")
|
||||
let object: [String: Any] = ["array": [], "dictionary": [:]]
|
||||
XCTAssertEqual(template.render(object), "ArrayDictionary")
|
||||
}
|
||||
|
||||
func testListOutput() throws {
|
||||
let object = [1, 2, 3, 4]
|
||||
let template = try MustacheTemplate(string: "{{#.}}{{.}}{{^last()}}, {{/last()}}{{/.}}")
|
||||
XCTAssertEqual(template.render(object), "1, 2, 3, 4")
|
||||
}
|
||||
|
||||
func testDictionaryEnumerated() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#enumerated(.)}}<b>{{ key }} = {{ value }}</b>{{/enumerated(.)}}
|
||||
""")
|
||||
let object: [String: Any] = ["one": 1, "two": 2]
|
||||
let result = template.render(object)
|
||||
XCTAssertTrue(result == "<b>one = 1</b><b>two = 2</b>" || result == "<b>two = 2</b><b>one = 1</b>")
|
||||
}
|
||||
|
||||
func testDictionarySortedByKey() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
{{#sorted(.)}}<b>{{ key }} = {{ value }}</b>{{/sorted(.)}}
|
||||
""")
|
||||
let object: [String: Any] = ["one": 1, "two": 2, "three": 3]
|
||||
let result = template.render(object)
|
||||
XCTAssertEqual(result, "<b>one = 1</b><b>three = 3</b><b>two = 2</b>")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user