Update to Spectre 0.9.0 (#247)

* update to Spectre 0.9.0

* fix variable spec tests

* fix flatMap warning

* updated CHANGELOG
This commit is contained in:
Ilya Puchka
2018-09-23 03:46:27 +03:00
committed by GitHub
parent d238c25eef
commit f7bda226e8
26 changed files with 2386 additions and 2214 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
.conche/
.build/
Packages/
Package.resolved
Package.pins
*.xcodeproj

View File

@@ -25,7 +25,6 @@
- Now you can conditionally render variables with `{{ variable if condition }}`, which is a shorthand for `{% if condition %}{{ variable }}{% endif %}`. You can also use `else` like `{{ variable1 if condition else variable2 }}`, which is a shorthand for `{% if condition %}{{ variable1 }}{% else %}{{ variable2 }}{% endif %}`
[Ilya Puchka](https://github.com/ilyapuchka)
[#243](https://github.com/stencilproject/Stencil/pull/243)
- Now you can access string characters by index or get string length the same was as if it was an array, i.e. `{{ 'string'.first }}`, `{{ 'string'.last }}`, `{{ 'string'.1 }}`, `{{ 'string'.count }}`.
[Ilya Puchka](https://github.com/ilyapuchka)
[#245](https://github.com/stencilproject/Stencil/pull/245)
@@ -35,6 +34,9 @@
- Updated the codebase to use Swift 4 features.
[David Jennes](https://github.com/djbe)
[#239](https://github.com/stencilproject/Stencil/pull/239)
- Update to Spectre 0.9.0.
[Ilya Puchka](https://github.com/ilyapuchka)
[#247](https://github.com/stencilproject/Stencil/pull/247)
## 0.12.1

25
Package.resolved Normal file
View File

@@ -0,0 +1,25 @@
{
"object": {
"pins": [
{
"package": "PathKit",
"repositoryURL": "https://github.com/kylef/PathKit.git",
"state": {
"branch": null,
"revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0",
"version": "0.9.2"
}
},
{
"package": "Spectre",
"repositoryURL": "https://github.com/kylef/Spectre.git",
"state": {
"branch": null,
"revision": "f14ff47f45642aa5703900980b014c2e9394b6e5",
"version": "0.9.0"
}
}
]
},
"version": 1
}

View File

@@ -8,7 +8,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/kylef/PathKit.git", from: "0.9.0"),
.package(url: "https://github.com/kylef/Spectre.git", from: "0.8.0"),
.package(url: "https://github.com/kylef/Spectre.git", from: "0.9.0"),
],
targets: [
.target(name: "Stencil", dependencies: [

View File

@@ -118,7 +118,7 @@ final class IfExpressionParser {
private init(components: ArraySlice<String>, tokenParser: TokenParser, token: Token) throws {
var parsedComponents = Set<Int>()
var bracketsBalance = 0
self.tokens = try zip(components.indices, components).flatMap { (index, component) in
self.tokens = try zip(components.indices, components).compactMap { (index, component) in
guard !parsedComponents.contains(index) else { return nil }
if component == "(" {

View File

@@ -1,3 +1,8 @@
import XCTest
import StencilTests
stencilTests()
var tests = [XCTestCaseEntry]()
tests += StencilTests.__allTests()
XCTMain(tests)

View File

@@ -1,80 +1,84 @@
import XCTest
import Spectre
@testable import Stencil
func testContext() {
describe("Context") {
var context: Context!
class ContextTests: XCTestCase {
$0.before {
context = Context(dictionary: ["name": "Kyle"])
}
func testContext() {
describe("Context") {
var context: Context!
$0.it("allows you to get a value via subscripting") {
try expect(context["name"] as? String) == "Kyle"
}
$0.before {
context = Context(dictionary: ["name": "Kyle"])
}
$0.it("allows you to set a value via subscripting") {
context["name"] = "Katie"
try expect(context["name"] as? String) == "Katie"
}
$0.it("allows you to remove a value via subscripting") {
context["name"] = nil
try expect(context["name"]).to.beNil()
}
$0.it("allows you to retrieve a value from a parent") {
try context.push {
$0.it("allows you to get a value via subscripting") {
try expect(context["name"] as? String) == "Kyle"
}
}
$0.it("allows you to override a parent's value") {
try context.push {
$0.it("allows you to set a value via subscripting") {
context["name"] = "Katie"
try expect(context["name"] as? String) == "Katie"
}
}
$0.it("allows you to pop to restore previous state") {
context.push {
context["name"] = "Katie"
}
try expect(context["name"] as? String) == "Kyle"
}
$0.it("allows you to remove a parent's value in a level") {
try context.push {
$0.it("allows you to remove a value via subscripting") {
context["name"] = nil
try expect(context["name"]).to.beNil()
}
try expect(context["name"] as? String) == "Kyle"
}
$0.it("allows you to push a dictionary and run a closure then restoring previous state") {
var didRun = false
try context.push(dictionary: ["name": "Katie"]) {
didRun = true
try expect(context["name"] as? String) == "Katie"
$0.it("allows you to retrieve a value from a parent") {
try context.push {
try expect(context["name"] as? String) == "Kyle"
}
}
try expect(didRun).to.beTrue()
try expect(context["name"] as? String) == "Kyle"
}
$0.it("allows you to override a parent's value") {
try context.push {
context["name"] = "Katie"
try expect(context["name"] as? String) == "Katie"
}
}
$0.it("allows you to flatten the context contents") {
try context.push(dictionary: ["test": "abc"]) {
let flattened = context.flatten()
$0.it("allows you to pop to restore previous state") {
context.push {
context["name"] = "Katie"
}
try expect(flattened.count) == 2
try expect(flattened["name"] as? String) == "Kyle"
try expect(flattened["test"] as? String) == "abc"
try expect(context["name"] as? String) == "Kyle"
}
$0.it("allows you to remove a parent's value in a level") {
try context.push {
context["name"] = nil
try expect(context["name"]).to.beNil()
}
try expect(context["name"] as? String) == "Kyle"
}
$0.it("allows you to push a dictionary and run a closure then restoring previous state") {
var didRun = false
try context.push(dictionary: ["name": "Katie"]) {
didRun = true
try expect(context["name"] as? String) == "Katie"
}
try expect(didRun).to.beTrue()
try expect(context["name"] as? String) == "Kyle"
}
$0.it("allows you to flatten the context contents") {
try context.push(dictionary: ["test": "abc"]) {
let flattened = context.flatten()
try expect(flattened.count) == 2
try expect(flattened["name"] as? String) == "Kyle"
try expect(flattened["test"] as? String) == "abc"
}
}
}
}

View File

@@ -1,333 +1,335 @@
import XCTest
import Spectre
import PathKit
@testable import Stencil
func testEnvironment() {
describe("Environment") {
var environment: Environment!
var template: Template!
$0.before {
environment = Environment(loader: ExampleLoader())
template = nil
}
$0.it("can load a template from a name") {
let template = try environment.loadTemplate(name: "example.html")
try expect(template.name) == "example.html"
}
$0.it("can load a template from a names") {
let template = try environment.loadTemplate(names: ["first.html", "example.html"])
try expect(template.name) == "example.html"
}
$0.it("can render a template from a string") {
let result = try environment.renderTemplate(string: "Hello World")
try expect(result) == "Hello World"
}
$0.it("can render a template from a file") {
let result = try environment.renderTemplate(name: "example.html")
try expect(result) == "Hello World!"
}
$0.it("allows you to provide a custom template class") {
let environment = Environment(loader: ExampleLoader(), templateClass: CustomTemplate.self)
let result = try environment.renderTemplate(string: "Hello World")
try expect(result) == "here"
}
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
guard let range = template.templateString.range(of: token) else {
fatalError("Can't find '\(token)' in '\(template)'")
}
let lexer = Lexer(templateString: template.templateString)
let location = lexer.rangeLocation(range)
let sourceMap = SourceMap(filename: template.name, location: location)
let token = Token.block(value: token, at: sourceMap)
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
}
func expectError(reason: String, token: String,
file: String = #file, line: Int = #line, function: String = #function) throws {
let expectedError = expectedSyntaxError(token: token, template: template, description: reason)
let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.context("given syntax error") {
$0.it("reports syntax error on invalid for tag syntax") {
template = "Hello {% for name in %}{{ name }}, {% endfor %}!"
try expectError(reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.", token: "for name in")
}
$0.it("reports syntax error on missing endfor") {
template = "{% for name in names %}{{ name }}"
try expectError(reason: "`endfor` was not found.", token: "for name in names")
}
$0.it("reports syntax error on unknown tag") {
template = "{% for name in names %}{{ name }}{% end %}"
try expectError(reason: "Unknown template tag 'end'", token: "end")
}
}
$0.context("given unknown filter") {
$0.it("reports syntax error in for tag") {
template = "{% for name in names|unknown %}{{ name }}{% endfor %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "names|unknown")
}
$0.it("reports syntax error in for-where tag") {
template = "{% for name in names where name|unknown %}{{ name }}{% endfor %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in if tag") {
template = "{% if name|unknown %}{{ name }}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in elif tag") {
template = "{% if name %}{{ name }}{% elif name|unknown %}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in ifnot tag") {
template = "{% ifnot name|unknown %}{{ name }}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in filter tag") {
template = "{% filter unknown %}Text{% endfilter %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "filter unknown")
}
$0.it("reports syntax error in variable tag") {
template = "{{ name|unknown }}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
}
$0.context("given rendering error") {
$0.it("reports rendering error in variable filter") {
let filterExtension = Extension()
filterExtension.registerFilter("throw") { (value: Any?) in
throw TemplateSyntaxError("filter error")
}
environment.extensions += [filterExtension]
template = Template(templateString: "{{ name|throw }}", environment: environment)
try expectError(reason: "filter error", token: "name|throw")
}
$0.it("reports rendering error in filter tag") {
let filterExtension = Extension()
filterExtension.registerFilter("throw") { (value: Any?) in
throw TemplateSyntaxError("filter error")
}
environment.extensions += [filterExtension]
template = Template(templateString: "{% filter throw %}Test{% endfilter %}", environment: environment)
try expectError(reason: "filter error", token: "filter throw")
}
$0.it("reports rendering error in simple tag") {
let tagExtension = Extension()
tagExtension.registerSimpleTag("simpletag") { context in
throw TemplateSyntaxError("simpletag error")
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% simpletag %}", environment: environment)
try expectError(reason: "simpletag error", token: "simpletag")
}
$0.it("reporsts passing argument to simple filter") {
template = "{{ name|uppercase:5 }}"
try expectError(reason: "cannot invoke filter with an argument", token: "name|uppercase:5")
}
$0.it("reports rendering error in custom tag") {
let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in
return ErrorNode(token: token)
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% customtag %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag")
}
$0.it("reports rendering error in for body") {
let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in
return ErrorNode(token: token)
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% for name in names %}{% customtag %}{% endfor %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag")
}
$0.it("reports rendering error in block") {
let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in
return ErrorNode(token: token)
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% block some %}{% customtag %}{% endblock %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag")
}
}
$0.context("given included template") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
var environment = Environment(loader: loader)
class EnvironmentTests: XCTestCase {
func testEnvironment() {
describe("Environment") {
var environment: Environment!
var template: Template!
var includedTemplate: Template!
$0.before {
environment = Environment(loader: loader)
environment = Environment(loader: ExampleLoader())
template = nil
includedTemplate = nil
}
func expectError(reason: String, token: String, includedToken: String,
file: String = #file, line: Int = #line, function: String = #function) throws {
var expectedError = expectedSyntaxError(token: token, template: template, description: reason)
expectedError.stackTrace = [expectedSyntaxError(token: includedToken, template: includedTemplate, description: reason).token!]
$0.it("can load a template from a name") {
let template = try environment.loadTemplate(name: "example.html")
try expect(template.name) == "example.html"
}
let error = try expect(environment.render(template: template, context: ["target": "World"]),
$0.it("can load a template from a names") {
let template = try environment.loadTemplate(names: ["first.html", "example.html"])
try expect(template.name) == "example.html"
}
$0.it("can render a template from a string") {
let result = try environment.renderTemplate(string: "Hello World")
try expect(result) == "Hello World"
}
$0.it("can render a template from a file") {
let result = try environment.renderTemplate(name: "example.html")
try expect(result) == "Hello World!"
}
$0.it("allows you to provide a custom template class") {
let environment = Environment(loader: ExampleLoader(), templateClass: CustomTemplate.self)
let result = try environment.renderTemplate(string: "Hello World")
try expect(result) == "here"
}
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
guard let range = template.templateString.range(of: token) else {
fatalError("Can't find '\(token)' in '\(template)'")
}
let lexer = Lexer(templateString: template.templateString)
let location = lexer.rangeLocation(range)
let sourceMap = SourceMap(filename: template.name, location: location)
let token = Token.block(value: token, at: sourceMap)
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
}
func expectError(reason: String, token: String,
file: String = #file, line: Int = #line, function: String = #function) throws {
let expectedError = expectedSyntaxError(token: token, template: template, description: reason)
let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.it("reports syntax error in included template") {
template = Template(templateString: """
$0.context("given syntax error") {
$0.it("reports syntax error on invalid for tag syntax") {
template = "Hello {% for name in %}{{ name }}, {% endfor %}!"
try expectError(reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.", token: "for name in")
}
$0.it("reports syntax error on missing endfor") {
template = "{% for name in names %}{{ name }}"
try expectError(reason: "`endfor` was not found.", token: "for name in names")
}
$0.it("reports syntax error on unknown tag") {
template = "{% for name in names %}{{ name }}{% end %}"
try expectError(reason: "Unknown template tag 'end'", token: "end")
}
}
$0.context("given unknown filter") {
$0.it("reports syntax error in for tag") {
template = "{% for name in names|unknown %}{{ name }}{% endfor %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "names|unknown")
}
$0.it("reports syntax error in for-where tag") {
template = "{% for name in names where name|unknown %}{{ name }}{% endfor %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in if tag") {
template = "{% if name|unknown %}{{ name }}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in elif tag") {
template = "{% if name %}{{ name }}{% elif name|unknown %}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in ifnot tag") {
template = "{% ifnot name|unknown %}{{ name }}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
$0.it("reports syntax error in filter tag") {
template = "{% filter unknown %}Text{% endfilter %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "filter unknown")
}
$0.it("reports syntax error in variable tag") {
template = "{{ name|unknown }}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
}
}
$0.context("given rendering error") {
$0.it("reports rendering error in variable filter") {
let filterExtension = Extension()
filterExtension.registerFilter("throw") { (value: Any?) in
throw TemplateSyntaxError("filter error")
}
environment.extensions += [filterExtension]
template = Template(templateString: "{{ name|throw }}", environment: environment)
try expectError(reason: "filter error", token: "name|throw")
}
$0.it("reports rendering error in filter tag") {
let filterExtension = Extension()
filterExtension.registerFilter("throw") { (value: Any?) in
throw TemplateSyntaxError("filter error")
}
environment.extensions += [filterExtension]
template = Template(templateString: "{% filter throw %}Test{% endfilter %}", environment: environment)
try expectError(reason: "filter error", token: "filter throw")
}
$0.it("reports rendering error in simple tag") {
let tagExtension = Extension()
tagExtension.registerSimpleTag("simpletag") { context in
throw TemplateSyntaxError("simpletag error")
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% simpletag %}", environment: environment)
try expectError(reason: "simpletag error", token: "simpletag")
}
$0.it("reporsts passing argument to simple filter") {
template = "{{ name|uppercase:5 }}"
try expectError(reason: "cannot invoke filter with an argument", token: "name|uppercase:5")
}
$0.it("reports rendering error in custom tag") {
let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in
return ErrorNode(token: token)
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% customtag %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag")
}
$0.it("reports rendering error in for body") {
let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in
return ErrorNode(token: token)
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% for name in names %}{% customtag %}{% endfor %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag")
}
$0.it("reports rendering error in block") {
let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in
return ErrorNode(token: token)
}
environment.extensions += [tagExtension]
template = Template(templateString: "{% block some %}{% customtag %}{% endblock %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag")
}
}
$0.context("given included template") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
var environment = Environment(loader: loader)
var template: Template!
var includedTemplate: Template!
$0.before {
environment = Environment(loader: loader)
template = nil
includedTemplate = nil
}
func expectError(reason: String, token: String, includedToken: String,
file: String = #file, line: Int = #line, function: String = #function) throws {
var expectedError = expectedSyntaxError(token: token, template: template, description: reason)
expectedError.stackTrace = [expectedSyntaxError(token: includedToken, template: includedTemplate, description: reason).token!]
let error = try expect(environment.render(template: template, context: ["target": "World"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.it("reports syntax error in included template") {
template = Template(templateString: """
{% include "invalid-include.html" %}
""", environment: environment)
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: """
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: """
include "invalid-include.html"
""",
includedToken: "target|unknown")
}
includedToken: "target|unknown")
}
$0.it("reports runtime error in included template") {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
environment.extensions += [filterExtension]
$0.it("reports runtime error in included template") {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
environment.extensions += [filterExtension]
template = Template(templateString: """
template = Template(templateString: """
{% include "invalid-include.html" %}
""", environment: environment)
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
try expectError(reason: "filter error",
token: "include \"invalid-include.html\"",
includedToken: "target|unknown")
}
}
$0.context("given base and child templates") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
var environment: Environment!
var childTemplate: Template!
var baseTemplate: Template!
$0.before {
environment = Environment(loader: loader)
childTemplate = nil
baseTemplate = nil
}
func expectError(reason: String, childToken: String, baseToken: String?,
file: String = #file, line: Int = #line, function: String = #function) throws {
var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason)
if let baseToken = baseToken {
expectedError.stackTrace = [expectedSyntaxError(token: baseToken, template: baseTemplate, description: reason).token!]
try expectError(reason: "filter error",
token: "include \"invalid-include.html\"",
includedToken: "target|unknown")
}
let error = try expect(environment.render(template: childTemplate, context: ["target": "World"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.it("reports syntax error in base template") {
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
$0.context("given base and child templates") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
var environment: Environment!
var childTemplate: Template!
var baseTemplate: Template!
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
childToken: "extends \"invalid-base.html\"",
baseToken: "target|unknown")
}
$0.before {
environment = Environment(loader: loader)
childTemplate = nil
baseTemplate = nil
}
$0.it("reports runtime error in base template") {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
environment.extensions += [filterExtension]
func expectError(reason: String, childToken: String, baseToken: String?,
file: String = #file, line: Int = #line, function: String = #function) throws {
var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason)
if let baseToken = baseToken {
expectedError.stackTrace = [expectedSyntaxError(token: baseToken, template: baseTemplate, description: reason).token!]
}
let error = try expect(environment.render(template: childTemplate, context: ["target": "World"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
$0.it("reports syntax error in base template") {
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
try expectError(reason: "filter error",
childToken: "block.super",
baseToken: "target|unknown")
}
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
childToken: "extends \"invalid-base.html\"",
baseToken: "target|unknown")
}
$0.it("reports syntax error in child template") {
childTemplate = Template(templateString: """
$0.it("reports runtime error in base template") {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
environment.extensions += [filterExtension]
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
try expectError(reason: "filter error",
childToken: "block.super",
baseToken: "target|unknown")
}
$0.it("reports syntax error in child template") {
childTemplate = Template(templateString: """
{% extends "base.html" %}
{% block body %}Child {{ target|unknown }}{% endblock %}
""", environment: environment, name: nil)
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
childToken: "target|unknown",
baseToken: nil)
}
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
childToken: "target|unknown",
baseToken: nil)
}
$0.it("reports runtime error in child template") {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
environment.extensions += [filterExtension]
$0.it("reports runtime error in child template") {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
environment.extensions += [filterExtension]
childTemplate = Template(templateString: """
childTemplate = Template(templateString: """
{% extends "base.html" %}
{% block body %}Child {{ target|unknown }}{% endblock %}
""", environment: environment, name: nil)
try expectError(reason: "filter error",
childToken: "target|unknown",
baseToken: nil)
try expectError(reason: "filter error",
childToken: "target|unknown",
baseToken: nil)
}
}
}
}
}

View File

@@ -1,342 +1,344 @@
import XCTest
import Spectre
@testable import Stencil
class ExpressionsTests: XCTestCase {
func testExpressions() {
describe("Expression") {
let parser = TokenParser(tokens: [], environment: Environment())
func testExpressions() {
describe("Expression") {
let parser = TokenParser(tokens: [], environment: Environment())
func parseExpression(components: [String]) throws -> Expression {
let parser = try IfExpressionParser.parser(components: components, tokenParser: parser, token: .text(value: "", at: .unknown))
return try parser.parse()
}
$0.describe("VariableExpression") {
let expression = VariableExpression(variable: Variable("value"))
$0.it("evaluates to true when value is not nil") {
let context = Context(dictionary: ["value": "known"])
try expect(try expression.evaluate(context: context)).to.beTrue()
func parseExpression(components: [String]) throws -> Expression {
let parser = try IfExpressionParser.parser(components: components, tokenParser: parser, token: .text(value: "", at: .unknown))
return try parser.parse()
}
$0.it("evaluates to false when value is unset") {
let context = Context()
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.describe("VariableExpression") {
let expression = VariableExpression(variable: Variable("value"))
$0.it("evaluates to true when array variable is not empty") {
let items: [[String: Any]] = [["key":"key1","value":42],["key":"key2","value":1337]]
let context = Context(dictionary: ["value": [items]])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when array value is empty") {
let emptyItems = [[String: Any]]()
let context = Context(dictionary: ["value": emptyItems])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when dictionary value is empty") {
let emptyItems = [String:Any]()
let context = Context(dictionary: ["value": emptyItems])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when Array<Any> value is empty") {
let context = Context(dictionary: ["value": ([] as [Any])])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to true when integer value is above 0") {
let context = Context(dictionary: ["value": 1])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to true with string") {
let context = Context(dictionary: ["value": "test"])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when empty string") {
let context = Context(dictionary: ["value": ""])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when integer value is below 0 or below") {
let context = Context(dictionary: ["value": 0])
try expect(try expression.evaluate(context: context)).to.beFalse()
let negativeContext = Context(dictionary: ["value": 0])
try expect(try expression.evaluate(context: negativeContext)).to.beFalse()
}
$0.it("evaluates to true when float value is above 0") {
let context = Context(dictionary: ["value": Float(0.5)])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when float is 0 or below") {
let context = Context(dictionary: ["value": Float(0)])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to true when double value is above 0") {
let context = Context(dictionary: ["value": Double(0.5)])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when double is 0 or below") {
let context = Context(dictionary: ["value": Double(0)])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when uint is 0") {
let context = Context(dictionary: ["value": UInt(0)])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
}
$0.describe("NotExpression") {
$0.it("returns truthy for positive expressions") {
let expression = NotExpression(expression: StaticExpression(value: true))
try expect(expression.evaluate(context: Context())).to.beFalse()
}
$0.it("returns falsy for negative expressions") {
let expression = NotExpression(expression: StaticExpression(value: false))
try expect(expression.evaluate(context: Context())).to.beTrue()
}
}
$0.describe("expression parsing") {
$0.it("can parse a variable expression") {
let expression = try parseExpression(components: ["value"])
try expect(expression.evaluate(context: Context())).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
}
$0.it("can parse a not expression") {
let expression = try parseExpression(components: ["not", "value"])
try expect(expression.evaluate(context: Context())).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
}
$0.describe("and expression") {
let expression = try! parseExpression(components: ["lhs", "and", "rhs"])
$0.it("evaluates to false with lhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse()
$0.it("evaluates to true when value is not nil") {
let context = Context(dictionary: ["value": "known"])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false with rhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse()
$0.it("evaluates to false when value is unset") {
let context = Context()
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false with lhs and rhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse()
$0.it("evaluates to true when array variable is not empty") {
let items: [[String: Any]] = [["key":"key1","value":42],["key":"key2","value":1337]]
let context = Context(dictionary: ["value": [items]])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to true with lhs and rhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
$0.it("evaluates to false when array value is empty") {
let emptyItems = [[String: Any]]()
let context = Context(dictionary: ["value": emptyItems])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when dictionary value is empty") {
let emptyItems = [String:Any]()
let context = Context(dictionary: ["value": emptyItems])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when Array<Any> value is empty") {
let context = Context(dictionary: ["value": ([] as [Any])])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to true when integer value is above 0") {
let context = Context(dictionary: ["value": 1])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to true with string") {
let context = Context(dictionary: ["value": "test"])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when empty string") {
let context = Context(dictionary: ["value": ""])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when integer value is below 0 or below") {
let context = Context(dictionary: ["value": 0])
try expect(try expression.evaluate(context: context)).to.beFalse()
let negativeContext = Context(dictionary: ["value": 0])
try expect(try expression.evaluate(context: negativeContext)).to.beFalse()
}
$0.it("evaluates to true when float value is above 0") {
let context = Context(dictionary: ["value": Float(0.5)])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when float is 0 or below") {
let context = Context(dictionary: ["value": Float(0)])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to true when double value is above 0") {
let context = Context(dictionary: ["value": Double(0.5)])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when double is 0 or below") {
let context = Context(dictionary: ["value": Double(0)])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when uint is 0") {
let context = Context(dictionary: ["value": UInt(0)])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
}
$0.describe("or expression") {
let expression = try! parseExpression(components: ["lhs", "or", "rhs"])
$0.it("evaluates to true with lhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue()
}
$0.it("evaluates to true with rhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beTrue()
}
$0.it("evaluates to true with lhs and rhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
}
$0.it("evaluates to false with lhs and rhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse()
}
}
$0.describe("equality expression") {
let expression = try! parseExpression(components: ["lhs", "==", "rhs"])
$0.it("evaluates to true with equal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue()
}
$0.it("evaluates to false with non equal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beFalse()
}
$0.it("evaluates to true with nils") {
try expect(expression.evaluate(context: Context(dictionary: [:]))).to.beTrue()
}
$0.it("evaluates to true with numbers") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.0]))).to.beTrue()
}
$0.it("evaluates to false with non equal numbers") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.1]))).to.beFalse()
}
$0.it("evaluates to true with booleans") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
}
$0.it("evaluates to false with falsy booleans") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse()
}
$0.it("evaluates to false with different types") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": 1]))).to.beFalse()
}
}
$0.describe("inequality expression") {
let expression = try! parseExpression(components: ["lhs", "!=", "rhs"])
$0.it("evaluates to true with inequal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue()
}
$0.it("evaluates to false with equal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "b", "rhs": "b"]))).to.beFalse()
}
}
$0.describe("more than expression") {
let expression = try! parseExpression(components: ["lhs", ">", "rhs"])
$0.it("evaluates to true with lhs > rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue()
}
$0.it("evaluates to false with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse()
}
}
$0.describe("more than equal expression") {
let expression = try! parseExpression(components: ["lhs", ">=", "rhs"])
$0.it("evaluates to true with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
}
$0.it("evaluates to false with lhs < rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.1]))).to.beFalse()
}
}
$0.describe("less than expression") {
let expression = try! parseExpression(components: ["lhs", "<", "rhs"])
$0.it("evaluates to true with lhs < rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue()
}
$0.it("evaluates to false with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse()
}
}
$0.describe("less than equal expression") {
let expression = try! parseExpression(components: ["lhs", "<=", "rhs"])
$0.it("evaluates to true with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
}
$0.it("evaluates to false with lhs > rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.1, "rhs": 5.0]))).to.beFalse()
}
}
$0.describe("multiple expression") {
let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"])
$0.it("evaluates to true with one") {
try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue()
}
$0.it("evaluates to true with one and three") {
try expect(expression.evaluate(context: Context(dictionary: ["one": true, "three": true]))).to.beTrue()
}
$0.it("evaluates to true with two") {
try expect(expression.evaluate(context: Context(dictionary: ["two": true]))).to.beTrue()
}
$0.it("evaluates to false with two and three") {
try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse()
}
$0.it("evaluates to false with two and three") {
try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse()
}
$0.it("evaluates to false with nothing") {
$0.describe("NotExpression") {
$0.it("returns truthy for positive expressions") {
let expression = NotExpression(expression: StaticExpression(value: true))
try expect(expression.evaluate(context: Context())).to.beFalse()
}
}
$0.describe("in expression") {
let expression = try! parseExpression(components: ["lhs", "in", "rhs"])
$0.it("evaluates to true when rhs contains lhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [1, 2, 3]]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["a", "b", "c"]]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "abc"]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1...3]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1..<3]))).to.beTrue()
}
$0.it("evaluates to false when rhs does not contain lhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [2, 3, 4]]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["b", "c", "d"]]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "bcd"]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 1...3]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 3, "rhs": 1..<3]))).to.beFalse()
$0.it("returns falsy for negative expressions") {
let expression = NotExpression(expression: StaticExpression(value: false))
try expect(expression.evaluate(context: Context())).to.beTrue()
}
}
$0.describe("sub expression") {
$0.it("evaluates correctly") {
let context = Context(dictionary: ["one": false, "two": false, "three": true, "four": true])
let expression = try! parseExpression(components: ["one", "and", "two", "or", "three", "and", "four"])
let expressionWithBrackets = try! parseExpression(components: ["one", "and", "(", "(", "two", ")", "or", "(", "three", "and", "four", ")", ")"])
try expect(expression.evaluate(context: context)).to.beTrue()
try expect(expressionWithBrackets.evaluate(context: context)).to.beFalse()
let notExpression = try! parseExpression(components: ["not", "one", "or", "three"])
let notExpressionWithBrackets = try! parseExpression(components: ["not", "(", "one", "or", "three", ")"])
try expect(notExpression.evaluate(context: context)).to.beTrue()
try expect(notExpressionWithBrackets.evaluate(context: context)).to.beFalse()
$0.describe("expression parsing") {
$0.it("can parse a variable expression") {
let expression = try parseExpression(components: ["value"])
try expect(expression.evaluate(context: Context())).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
}
$0.it("fails when brackets are not balanced") {
try expect(parseExpression(components: ["(", "lhs", "and", "rhs"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket"))
try expect(parseExpression(components: [")", "lhs", "and", "rhs"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket"))
try expect(parseExpression(components: ["lhs", "and", "rhs", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket"))
try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", "("]))
.toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket"))
try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket"))
try expect(parseExpression(components: ["(", "lhs", "and", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: end"))
try expect(parseExpression(components: ["(", "and", "rhs", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: infix operator 'and' doesn't have a left hand side"))
$0.it("can parse a not expression") {
let expression = try parseExpression(components: ["not", "value"])
try expect(expression.evaluate(context: Context())).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
}
$0.describe("and expression") {
let expression = try! parseExpression(components: ["lhs", "and", "rhs"])
$0.it("evaluates to false with lhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse()
}
$0.it("evaluates to false with rhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse()
}
$0.it("evaluates to false with lhs and rhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse()
}
$0.it("evaluates to true with lhs and rhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
}
}
$0.describe("or expression") {
let expression = try! parseExpression(components: ["lhs", "or", "rhs"])
$0.it("evaluates to true with lhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue()
}
$0.it("evaluates to true with rhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beTrue()
}
$0.it("evaluates to true with lhs and rhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
}
$0.it("evaluates to false with lhs and rhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse()
}
}
$0.describe("equality expression") {
let expression = try! parseExpression(components: ["lhs", "==", "rhs"])
$0.it("evaluates to true with equal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue()
}
$0.it("evaluates to false with non equal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beFalse()
}
$0.it("evaluates to true with nils") {
try expect(expression.evaluate(context: Context(dictionary: [:]))).to.beTrue()
}
$0.it("evaluates to true with numbers") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.0]))).to.beTrue()
}
$0.it("evaluates to false with non equal numbers") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.1]))).to.beFalse()
}
$0.it("evaluates to true with booleans") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
}
$0.it("evaluates to false with falsy booleans") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse()
}
$0.it("evaluates to false with different types") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": 1]))).to.beFalse()
}
}
$0.describe("inequality expression") {
let expression = try! parseExpression(components: ["lhs", "!=", "rhs"])
$0.it("evaluates to true with inequal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue()
}
$0.it("evaluates to false with equal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "b", "rhs": "b"]))).to.beFalse()
}
}
$0.describe("more than expression") {
let expression = try! parseExpression(components: ["lhs", ">", "rhs"])
$0.it("evaluates to true with lhs > rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue()
}
$0.it("evaluates to false with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse()
}
}
$0.describe("more than equal expression") {
let expression = try! parseExpression(components: ["lhs", ">=", "rhs"])
$0.it("evaluates to true with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
}
$0.it("evaluates to false with lhs < rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.1]))).to.beFalse()
}
}
$0.describe("less than expression") {
let expression = try! parseExpression(components: ["lhs", "<", "rhs"])
$0.it("evaluates to true with lhs < rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue()
}
$0.it("evaluates to false with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse()
}
}
$0.describe("less than equal expression") {
let expression = try! parseExpression(components: ["lhs", "<=", "rhs"])
$0.it("evaluates to true with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
}
$0.it("evaluates to false with lhs > rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.1, "rhs": 5.0]))).to.beFalse()
}
}
$0.describe("multiple expression") {
let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"])
$0.it("evaluates to true with one") {
try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue()
}
$0.it("evaluates to true with one and three") {
try expect(expression.evaluate(context: Context(dictionary: ["one": true, "three": true]))).to.beTrue()
}
$0.it("evaluates to true with two") {
try expect(expression.evaluate(context: Context(dictionary: ["two": true]))).to.beTrue()
}
$0.it("evaluates to false with two and three") {
try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse()
}
$0.it("evaluates to false with two and three") {
try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse()
}
$0.it("evaluates to false with nothing") {
try expect(expression.evaluate(context: Context())).to.beFalse()
}
}
$0.describe("in expression") {
let expression = try! parseExpression(components: ["lhs", "in", "rhs"])
$0.it("evaluates to true when rhs contains lhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [1, 2, 3]]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["a", "b", "c"]]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "abc"]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1...3]))).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1..<3]))).to.beTrue()
}
$0.it("evaluates to false when rhs does not contain lhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [2, 3, 4]]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["b", "c", "d"]]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "bcd"]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 1...3]))).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 3, "rhs": 1..<3]))).to.beFalse()
}
}
$0.describe("sub expression") {
$0.it("evaluates correctly") {
let context = Context(dictionary: ["one": false, "two": false, "three": true, "four": true])
let expression = try! parseExpression(components: ["one", "and", "two", "or", "three", "and", "four"])
let expressionWithBrackets = try! parseExpression(components: ["one", "and", "(", "(", "two", ")", "or", "(", "three", "and", "four", ")", ")"])
try expect(expression.evaluate(context: context)).to.beTrue()
try expect(expressionWithBrackets.evaluate(context: context)).to.beFalse()
let notExpression = try! parseExpression(components: ["not", "one", "or", "three"])
let notExpressionWithBrackets = try! parseExpression(components: ["not", "(", "one", "or", "three", ")"])
try expect(notExpression.evaluate(context: context)).to.beTrue()
try expect(notExpressionWithBrackets.evaluate(context: context)).to.beFalse()
}
$0.it("fails when brackets are not balanced") {
try expect(parseExpression(components: ["(", "lhs", "and", "rhs"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket"))
try expect(parseExpression(components: [")", "lhs", "and", "rhs"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket"))
try expect(parseExpression(components: ["lhs", "and", "rhs", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket"))
try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", "("]))
.toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket"))
try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket"))
try expect(parseExpression(components: ["(", "lhs", "and", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: end"))
try expect(parseExpression(components: ["(", "and", "rhs", ")"]))
.toThrow(TemplateSyntaxError("'if' expression error: infix operator 'and' doesn't have a left hand side"))
}
}
}
}

View File

@@ -1,370 +1,372 @@
import XCTest
import Spectre
@testable import Stencil
class FilterTests: XCTestCase {
func testFilter() {
describe("template filters") {
let context: [String: Any] = ["name": "Kyle"]
func testFilter() {
describe("template filters") {
let context: [String: Any] = ["name": "Kyle"]
$0.it("allows you to register a custom filter") {
let template = Template(templateString: "{{ name|repeat }}")
$0.it("allows you to register a custom filter") {
let template = Template(templateString: "{{ name|repeat }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { (value: Any?) in
if let value = value as? String {
return "\(value) \(value)"
}
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { (value: Any?) in
if let value = value as? String {
return "\(value) \(value)"
return nil
}
return nil
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == "Kyle Kyle"
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == "Kyle Kyle"
}
$0.it("allows you to register a custom filter which accepts single argument") {
let template = Template(templateString: """
{{ name|repeat:'value1, "value2"' }}
""")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { value, arguments in
if !arguments.isEmpty {
return "\(value!) \(value!) with args \(arguments.first!!)"
}
return nil
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == """
Kyle Kyle with args value1, "value2"
"""
}
$0.it("allows you to register a custom filter which accepts several arguments") {
$0.it("allows you to register a custom filter which accepts single argument") {
let template = Template(templateString: """
{{ name|repeat:'value"1"',"value'2'",'(key, value)' }}
{{ name|repeat:'value1, "value2"' }}
""")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { value, arguments in
if !arguments.isEmpty {
return "\(value!) \(value!) with args 0: \(arguments[0]!), 1: \(arguments[1]!), 2: \(arguments[2]!)"
}
if !arguments.isEmpty {
return "\(value!) \(value!) with args \(arguments.first!!)"
}
return nil
return nil
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == """
Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value)
Kyle Kyle with args value1, "value2"
"""
}
$0.it("allows you to register a custom which throws") {
let template = Template(templateString: "{{ name|repeat }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { (value: Any?) in
throw TemplateSyntaxError("No Repeat")
}
let context = Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))
try expect(try template.render(context)).toThrow(TemplateSyntaxError(reason: "No Repeat", token: template.tokens.first))
}
$0.it("allows you to register a custom filter which accepts several arguments") {
let template = Template(templateString: """
{{ name|repeat:'value"1"',"value'2'",'(key, value)' }}
""")
$0.it("allows you to override a default filter") {
let template = Template(templateString: "{{ name|join }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { value, arguments in
if !arguments.isEmpty {
return "\(value!) \(value!) with args 0: \(arguments[0]!), 1: \(arguments[1]!), 2: \(arguments[2]!)"
}
let repeatExtension = Extension()
repeatExtension.registerFilter("join") { (value: Any?) in
return "joined"
return nil
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == """
Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value)
"""
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == "joined"
$0.it("allows you to register a custom which throws") {
let template = Template(templateString: "{{ name|repeat }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { (value: Any?) in
throw TemplateSyntaxError("No Repeat")
}
let context = Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))
try expect(try template.render(context)).toThrow(TemplateSyntaxError(reason: "No Repeat", token: template.tokens.first))
}
$0.it("allows you to override a default filter") {
let template = Template(templateString: "{{ name|join }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("join") { (value: Any?) in
return "joined"
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == "joined"
}
$0.it("allows whitespace in expression") {
let template = Template(templateString: """
{{ value | join : ", " }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "One, Two"
}
$0.it("throws when you pass arguments to simple filter") {
let template = Template(templateString: "{{ name|uppercase:5 }}")
try expect(try template.render(Context(dictionary: ["name": "kyle"]))).toThrow()
}
}
$0.it("allows whitespace in expression") {
describe("string filters") {
$0.context("given string") {
$0.it("transforms a string to be capitalized") {
let template = Template(templateString: "{{ name|capitalize }}")
let result = try template.render(Context(dictionary: ["name": "kyle"]))
try expect(result) == "Kyle"
}
$0.it("transforms a string to be uppercase") {
let template = Template(templateString: "{{ name|uppercase }}")
let result = try template.render(Context(dictionary: ["name": "kyle"]))
try expect(result) == "KYLE"
}
$0.it("transforms a string to be lowercase") {
let template = Template(templateString: "{{ name|lowercase }}")
let result = try template.render(Context(dictionary: ["name": "Kyle"]))
try expect(result) == "kyle"
}
}
$0.context("given array of strings") {
$0.it("transforms a string to be capitalized") {
let template = Template(templateString: "{{ names|capitalize }}")
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
try expect(result) == """
["Kyle", "Kyle"]
"""
}
$0.it("transforms a string to be uppercase") {
let template = Template(templateString: "{{ names|uppercase }}")
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
try expect(result) == """
["KYLE", "KYLE"]
"""
}
$0.it("transforms a string to be lowercase") {
let template = Template(templateString: "{{ names|lowercase }}")
let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]]))
try expect(result) == """
["kyle", "kyle"]
"""
}
}
}
describe("default filter") {
let template = Template(templateString: """
{{ value | join : ", " }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "One, Two"
}
Hello {{ name|default:"World" }}
""")
$0.it("throws when you pass arguments to simple filter") {
let template = Template(templateString: "{{ name|uppercase:5 }}")
try expect(try template.render(Context(dictionary: ["name": "kyle"]))).toThrow()
}
}
describe("string filters") {
$0.context("given string") {
$0.it("transforms a string to be capitalized") {
let template = Template(templateString: "{{ name|capitalize }}")
let result = try template.render(Context(dictionary: ["name": "kyle"]))
try expect(result) == "Kyle"
}
$0.it("transforms a string to be uppercase") {
let template = Template(templateString: "{{ name|uppercase }}")
let result = try template.render(Context(dictionary: ["name": "kyle"]))
try expect(result) == "KYLE"
}
$0.it("transforms a string to be lowercase") {
let template = Template(templateString: "{{ name|lowercase }}")
$0.it("shows the variable value") {
let result = try template.render(Context(dictionary: ["name": "Kyle"]))
try expect(result) == "kyle"
try expect(result) == "Hello Kyle"
}
$0.it("shows the default value") {
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "Hello World"
}
$0.it("supports multiple defaults") {
let template = Template(templateString: """
Hello {{ name|default:a,b,c,"World" }}
""")
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "Hello World"
}
$0.it("can use int as default") {
let template = Template(templateString: "{{ value|default:1 }}")
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "1"
}
$0.it("can use float as default") {
let template = Template(templateString: "{{ value|default:1.5 }}")
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "1.5"
}
$0.it("checks for underlying nil value correctly") {
let template = Template(templateString: """
Hello {{ user.name|default:"anonymous" }}
""")
let nilName: String? = nil
let user: [String: Any?] = ["name": nilName]
let result = try template.render(Context(dictionary: ["user": user]))
try expect(result) == "Hello anonymous"
}
}
$0.context("given array of strings") {
$0.it("transforms a string to be capitalized") {
let template = Template(templateString: "{{ names|capitalize }}")
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
describe("join filter") {
let template = Template(templateString: """
{{ value|join:", " }}
""")
$0.it("joins a collection of strings") {
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "One, Two"
}
$0.it("joins a mixed-type collection") {
let result = try template.render(Context(dictionary: ["value": ["One", 2, true, 10.5, "Five"]]))
try expect(result) == "One, 2, true, 10.5, Five"
}
$0.it("can join by non string") {
let template = Template(templateString: """
{{ value|join:separator }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"], "separator": true]))
try expect(result) == "OnetrueTwo"
}
$0.it("can join without arguments") {
let template = Template(templateString: """
{{ value|join }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "OneTwo"
}
}
describe("split filter") {
let template = Template(templateString: """
{{ value|split:", " }}
""")
$0.it("split a string into array") {
let result = try template.render(Context(dictionary: ["value": "One, Two"]))
try expect(result) == """
["Kyle", "Kyle"]
["One", "Two"]
"""
}
$0.it("transforms a string to be uppercase") {
let template = Template(templateString: "{{ names|uppercase }}")
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
$0.it("can split without arguments") {
let template = Template(templateString: """
{{ value|split }}
""")
let result = try template.render(Context(dictionary: ["value": "One, Two"]))
try expect(result) == """
["KYLE", "KYLE"]
"""
}
$0.it("transforms a string to be lowercase") {
let template = Template(templateString: "{{ names|lowercase }}")
let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]]))
try expect(result) == """
["kyle", "kyle"]
["One,", "Two"]
"""
}
}
}
describe("default filter") {
let template = Template(templateString: """
Hello {{ name|default:"World" }}
""")
$0.it("shows the variable value") {
let result = try template.render(Context(dictionary: ["name": "Kyle"]))
try expect(result) == "Hello Kyle"
}
$0.it("shows the default value") {
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "Hello World"
}
$0.it("supports multiple defaults") {
let template = Template(templateString: """
Hello {{ name|default:a,b,c,"World" }}
""")
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "Hello World"
}
$0.it("can use int as default") {
let template = Template(templateString: "{{ value|default:1 }}")
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "1"
}
$0.it("can use float as default") {
let template = Template(templateString: "{{ value|default:1.5 }}")
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "1.5"
}
$0.it("checks for underlying nil value correctly") {
let template = Template(templateString: """
Hello {{ user.name|default:"anonymous" }}
""")
let nilName: String? = nil
let user: [String: Any?] = ["name": nilName]
let result = try template.render(Context(dictionary: ["user": user]))
try expect(result) == "Hello anonymous"
}
}
describe("join filter") {
let template = Template(templateString: """
{{ value|join:", " }}
""")
$0.it("joins a collection of strings") {
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "One, Two"
}
$0.it("joins a mixed-type collection") {
let result = try template.render(Context(dictionary: ["value": ["One", 2, true, 10.5, "Five"]]))
try expect(result) == "One, 2, true, 10.5, Five"
}
$0.it("can join by non string") {
let template = Template(templateString: """
{{ value|join:separator }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"], "separator": true]))
try expect(result) == "OnetrueTwo"
}
$0.it("can join without arguments") {
let template = Template(templateString: """
{{ value|join }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "OneTwo"
}
}
describe("split filter") {
let template = Template(templateString: """
{{ value|split:", " }}
""")
$0.it("split a string into array") {
let result = try template.render(Context(dictionary: ["value": "One, Two"]))
try expect(result) == """
["One", "Two"]
"""
}
$0.it("can split without arguments") {
let template = Template(templateString: """
{{ value|split }}
""")
let result = try template.render(Context(dictionary: ["value": "One, Two"]))
try expect(result) == """
["One,", "Two"]
"""
}
}
describe("filter suggestion") {
var template: Template!
var filterExtension: Extension!
describe("filter suggestion") {
var template: Template!
var filterExtension: Extension!
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
guard let range = template.templateString.range(of: token) else {
fatalError("Can't find '\(token)' in '\(template)'")
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
guard let range = template.templateString.range(of: token) else {
fatalError("Can't find '\(token)' in '\(template)'")
}
let lexer = Lexer(templateString: template.templateString)
let location = lexer.rangeLocation(range)
let sourceMap = SourceMap(filename: template.name, location: location)
let token = Token.block(value: token, at: sourceMap)
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
}
let lexer = Lexer(templateString: template.templateString)
let location = lexer.rangeLocation(range)
let sourceMap = SourceMap(filename: template.name, location: location)
let token = Token.block(value: token, at: sourceMap)
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
func expectError(reason: String, token: String,
file: String = #file, line: Int = #line, function: String = #function) throws {
let expectedError = expectedSyntaxError(token: token, template: template, description: reason)
let environment = Environment(extensions: [filterExtension])
let error = try expect(environment.render(template: template, context: [:]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.it("made for unknown filter") {
template = Template(templateString: "{{ value|unknownFilter }}")
filterExtension = Extension()
filterExtension.registerFilter("knownFilter") { value, _ in value }
try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'.", token: "value|unknownFilter")
}
$0.it("made for multiple similar filters") {
template = Template(templateString: "{{ value|lowerFirst }}")
filterExtension = Extension()
filterExtension.registerFilter("lowerFirstWord") { value, _ in value }
filterExtension.registerFilter("lowerFirstLetter") { value, _ in value }
try expectError(reason: "Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'.", token: "value|lowerFirst")
}
$0.it("not made when can't find similar filter") {
template = Template(templateString: "{{ value|unknownFilter }}")
try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'lowerFirstWord'.", token: "value|unknownFilter")
}
}
func expectError(reason: String, token: String,
file: String = #file, line: Int = #line, function: String = #function) throws {
let expectedError = expectedSyntaxError(token: token, template: template, description: reason)
let environment = Environment(extensions: [filterExtension])
let error = try expect(environment.render(template: template, context: [:]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.it("made for unknown filter") {
template = Template(templateString: "{{ value|unknownFilter }}")
filterExtension = Extension()
filterExtension.registerFilter("knownFilter") { value, _ in value }
try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'.", token: "value|unknownFilter")
}
$0.it("made for multiple similar filters") {
template = Template(templateString: "{{ value|lowerFirst }}")
filterExtension = Extension()
filterExtension.registerFilter("lowerFirstWord") { value, _ in value }
filterExtension.registerFilter("lowerFirstLetter") { value, _ in value }
try expectError(reason: "Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'.", token: "value|lowerFirst")
}
$0.it("not made when can't find similar filter") {
template = Template(templateString: "{{ value|unknownFilter }}")
try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'lowerFirstWord'.", token: "value|unknownFilter")
}
}
describe("indent filter") {
$0.it("indents content") {
let template = Template(templateString: """
{{ value|indent:2 }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
Two
"""
}
$0.it("can indent with arbitrary character") {
let template = Template(templateString: """
{{ value|indent:2,"\t" }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
\t\tTwo
"""
}
$0.it("can indent first line") {
let template = Template(templateString: """
{{ value|indent:2," ",true }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
describe("indent filter") {
$0.it("indents content") {
let template = Template(templateString: """
{{ value|indent:2 }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""
}
$0.it("does not indent empty lines") {
let template = Template(templateString: """
{{ value|indent }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
"""]))
try expect(result) == """
One
Two
"""
}
$0.it("can indent with arbitrary character") {
let template = Template(templateString: """
{{ value|indent:2,"\t" }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
\t\tTwo
"""
}
$0.it("can indent first line") {
let template = Template(templateString: """
{{ value|indent:2," ",true }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
Two
"""
}
$0.it("does not indent empty lines") {
let template = Template(templateString: """
{{ value|indent }}
""")
let result = try template.render(Context(dictionary: ["value": """
One
"""
Two
"""]))
try expect(result) == """
One
Two
"""
}
}
}
}

View File

@@ -1,49 +1,51 @@
import XCTest
import Spectre
import Stencil
class FilterTagTests: XCTestCase {
func testFilterTag() {
describe("Filter Tag") {
$0.it("allows you to use a filter") {
let template = Template(templateString: "{% filter uppercase %}Test{% endfilter %}")
let result = try template.render()
try expect(result) == "TEST"
}
func testFilterTag() {
describe("Filter Tag") {
$0.it("allows you to use a filter") {
let template = Template(templateString: "{% filter uppercase %}Test{% endfilter %}")
let result = try template.render()
try expect(result) == "TEST"
}
$0.it("allows you to chain filters") {
let template = Template(templateString: "{% filter lowercase|capitalize %}TEST{% endfilter %}")
let result = try template.render()
try expect(result) == "Test"
}
$0.it("allows you to chain filters") {
let template = Template(templateString: "{% filter lowercase|capitalize %}TEST{% endfilter %}")
let result = try template.render()
try expect(result) == "Test"
}
$0.it("errors without a filter") {
let template = Template(templateString: "Some {% filter %}Test{% endfilter %}")
try expect(try template.render()).toThrow()
}
$0.it("errors without a filter") {
let template = Template(templateString: "Some {% filter %}Test{% endfilter %}")
try expect(try template.render()).toThrow()
}
$0.it("can render filters with arguments") {
let ext = Extension()
ext.registerFilter("split", filter: {
return ($0 as! String).components(separatedBy: $1[0] as! String)
})
let env = Environment(extensions: [ext])
let result = try env.renderTemplate(string: """
$0.it("can render filters with arguments") {
let ext = Extension()
ext.registerFilter("split", filter: {
return ($0 as! String).components(separatedBy: $1[0] as! String)
})
let env = Environment(extensions: [ext])
let result = try env.renderTemplate(string: """
{% filter split:","|join:";" %}{{ items|join:"," }}{% endfilter %}
""", context: ["items": [1, 2]])
try expect(result) == "1;2"
}
try expect(result) == "1;2"
}
$0.it("can render filters with quote as an argument") {
$0.it("can render filters with quote as an argument") {
let ext = Extension()
ext.registerFilter("replace", filter: {
print($1[0] as! String)
return ($0 as! String).replacingOccurrences(of: $1[0] as! String, with: $1[1] as! String)
print($1[0] as! String)
return ($0 as! String).replacingOccurrences(of: $1[0] as! String, with: $1[1] as! String)
})
let env = Environment(extensions: [ext])
let result = try env.renderTemplate(string: """
{% filter replace:'"',"" %}{{ items|join:"," }}{% endfilter %}
""", context: ["items": ["\"1\"", "\"2\""]])
try expect(result) == "1,2"
}
}
}
}

View File

@@ -1,351 +1,353 @@
import XCTest
import Spectre
@testable import Stencil
import Foundation
func testForNode() {
describe("ForNode") {
let context = Context(dictionary: [
"items": [1, 2, 3],
"emptyItems": [Int](),
"dict": [
"one": "I",
"two": "II",
],
"tuples": [(1, 2, 3), (4, 5, 6)]
])
$0.it("renders the given nodes for each item") {
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "123"
}
$0.it("renders the given empty nodes when no items found item") {
let nodes: [NodeType] = [VariableNode(variable: "item")]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes)
try expect(try node.render(context)) == "empty"
}
$0.it("renders a context variable of type Array<Any>") {
let any_context = Context(dictionary: [
"items": ([1, 2, 3] as [Any])
class ForNodeTests: XCTestCase {
func testForNode() {
describe("ForNode") {
let context = Context(dictionary: [
"items": [1, 2, 3],
"emptyItems": [Int](),
"dict": [
"one": "I",
"two": "II",
],
"tuples": [(1, 2, 3), (4, 5, 6)]
])
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(any_context)) == "123"
}
$0.it("renders the given nodes for each item") {
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "123"
}
$0.it("renders a context variable of type CountableClosedRange<Int>") {
let context = Context(dictionary: ["range": 1...3])
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
$0.it("renders the given empty nodes when no items found item") {
let nodes: [NodeType] = [VariableNode(variable: "item")]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes)
try expect(try node.render(context)) == "empty"
}
try expect(try node.render(context)) == "123"
}
$0.it("renders a context variable of type Array<Any>") {
let any_context = Context(dictionary: [
"items": ([1, 2, 3] as [Any])
])
$0.it("renders a context variable of type CountableRange<Int>") {
let context = Context(dictionary: ["range": 1..<4])
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(any_context)) == "123"
}
try expect(try node.render(context)) == "123"
}
$0.it("renders a context variable of type CountableClosedRange<Int>") {
let context = Context(dictionary: ["range": 1...3])
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
#if os(OSX)
$0.it("renders a context variable of type NSArray") {
let nsarray_context = Context(dictionary: [
"items": NSArray(array: [1, 2, 3])
])
try expect(try node.render(context)) == "123"
}
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(nsarray_context)) == "123"
}
#endif
$0.it("renders a context variable of type CountableRange<Int>") {
let context = Context(dictionary: ["range": 1..<4])
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
$0.it("renders the given nodes while providing if the item is first in the context") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.first")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "1true2false3false"
}
try expect(try node.render(context)) == "123"
}
$0.it("renders the given nodes while providing if the item is last in the context") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.last")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "1false2false3true"
}
#if os(OSX)
$0.it("renders a context variable of type NSArray") {
let nsarray_context = Context(dictionary: [
"items": NSArray(array: [1, 2, 3])
])
$0.it("renders the given nodes while providing item counter") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "112233"
}
let nodes: [NodeType] = [VariableNode(variable: "item")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(nsarray_context)) == "123"
}
#endif
$0.it("renders the given nodes while providing item counter") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter0")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "102132"
}
$0.it("renders the given nodes while providing if the item is first in the context") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.first")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "1true2false3false"
}
$0.it("renders the given nodes while providing loop length") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.length")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "132333"
}
$0.it("renders the given nodes while providing if the item is last in the context") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.last")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "1false2false3true"
}
$0.it("renders the given nodes while filtering items using where expression") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown))
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`)
try expect(try node.render(context)) == "2132"
}
$0.it("renders the given nodes while providing item counter") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "112233"
}
$0.it("renders the given empty nodes when all items filtered out with where expression") {
let nodes: [NodeType] = [VariableNode(variable: "item")]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown))
let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`)
try expect(try node.render(context)) == "empty"
}
$0.it("renders the given nodes while providing item counter") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter0")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "102132"
}
$0.it("can render a filter with spaces") {
let templateString = """
$0.it("renders the given nodes while providing loop length") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.length")]
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [])
try expect(try node.render(context)) == "132333"
}
$0.it("renders the given nodes while filtering items using where expression") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown))
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`)
try expect(try node.render(context)) == "2132"
}
$0.it("renders the given empty nodes when all items filtered out with where expression") {
let nodes: [NodeType] = [VariableNode(variable: "item")]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown))
let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`)
try expect(try node.render(context)) == "empty"
}
$0.it("can render a filter with spaces") {
let templateString = """
{% for article in ars | default: a, b , articles %}\
- {{ article.title }} by {{ article.author }}.
{% endfor %}
"""
let context = Context(dictionary: [
"articles": [
Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
Article(title: "Memory Management with ARC", author: "Kyle Fuller"),
]
])
let context = Context(dictionary: [
"articles": [
Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
Article(title: "Memory Management with ARC", author: "Kyle Fuller"),
]
])
let template = Template(templateString: templateString)
let result = try template.render(context)
let template = Template(templateString: templateString)
let result = try template.render(context)
try expect(result) == """
try expect(result) == """
- Migrating from OCUnit to XCTest by Kyle Fuller.
- Memory Management with ARC by Kyle Fuller.
"""
}
}
$0.context("given array of tuples") {
$0.it("can iterate over all tuple values") {
let templateString = """
$0.context("given array of tuples") {
$0.it("can iterate over all tuple values") {
let templateString = """
{% for first,second,third in tuples %}\
{{ first }}, {{ second }}, {{ third }}
{% endfor %}
"""
let template = Template(templateString: templateString)
let result = try template.render(context)
let template = Template(templateString: templateString)
let result = try template.render(context)
try expect(result) == """
try expect(result) == """
1, 2, 3
4, 5, 6
"""
}
}
$0.it("can iterate with less number of variables") {
let templateString = """
$0.it("can iterate with less number of variables") {
let templateString = """
{% for first,second in tuples %}\
{{ first }}, {{ second }}
{% endfor %}
"""
let template = Template(templateString: templateString)
let result = try template.render(context)
let template = Template(templateString: templateString)
let result = try template.render(context)
try expect(result) == """
try expect(result) == """
1, 2
4, 5
"""
}
}
$0.it("can use _ to skip variables") {
let templateString = """
$0.it("can use _ to skip variables") {
let templateString = """
{% for first,_,third in tuples %}\
{{ first }}, {{ third }}
{% endfor %}
"""
let template = Template(templateString: templateString)
let result = try template.render(context)
let template = Template(templateString: templateString)
let result = try template.render(context)
try expect(result) == """
try expect(result) == """
1, 3
4, 6
"""
}
}
$0.it("throws when number of variables is more than number of tuple values") {
let templateString = """
$0.it("throws when number of variables is more than number of tuple values") {
let templateString = """
{% for key,value,smth in dict %}
{% endfor %}
"""
let template = Template(templateString: templateString)
try expect(template.render(context)).toThrow()
let template = Template(templateString: templateString)
try expect(template.render(context)).toThrow()
}
}
}
$0.it("can iterate over dictionary") {
let templateString = """
$0.it("can iterate over dictionary") {
let templateString = """
{% for key, value in dict %}\
{{ key }}: {{ value }},\
{% endfor %}
"""
let template = Template(templateString: templateString)
let result = try template.render(context)
let template = Template(templateString: templateString)
let result = try template.render(context)
try expect(result) == """
try expect(result) == """
one: I,two: II,
"""
}
$0.it("renders supports iterating over dictionary") {
let nodes: [NodeType] = [
VariableNode(variable: "key"),
TextNode(text: ","),
]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil)
let result = try node.render(context)
try expect(result) == """
one,two,
"""
}
$0.it("renders supports iterating over dictionary") {
let nodes: [NodeType] = [
VariableNode(variable: "key"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: ","),
]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key", "value"], nodes: nodes, emptyNodes: emptyNodes, where: nil)
let result = try node.render(context)
try expect(result) == """
one=I,two=II,
"""
}
$0.it("handles invalid input") {
let token = Token.block(value: "for i", at: .unknown)
let parser = TokenParser(tokens: [token], environment: Environment())
let error = TemplateSyntaxError(reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.", token: token)
try expect(try parser.parse()).toThrow(error)
}
$0.it("can iterate over struct properties") {
struct MyStruct {
let string: String
let number: Int
}
let context = Context(dictionary: [
"struct": MyStruct(string: "abc", number: 123)
])
$0.it("renders supports iterating over dictionary") {
let nodes: [NodeType] = [
VariableNode(variable: "key"),
TextNode(text: ","),
]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil)
let result = try node.render(context)
let nodes: [NodeType] = [
VariableNode(variable: "property"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: "\n"),
]
let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context)
try expect(result) == """
one,two,
"""
}
try expect(result) == """
$0.it("renders supports iterating over dictionary") {
let nodes: [NodeType] = [
VariableNode(variable: "key"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: ","),
]
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key", "value"], nodes: nodes, emptyNodes: emptyNodes, where: nil)
let result = try node.render(context)
try expect(result) == """
one=I,two=II,
"""
}
$0.it("handles invalid input") {
let token = Token.block(value: "for i", at: .unknown)
let parser = TokenParser(tokens: [token], environment: Environment())
let error = TemplateSyntaxError(reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.", token: token)
try expect(try parser.parse()).toThrow(error)
}
$0.it("can iterate over struct properties") {
struct MyStruct {
let string: String
let number: Int
}
let context = Context(dictionary: [
"struct": MyStruct(string: "abc", number: 123)
])
let nodes: [NodeType] = [
VariableNode(variable: "property"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: "\n"),
]
let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context)
try expect(result) == """
string=abc
number=123
"""
}
}
$0.it("can iterate tuple items") {
let context = Context(dictionary: [
"tuple": (one: 1, two: "dva"),
])
$0.it("can iterate tuple items") {
let context = Context(dictionary: [
"tuple": (one: 1, two: "dva"),
])
let nodes: [NodeType] = [
VariableNode(variable: "label"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: "\n"),
]
let nodes: [NodeType] = [
VariableNode(variable: "label"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: "\n"),
]
let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context)
let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context)
try expect(result) == """
try expect(result) == """
one=1
two=dva
"""
}
$0.it("can iterate over class properties") {
class MyClass {
var baseString: String
var baseInt: Int
init(_ string: String, _ int: Int) {
baseString = string
baseInt = int
}
}
class MySubclass: MyClass {
var childString: String
init(_ childString: String, _ string: String, _ int: Int) {
self.childString = childString
super.init(string, int)
$0.it("can iterate over class properties") {
class MyClass {
var baseString: String
var baseInt: Int
init(_ string: String, _ int: Int) {
baseString = string
baseInt = int
}
}
}
let context = Context(dictionary: [
"class": MySubclass("child", "base", 1)
])
class MySubclass: MyClass {
var childString: String
init(_ childString: String, _ string: String, _ int: Int) {
self.childString = childString
super.init(string, int)
}
}
let nodes: [NodeType] = [
VariableNode(variable: "label"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: "\n"),
]
let context = Context(dictionary: [
"class": MySubclass("child", "base", 1)
])
let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context)
let nodes: [NodeType] = [
VariableNode(variable: "label"),
TextNode(text: "="),
VariableNode(variable: "value"),
TextNode(text: "\n"),
]
try expect(result) == """
let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context)
try expect(result) == """
childString=child
baseString=base
baseInt=1
"""
}
}
$0.it("can iterate in range of variables") {
let template: Template = "{% for i in 1...j %}{{ i }}{% endfor %}"
try expect(try template.render(Context(dictionary: ["j": 3]))) == "123"
}
$0.it("can iterate in range of variables") {
let template: Template = "{% for i in 1...j %}{{ i }}{% endfor %}"
try expect(try template.render(Context(dictionary: ["j": 3]))) == "123"
}
}
}
fileprivate struct Article {

View File

@@ -1,242 +1,243 @@
import XCTest
import Spectre
@testable import Stencil
class IfNodeTests: XCTestCase {
func testIfNode() {
describe("IfNode") {
$0.describe("parsing") {
$0.it("can parse an if block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
func testIfNode() {
describe("IfNode") {
$0.describe("parsing") {
$0.it("can parse an if block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 1
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
}
let conditions = node?.conditions
try expect(conditions?.count) == 1
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
$0.it("can parse an if with else block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 2
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let falseNode = conditions?[1].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with elif block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "elif something", at: .unknown),
.text(value: "some", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 3
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let elifNode = conditions?[1].nodes.first as? TextNode
try expect(elifNode?.text) == "some"
try expect(conditions?[2].nodes.count) == 1
let falseNode = conditions?[2].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with elif block without else") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "elif something", at: .unknown),
.text(value: "some", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 2
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let elifNode = conditions?[1].nodes.first as? TextNode
try expect(elifNode?.text) == "some"
}
$0.it("can parse an if with multiple elif block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "elif something1", at: .unknown),
.text(value: "some1", at: .unknown),
.block(value: "elif something2", at: .unknown),
.text(value: "some2", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 4
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let elifNode = conditions?[1].nodes.first as? TextNode
try expect(elifNode?.text) == "some1"
try expect(conditions?[2].nodes.count) == 1
let elif2Node = conditions?[2].nodes.first as? TextNode
try expect(elif2Node?.text) == "some2"
try expect(conditions?[3].nodes.count) == 1
let falseNode = conditions?[3].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with complex expression") {
let tokens: [Token] = [
.block(value: "if value == \"test\" and not name", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
try expect(nodes.first is IfNode).beTrue()
}
$0.it("can parse an ifnot block") {
let tokens: [Token] = [
.block(value: "ifnot value", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 2
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let falseNode = conditions?[1].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
}
$0.it("throws an error when parsing an if block without an endif") {
let tokens: [Token] = [.block(value: "if value", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first)
try expect(try parser.parse()).toThrow(error)
}
$0.it("throws an error when parsing an ifnot without an endif") {
let tokens: [Token] = [.block(value: "ifnot value", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first)
try expect(try parser.parse()).toThrow(error)
}
}
$0.it("can parse an if with else block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
$0.describe("rendering") {
$0.it("renders a true expression") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]),
])
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
try expect(try node.render(Context())) == "1"
}
let conditions = node?.conditions
try expect(conditions?.count) == 2
$0.it("renders the first true expression") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]),
])
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(try node.render(Context())) == "2"
}
try expect(conditions?[1].nodes.count) == 1
let falseNode = conditions?[1].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
$0.it("renders the empty expression when other conditions are falsy") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]),
])
try expect(try node.render(Context())) == "3"
}
$0.it("renders empty when no truthy conditions") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]),
])
try expect(try node.render(Context())) == ""
}
}
$0.it("can parse an if with elif block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "elif something", at: .unknown),
.text(value: "some", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 3
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let elifNode = conditions?[1].nodes.first as? TextNode
try expect(elifNode?.text) == "some"
try expect(conditions?[2].nodes.count) == 1
let falseNode = conditions?[2].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with elif block without else") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "elif something", at: .unknown),
.text(value: "some", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 2
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let elifNode = conditions?[1].nodes.first as? TextNode
try expect(elifNode?.text) == "some"
}
$0.it("can parse an if with multiple elif block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "elif something1", at: .unknown),
.text(value: "some1", at: .unknown),
.block(value: "elif something2", at: .unknown),
.text(value: "some2", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 4
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let elifNode = conditions?[1].nodes.first as? TextNode
try expect(elifNode?.text) == "some1"
try expect(conditions?[2].nodes.count) == 1
let elif2Node = conditions?[2].nodes.first as? TextNode
try expect(elif2Node?.text) == "some2"
try expect(conditions?[3].nodes.count) == 1
let falseNode = conditions?[3].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with complex expression") {
let tokens: [Token] = [
.block(value: "if value == \"test\" and not name", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
try expect(nodes.first is IfNode).beTrue()
}
$0.it("can parse an ifnot block") {
let tokens: [Token] = [
.block(value: "ifnot value", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IfNode
let conditions = node?.conditions
try expect(conditions?.count) == 2
try expect(conditions?[0].nodes.count) == 1
let trueNode = conditions?[0].nodes.first as? TextNode
try expect(trueNode?.text) == "true"
try expect(conditions?[1].nodes.count) == 1
let falseNode = conditions?[1].nodes.first as? TextNode
try expect(falseNode?.text) == "false"
}
$0.it("throws an error when parsing an if block without an endif") {
let tokens: [Token] = [.block(value: "if value", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first)
try expect(try parser.parse()).toThrow(error)
}
$0.it("throws an error when parsing an ifnot without an endif") {
let tokens: [Token] = [.block(value: "ifnot value", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first)
try expect(try parser.parse()).toThrow(error)
}
}
$0.describe("rendering") {
$0.it("renders a true expression") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]),
])
try expect(try node.render(Context())) == "1"
}
$0.it("renders the first true expression") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]),
])
try expect(try node.render(Context())) == "2"
}
$0.it("renders the empty expression when other conditions are falsy") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]),
])
try expect(try node.render(Context())) == "3"
}
$0.it("renders empty when no truthy conditions") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]),
])
try expect(try node.render(Context())) == ""
}
}
$0.it("supports variable filters in the if expression") {
$0.it("supports variable filters in the if expression") {
let tokens: [Token] = [
.block(value: "if value|uppercase == \"TEST\"", at: .unknown),
.text(value: "true", at: .unknown),
@@ -248,40 +249,41 @@ func testIfNode() {
let result = try renderNodes(nodes, Context(dictionary: ["value": "test"]))
try expect(result) == "true"
}
$0.it("evaluates nil properties as false") {
let tokens: [Token] = [
.block(value: "if instance.value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
struct SomeType {
let value: String? = nil
}
let result = try renderNodes(nodes, Context(dictionary: ["instance": SomeType()]))
try expect(result) == ""
$0.it("evaluates nil properties as false") {
let tokens: [Token] = [
.block(value: "if instance.value", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
struct SomeType {
let value: String? = nil
}
let result = try renderNodes(nodes, Context(dictionary: ["instance": SomeType()]))
try expect(result) == ""
}
$0.it("supports closed range variables") {
let tokens: [Token] = [
.block(value: "if value in 1...3", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
try expect(renderNodes(nodes, Context(dictionary: ["value": 3]))) == "true"
try expect(renderNodes(nodes, Context(dictionary: ["value": 4]))) == "false"
}
}
$0.it("supports closed range variables") {
let tokens: [Token] = [
.block(value: "if value in 1...3", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "else", at: .unknown),
.text(value: "false", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
try expect(renderNodes(nodes, Context(dictionary: ["value": 3]))) == "true"
try expect(renderNodes(nodes, Context(dictionary: ["value": 4]))) == "false"
}
}
}

View File

@@ -1,69 +1,71 @@
import XCTest
import Spectre
@testable import Stencil
import PathKit
class IncludeTests: XCTestCase {
func testInclude() {
describe("Include") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
let environment = Environment(loader: loader)
func testInclude() {
describe("Include") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
let environment = Environment(loader: loader)
$0.describe("parsing") {
$0.it("throws an error when no template is given") {
let tokens: [Token] = [ .block(value: "include", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
$0.describe("parsing") {
$0.it("throws an error when no template is given") {
let tokens: [Token] = [ .block(value: "include", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
let error = TemplateSyntaxError(reason: "'include' tag requires one argument, the template file to be included. A second optional argument can be used to specify the context that will be passed to the included file", token: tokens.first)
try expect(try parser.parse()).toThrow(error)
}
let error = TemplateSyntaxError(reason: "'include' tag requires one argument, the template file to be included. A second optional argument can be used to specify the context that will be passed to the included file", token: tokens.first)
try expect(try parser.parse()).toThrow(error)
}
$0.it("can parse a valid include block") {
let tokens: [Token] = [ .block(value: "include \"test.html\"", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
$0.it("can parse a valid include block") {
let tokens: [Token] = [ .block(value: "include \"test.html\"", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? IncludeNode
try expect(nodes.count) == 1
try expect(node?.templateName) == Variable("\"test.html\"")
}
}
$0.describe("rendering") {
$0.it("throws an error when rendering without a loader") {
let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown))
do {
_ = try node.render(Context())
} catch {
try expect("\(error)") == "Template named `test.html` does not exist. No loaders found"
let nodes = try parser.parse()
let node = nodes.first as? IncludeNode
try expect(nodes.count) == 1
try expect(node?.templateName) == Variable("\"test.html\"")
}
}
$0.it("throws an error when it cannot find the included template") {
let node = IncludeNode(templateName: Variable("\"unknown.html\""), token: .block(value: "", at: .unknown))
$0.describe("rendering") {
$0.it("throws an error when rendering without a loader") {
let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown))
do {
_ = try node.render(Context(environment: environment))
} catch {
try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue()
do {
_ = try node.render(Context())
} catch {
try expect("\(error)") == "Template named `test.html` does not exist. No loaders found"
}
}
}
$0.it("successfully renders a found included template") {
let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown))
let context = Context(dictionary: ["target": "World"], environment: environment)
let value = try node.render(context)
try expect(value) == "Hello World!"
}
$0.it("throws an error when it cannot find the included template") {
let node = IncludeNode(templateName: Variable("\"unknown.html\""), token: .block(value: "", at: .unknown))
$0.it("successfully passes context") {
let template = Template(templateString: """
do {
_ = try node.render(Context(environment: environment))
} catch {
try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue()
}
}
$0.it("successfully renders a found included template") {
let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown))
let context = Context(dictionary: ["target": "World"], environment: environment)
let value = try node.render(context)
try expect(value) == "Hello World!"
}
$0.it("successfully passes context") {
let template = Template(templateString: """
{% include "test.html" child %}
""")
let context = Context(dictionary: ["child": ["target": "World"]], environment: environment)
let value = try template.render(context)
try expect(value) == "Hello World!"
let context = Context(dictionary: ["child": ["target": "World"]], environment: environment)
let value = try template.render(context)
try expect(value) == "Hello World!"
}
}
}
}

View File

@@ -1,36 +1,38 @@
import XCTest
import Spectre
import Stencil
import PathKit
class InheritenceTests: XCTestCase {
func testInheritence() {
describe("Inheritence") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
let environment = Environment(loader: loader)
func testInheritence() {
describe("Inheritence") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
let environment = Environment(loader: loader)
$0.it("can inherit from another template") {
let template = try environment.loadTemplate(name: "child.html")
try expect(try template.render()) == """
$0.it("can inherit from another template") {
let template = try environment.loadTemplate(name: "child.html")
try expect(try template.render()) == """
Super_Header Child_Header
Child_Body
"""
}
}
$0.it("can inherit from another template inheriting from another template") {
let template = try environment.loadTemplate(name: "child-child.html")
try expect(try template.render()) == """
$0.it("can inherit from another template inheriting from another template") {
let template = try environment.loadTemplate(name: "child-child.html")
try expect(try template.render()) == """
Super_Header Child_Header Child_Child_Header
Child_Body
"""
}
}
$0.it("can inherit from a template that calls a super block") {
let template = try environment.loadTemplate(name: "child-super.html")
try expect(try template.render()) == """
$0.it("can inherit from a template that calls a super block") {
let template = try environment.loadTemplate(name: "child-super.html")
try expect(try template.render()) == """
Header
Child_Body
"""
}
}
}
}

View File

@@ -1,75 +1,76 @@
import XCTest
import Spectre
@testable import Stencil
class LexerTests: XCTestCase {
func testLexer() {
describe("Lexer") {
$0.it("can tokenize text") {
let lexer = Lexer(templateString: "Hello World")
let tokens = lexer.tokenize()
func testLexer() {
describe("Lexer") {
$0.it("can tokenize text") {
let lexer = Lexer(templateString: "Hello World")
let tokens = lexer.tokenize()
try expect(tokens.count) == 1
try expect(tokens.first) == .text(value: "Hello World", at: SourceMap(location: ("Hello World", 1, 0)))
}
try expect(tokens.count) == 1
try expect(tokens.first) == .text(value: "Hello World", at: SourceMap(location: ("Hello World", 1, 0)))
}
$0.it("can tokenize a comment") {
let lexer = Lexer(templateString: "{# Comment #}")
let tokens = lexer.tokenize()
$0.it("can tokenize a comment") {
let lexer = Lexer(templateString: "{# Comment #}")
let tokens = lexer.tokenize()
try expect(tokens.count) == 1
try expect(tokens.first) == .comment(value: "Comment", at: SourceMap(location: ("{# Comment #}", 1, 3)))
}
try expect(tokens.count) == 1
try expect(tokens.first) == .comment(value: "Comment", at: SourceMap(location: ("{# Comment #}", 1, 3)))
}
$0.it("can tokenize a variable") {
let lexer = Lexer(templateString: "{{ Variable }}")
let tokens = lexer.tokenize()
$0.it("can tokenize a variable") {
let lexer = Lexer(templateString: "{{ Variable }}")
let tokens = lexer.tokenize()
try expect(tokens.count) == 1
try expect(tokens.first) == .variable(value: "Variable", at: SourceMap(location: ("{{ Variable }}", 1, 3)))
}
try expect(tokens.count) == 1
try expect(tokens.first) == .variable(value: "Variable", at: SourceMap(location: ("{{ Variable }}", 1, 3)))
}
$0.it("can tokenize unclosed tag by ignoring it") {
let templateString = "{{ thing"
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
$0.it("can tokenize unclosed tag by ignoring it") {
let templateString = "{{ thing"
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
try expect(tokens.count) == 1
try expect(tokens.first) == .text(value: "", at: SourceMap(location: ("{{ thing", 1, 0)))
}
try expect(tokens.count) == 1
try expect(tokens.first) == .text(value: "", at: SourceMap(location: ("{{ thing", 1, 0)))
}
$0.it("can tokenize a mixture of content") {
let templateString = "My name is {{ myname }}."
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
$0.it("can tokenize a mixture of content") {
let templateString = "My name is {{ myname }}."
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
try expect(tokens.count) == 3
try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is ")!)))
try expect(tokens[1]) == Token.variable(value: "myname", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "myname")!)))
try expect(tokens[2]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!)))
}
try expect(tokens.count) == 3
try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is ")!)))
try expect(tokens[1]) == Token.variable(value: "myname", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "myname")!)))
try expect(tokens[2]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!)))
}
$0.it("can tokenize two variables without being greedy") {
let templateString = "{{ thing }}{{ name }}"
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
$0.it("can tokenize two variables without being greedy") {
let templateString = "{{ thing }}{{ name }}"
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
try expect(tokens.count) == 2
try expect(tokens[0]) == Token.variable(value: "thing", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "thing")!)))
try expect(tokens[1]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name")!)))
}
try expect(tokens.count) == 2
try expect(tokens[0]) == Token.variable(value: "thing", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "thing")!)))
try expect(tokens[1]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name")!)))
}
$0.it("can tokenize an unclosed block") {
let lexer = Lexer(templateString: "{%}")
let _ = lexer.tokenize()
}
$0.it("can tokenize an unclosed block") {
let lexer = Lexer(templateString: "{%}")
let _ = lexer.tokenize()
}
$0.it("can tokenize an empty variable") {
let lexer = Lexer(templateString: "{{}}")
let _ = lexer.tokenize()
}
$0.it("can tokenize an empty variable") {
let lexer = Lexer(templateString: "{{}}")
let _ = lexer.tokenize()
}
$0.it("can tokenize with new lines") {
let templateString = """
$0.it("can tokenize with new lines") {
let templateString = """
My name is {%
if name
and
@@ -80,16 +81,17 @@ func testLexer() {
endif %}.
"""
let lexer = Lexer(templateString: templateString)
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
let tokens = lexer.tokenize()
try expect(tokens.count) == 5
try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is")!)))
try expect(tokens[1]) == Token.block(value: "if name and name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "{%")!)))
try expect(tokens[2]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name", options: [.backwards])!)))
try expect(tokens[3]) == Token.block(value: "endif", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "endif")!)))
try expect(tokens[4]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!)))
try expect(tokens.count) == 5
try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is")!)))
try expect(tokens[1]) == Token.block(value: "if name and name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "{%")!)))
try expect(tokens[2]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name", options: [.backwards])!)))
try expect(tokens[3]) == Token.block(value: "endif", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "endif")!)))
try expect(tokens[4]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!)))
}
}
}
}

View File

@@ -1,55 +1,57 @@
import XCTest
import Spectre
import Stencil
import PathKit
class TemplateLoaderTests: XCTestCase {
func testTemplateLoader() {
describe("FileSystemLoader") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
let environment = Environment(loader: loader)
func testTemplateLoader() {
describe("FileSystemLoader") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
let environment = Environment(loader: loader)
$0.it("errors when a template cannot be found") {
try expect(try environment.loadTemplate(name: "unknown.html")).toThrow()
}
$0.it("errors when a template cannot be found") {
try expect(try environment.loadTemplate(name: "unknown.html")).toThrow()
$0.it("errors when an array of templates cannot be found") {
try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
}
$0.it("can load a template from a file") {
_ = try environment.loadTemplate(name: "test.html")
}
$0.it("errors when loading absolute file outside of the selected path") {
try expect(try environment.loadTemplate(name: "/etc/hosts")).toThrow()
}
$0.it("errors when loading relative file outside of the selected path") {
try expect(try environment.loadTemplate(name: "../LoaderSpec.swift")).toThrow()
}
}
$0.it("errors when an array of templates cannot be found") {
try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
}
$0.it("can load a template from a file") {
_ = try environment.loadTemplate(name: "test.html")
}
$0.it("errors when loading absolute file outside of the selected path") {
try expect(try environment.loadTemplate(name: "/etc/hosts")).toThrow()
}
$0.it("errors when loading relative file outside of the selected path") {
try expect(try environment.loadTemplate(name: "../LoaderSpec.swift")).toThrow()
}
}
describe("DictionaryLoader") {
let loader = DictionaryLoader(templates: [
describe("DictionaryLoader") {
let loader = DictionaryLoader(templates: [
"index.html": "Hello World"
])
let environment = Environment(loader: loader)
])
let environment = Environment(loader: loader)
$0.it("errors when a template cannot be found") {
try expect(try environment.loadTemplate(name: "unknown.html")).toThrow()
}
$0.it("errors when a template cannot be found") {
try expect(try environment.loadTemplate(name: "unknown.html")).toThrow()
}
$0.it("errors when an array of templates cannot be found") {
try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
}
$0.it("errors when an array of templates cannot be found") {
try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
}
$0.it("can load a template from a known templates") {
_ = try environment.loadTemplate(name: "index.html")
}
$0.it("can load a template from a known templates") {
_ = try environment.loadTemplate(name: "index.html")
}
$0.it("can load a known template from a collection of templates") {
_ = try environment.loadTemplate(names: ["unknown.html", "index.html"])
$0.it("can load a known template from a collection of templates") {
_ = try environment.loadTemplate(names: ["unknown.html", "index.html"])
}
}
}
}

View File

@@ -1,7 +1,7 @@
import XCTest
import Spectre
@testable import Stencil
class ErrorNode : NodeType {
let token: Token?
init(token: Token? = nil) {
@@ -13,52 +13,53 @@ class ErrorNode : NodeType {
}
}
class NodeTests: XCTestCase {
func testNode() {
describe("Node") {
let context = Context(dictionary: [
"name": "Kyle",
"age": 27,
"items": [1, 2, 3],
])
func testNode() {
describe("Node") {
let context = Context(dictionary: [
"name": "Kyle",
"age": 27,
"items": [1, 2, 3],
])
$0.describe("TextNode") {
$0.it("renders the given text") {
let node = TextNode(text: "Hello World")
try expect(try node.render(context)) == "Hello World"
}
}
$0.describe("VariableNode") {
$0.it("resolves and renders the variable") {
let node = VariableNode(variable: Variable("name"))
try expect(try node.render(context)) == "Kyle"
$0.describe("TextNode") {
$0.it("renders the given text") {
let node = TextNode(text: "Hello World")
try expect(try node.render(context)) == "Hello World"
}
}
$0.it("resolves and renders a non string variable") {
let node = VariableNode(variable: Variable("age"))
try expect(try node.render(context)) == "27"
}
}
$0.describe("VariableNode") {
$0.it("resolves and renders the variable") {
let node = VariableNode(variable: Variable("name"))
try expect(try node.render(context)) == "Kyle"
}
$0.describe("rendering nodes") {
$0.it("renders the nodes") {
let nodes: [NodeType] = [
TextNode(text:"Hello "),
VariableNode(variable: "name"),
]
try expect(try renderNodes(nodes, context)) == "Hello Kyle"
$0.it("resolves and renders a non string variable") {
let node = VariableNode(variable: Variable("age"))
try expect(try node.render(context)) == "27"
}
}
$0.it("correctly throws a nodes failure") {
let nodes: [NodeType] = [
TextNode(text:"Hello "),
VariableNode(variable: "name"),
ErrorNode(),
]
$0.describe("rendering nodes") {
$0.it("renders the nodes") {
let nodes: [NodeType] = [
TextNode(text:"Hello "),
VariableNode(variable: "name"),
]
try expect(try renderNodes(nodes, context)).toThrow(TemplateSyntaxError("Custom Error"))
try expect(try renderNodes(nodes, context)) == "Hello Kyle"
}
$0.it("correctly throws a nodes failure") {
let nodes: [NodeType] = [
TextNode(text:"Hello "),
VariableNode(variable: "name"),
ErrorNode(),
]
try expect(try renderNodes(nodes, context)).toThrow(TemplateSyntaxError("Custom Error"))
}
}
}
}

View File

@@ -1,43 +1,46 @@
import XCTest
import Foundation
import Spectre
@testable import Stencil
func testNowNode() {
#if !os(Linux)
describe("NowNode") {
$0.describe("parsing") {
$0.it("parses default format without any now arguments") {
let tokens: [Token] = [ .block(value: "now", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
class NowNodeTests: XCTestCase {
func testNowNode() {
#if !os(Linux)
describe("NowNode") {
$0.describe("parsing") {
$0.it("parses default format without any now arguments") {
let tokens: [Token] = [ .block(value: "now", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? NowNode
try expect(nodes.count) == 1
try expect(node?.format.variable) == "\"yyyy-MM-dd 'at' HH:mm\""
let nodes = try parser.parse()
let node = nodes.first as? NowNode
try expect(nodes.count) == 1
try expect(node?.format.variable) == "\"yyyy-MM-dd 'at' HH:mm\""
}
$0.it("parses now with a format") {
let tokens: [Token] = [ .block(value: "now \"HH:mm\"", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? NowNode
try expect(nodes.count) == 1
try expect(node?.format.variable) == "\"HH:mm\""
}
}
$0.it("parses now with a format") {
let tokens: [Token] = [ .block(value: "now \"HH:mm\"", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? NowNode
try expect(nodes.count) == 1
try expect(node?.format.variable) == "\"HH:mm\""
}
}
$0.describe("rendering") {
$0.it("renders the date") {
let node = NowNode(format: Variable("\"yyyy-MM-dd\""))
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.string(from: NSDate() as Date)
try expect(try node.render(Context())) == date
$0.describe("rendering") {
$0.it("renders the date") {
let node = NowNode(format: Variable("\"yyyy-MM-dd\""))
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.string(from: NSDate() as Date)
try expect(try node.render(Context())) == date
}
}
}
#endif
}
#endif
}

View File

@@ -1,61 +1,63 @@
import XCTest
import Spectre
@testable import Stencil
class TokenParserTests: XCTestCase {
func testTokenParser() {
describe("TokenParser") {
$0.it("can parse a text token") {
let parser = TokenParser(tokens: [
.text(value: "Hello World", at: .unknown)
], environment: Environment())
func testTokenParser() {
describe("TokenParser") {
$0.it("can parse a text token") {
let parser = TokenParser(tokens: [
.text(value: "Hello World", at: .unknown)
], environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? TextNode
let nodes = try parser.parse()
let node = nodes.first as? TextNode
try expect(nodes.count) == 1
try expect(node?.text) == "Hello World"
}
$0.it("can parse a variable token") {
let parser = TokenParser(tokens: [
.variable(value: "'name'", at: .unknown)
], environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? VariableNode
try expect(nodes.count) == 1
let result = try node?.render(Context())
try expect(result) == "name"
}
$0.it("can parse a comment token") {
let parser = TokenParser(tokens: [
.comment(value: "Secret stuff!", at: .unknown)
], environment: Environment())
let nodes = try parser.parse()
try expect(nodes.count) == 0
}
$0.it("can parse a tag token") {
let simpleExtension = Extension()
simpleExtension.registerSimpleTag("known") { _ in
return ""
try expect(nodes.count) == 1
try expect(node?.text) == "Hello World"
}
let parser = TokenParser(tokens: [
.block(value: "known", at: .unknown),
], environment: Environment(extensions: [simpleExtension]))
$0.it("can parse a variable token") {
let parser = TokenParser(tokens: [
.variable(value: "'name'", at: .unknown)
], environment: Environment())
let nodes = try parser.parse()
try expect(nodes.count) == 1
}
let nodes = try parser.parse()
let node = nodes.first as? VariableNode
try expect(nodes.count) == 1
let result = try node?.render(Context())
try expect(result) == "name"
}
$0.it("errors when parsing an unknown tag") {
let tokens: [Token] = [.block(value: "unknown", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
$0.it("can parse a comment token") {
let parser = TokenParser(tokens: [
.comment(value: "Secret stuff!", at: .unknown)
], environment: Environment())
try expect(try parser.parse()).toThrow(TemplateSyntaxError(reason: "Unknown template tag 'unknown'", token: tokens.first))
let nodes = try parser.parse()
try expect(nodes.count) == 0
}
$0.it("can parse a tag token") {
let simpleExtension = Extension()
simpleExtension.registerSimpleTag("known") { _ in
return ""
}
let parser = TokenParser(tokens: [
.block(value: "known", at: .unknown),
], environment: Environment(extensions: [simpleExtension]))
let nodes = try parser.parse()
try expect(nodes.count) == 1
}
$0.it("errors when parsing an unknown tag") {
let tokens: [Token] = [.block(value: "unknown", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
try expect(try parser.parse()).toThrow(TemplateSyntaxError(reason: "Unknown template tag 'unknown'", token: tokens.first))
}
}
}
}

View File

@@ -1,7 +1,7 @@
import XCTest
import Spectre
import Stencil
fileprivate struct CustomNode : NodeType {
let token: Token?
func render(_ context:Context) throws -> String {
@@ -9,64 +9,64 @@ fileprivate struct CustomNode : NodeType {
}
}
fileprivate struct Article {
let title: String
let author: String
}
class StencilTests: XCTestCase {
func testStencil() {
describe("Stencil") {
let exampleExtension = Extension()
func testStencil() {
describe("Stencil") {
let exampleExtension = Extension()
exampleExtension.registerSimpleTag("simpletag") { context in
return "Hello World"
}
exampleExtension.registerSimpleTag("simpletag") { context in
return "Hello World"
}
exampleExtension.registerTag("customtag") { parser, token in
return CustomNode(token: token)
}
exampleExtension.registerTag("customtag") { parser, token in
return CustomNode(token: token)
}
let environment = Environment(extensions: [exampleExtension])
let environment = Environment(extensions: [exampleExtension])
$0.it("can render the README example") {
$0.it("can render the README example") {
let templateString = """
There are {{ articles.count }} articles.
let templateString = """
There are {{ articles.count }} articles.
{% for article in articles %}\
- {{ article.title }} by {{ article.author }}.
{% endfor %}
"""
{% for article in articles %}\
- {{ article.title }} by {{ article.author }}.
{% endfor %}
"""
let context = [
"articles": [
Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
Article(title: "Memory Management with ARC", author: "Kyle Fuller"),
let context = [
"articles": [
Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
Article(title: "Memory Management with ARC", author: "Kyle Fuller"),
]
]
]
let template = Template(templateString: templateString)
let result = try template.render(context)
let template = Template(templateString: templateString)
let result = try template.render(context)
try expect(result) == """
There are 2 articles.
try expect(result) == """
There are 2 articles.
- Migrating from OCUnit to XCTest by Kyle Fuller.
- Memory Management with ARC by Kyle Fuller.
- Migrating from OCUnit to XCTest by Kyle Fuller.
- Memory Management with ARC by Kyle Fuller.
"""
}
"""
}
$0.it("can render a custom template tag") {
let result = try environment.renderTemplate(string: "{% customtag %}")
try expect(result) == "Hello World"
}
$0.it("can render a custom template tag") {
let result = try environment.renderTemplate(string: "{% customtag %}")
try expect(result) == "Hello World"
}
$0.it("can render a simple custom tag") {
let result = try environment.renderTemplate(string: "{% simpletag %}")
try expect(result) == "Hello World"
$0.it("can render a simple custom tag") {
let result = try environment.renderTemplate(string: "{% simpletag %}")
try expect(result) == "Hello World"
}
}
}
}

View File

@@ -1,20 +1,22 @@
import XCTest
import Spectre
@testable import Stencil
class TemplateTests: XCTestCase {
func testTemplate() {
describe("Template") {
$0.it("can render a template from a string") {
let template = Template(templateString: "Hello World")
let result = try template.render([ "name": "Kyle" ])
try expect(result) == "Hello World"
}
func testTemplate() {
describe("Template") {
$0.it("can render a template from a string") {
let template = Template(templateString: "Hello World")
let result = try template.render([ "name": "Kyle" ])
try expect(result) == "Hello World"
}
$0.it("can render a template from a string literal") {
$0.it("can render a template from a string literal") {
let template: Template = "Hello World"
let result = try template.render([ "name": "Kyle" ])
try expect(result) == "Hello World"
}
}
}
}
}

View File

@@ -1,34 +1,36 @@
import XCTest
import Spectre
@testable import Stencil
class TokenTests: XCTestCase {
func testToken() {
describe("Token") {
$0.it("can split the contents into components") {
let token = Token.text(value: "hello world", at: .unknown)
let components = token.components()
func testToken() {
describe("Token") {
$0.it("can split the contents into components") {
let token = Token.text(value: "hello world", at: .unknown)
let components = token.components()
try expect(components.count) == 2
try expect(components[0]) == "hello"
try expect(components[1]) == "world"
}
try expect(components.count) == 2
try expect(components[0]) == "hello"
try expect(components[1]) == "world"
}
$0.it("can split the contents into components with single quoted strings") {
let token = Token.text(value: "hello 'kyle fuller'", at: .unknown)
let components = token.components()
$0.it("can split the contents into components with single quoted strings") {
let token = Token.text(value: "hello 'kyle fuller'", at: .unknown)
let components = token.components()
try expect(components.count) == 2
try expect(components[0]) == "hello"
try expect(components[1]) == "'kyle fuller'"
}
try expect(components.count) == 2
try expect(components[0]) == "hello"
try expect(components[1]) == "'kyle fuller'"
}
$0.it("can split the contents into components with double quoted strings") {
let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown)
let components = token.components()
$0.it("can split the contents into components with double quoted strings") {
let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown)
let components = token.components()
try expect(components.count) == 2
try expect(components[0]) == "hello"
try expect(components[1]) == "\"kyle fuller\""
try expect(components.count) == 2
try expect(components[0]) == "hello"
try expect(components[1]) == "\"kyle fuller\""
}
}
}
}

View File

@@ -1,3 +1,4 @@
import XCTest
import Foundation
import Spectre
@testable import Stencil
@@ -29,381 +30,383 @@ fileprivate class Blog: WebSite {
let featuring: Article? = Article(author: Person(name: "Jhon"))
}
func testVariable() {
describe("Variable") {
let context = Context(dictionary: [
"name": "Kyle",
"contacts": ["Katie", "Carlton"],
"profiles": [
"github": "kylef",
],
"counter": [
"count": "kylef",
class VariableTests: XCTestCase {
func testVariable() {
describe("Variable") {
let context = Context(dictionary: [
"name": "Kyle",
"contacts": ["Katie", "Carlton"],
"profiles": [
"github": "kylef",
],
"article": Article(author: Person(name: "Kyle")),
"tuple": (one: 1, two: 2)
])
"counter": [
"count": "kylef",
],
"article": Article(author: Person(name: "Kyle")),
"tuple": (one: 1, two: 2)
])
#if os(OSX)
context["object"] = Object()
#endif
context["blog"] = Blog()
#if os(OSX)
context["object"] = Object()
#endif
context["blog"] = Blog()
$0.it("can resolve a string literal with double quotes") {
let variable = Variable("\"name\"")
let result = try variable.resolve(context) as? String
try expect(result) == "name"
}
$0.it("can resolve a string literal with single quotes") {
let variable = Variable("'name'")
let result = try variable.resolve(context) as? String
try expect(result) == "name"
}
$0.it("can resolve an integer literal") {
let variable = Variable("5")
let result = try variable.resolve(context) as? Int
try expect(result) == 5
}
$0.it("can resolve an float literal") {
let variable = Variable("3.14")
let result = try variable.resolve(context) as? Number
try expect(result) == 3.14
}
$0.it("can resolve boolean literal") {
try expect(Variable("true").resolve(context) as? Bool) == true
try expect(Variable("false").resolve(context) as? Bool) == false
try expect(Variable("0").resolve(context) as? Int) == 0
try expect(Variable("1").resolve(context) as? Int) == 1
}
$0.it("can resolve a string variable") {
let variable = Variable("name")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
$0.context("given string") {
$0.it("can resolve an item via it's index") {
let variable = Variable("name.0")
let result = try variable.resolve(context) as? Character
try expect(result) == "K"
let variable1 = Variable("name.1")
let result1 = try variable1.resolve(context) as? Character
try expect(result1) == "y"
$0.it("can resolve a string literal with double quotes") {
let variable = Variable("\"name\"")
let result = try variable.resolve(context) as? String
try expect(result) == "name"
}
$0.it("can resolve an item via unknown index") {
let variable = Variable("name.5")
let result = try variable.resolve(context) as? Character
try expect(result).to.beNil()
let variable1 = Variable("name.-5")
let result1 = try variable1.resolve(context) as? Character
try expect(result1).to.beNil()
$0.it("can resolve a string literal with single quotes") {
let variable = Variable("'name'")
let result = try variable.resolve(context) as? String
try expect(result) == "name"
}
$0.it("can resolve the first item") {
let variable = Variable("name.first")
let result = try variable.resolve(context) as? Character
try expect(result) == "K"
}
$0.it("can resolve the last item") {
let variable = Variable("name.last")
let result = try variable.resolve(context) as? Character
try expect(result) == "e"
}
$0.it("can get the characters count") {
let variable = Variable("name.count")
$0.it("can resolve an integer literal") {
let variable = Variable("5")
let result = try variable.resolve(context) as? Int
try expect(result) == 4
try expect(result) == 5
}
}
$0.context("given dictionary") {
$0.it("can resolve an item") {
let variable = Variable("profiles.github")
$0.it("can resolve an float literal") {
let variable = Variable("3.14")
let result = try variable.resolve(context) as? Number
try expect(result) == 3.14
}
$0.it("can resolve boolean literal") {
try expect(Variable("true").resolve(context) as? Bool) == true
try expect(Variable("false").resolve(context) as? Bool) == false
try expect(Variable("0").resolve(context) as? Int) == 0
try expect(Variable("1").resolve(context) as? Int) == 1
}
$0.it("can resolve a string variable") {
let variable = Variable("name")
let result = try variable.resolve(context) as? String
try expect(result) == "kylef"
try expect(result) == "Kyle"
}
$0.it("can get the count") {
let variable = Variable("profiles.count")
let result = try variable.resolve(context) as? Int
try expect(result) == 1
}
}
$0.context("given string") {
$0.it("can resolve an item via it's index") {
let variable = Variable("name.0")
let result = try variable.resolve(context) as? Character
try expect(result) == "K"
$0.context("given array") {
$0.it("can resolve an item via it's index") {
let variable = Variable("contacts.0")
let result = try variable.resolve(context) as? String
try expect(result) == "Katie"
let variable1 = Variable("name.1")
let result1 = try variable1.resolve(context) as? Character
try expect(result1) == "y"
}
let variable1 = Variable("contacts.1")
let result1 = try variable1.resolve(context) as? String
try expect(result1) == "Carlton"
}
$0.it("can resolve an item via unknown index") {
let variable = Variable("name.5")
let result = try variable.resolve(context) as? Character
try expect(result).to.beNil()
$0.it("can resolve an item via unknown index") {
let variable = Variable("contacts.5")
let result = try variable.resolve(context) as? String
try expect(result).to.beNil()
let variable1 = Variable("name.-5")
let result1 = try variable1.resolve(context) as? Character
try expect(result1).to.beNil()
}
let variable1 = Variable("contacts.-5")
let result1 = try variable1.resolve(context) as? String
try expect(result1).to.beNil()
}
$0.it("can resolve the first item") {
let variable = Variable("name.first")
let result = try variable.resolve(context) as? Character
try expect(result) == "K"
}
$0.it("can resolve the first item") {
let variable = Variable("contacts.first")
let result = try variable.resolve(context) as? String
try expect(result) == "Katie"
}
$0.it("can resolve the last item") {
let variable = Variable("name.last")
let result = try variable.resolve(context) as? Character
try expect(result) == "e"
}
$0.it("can resolve the last item") {
let variable = Variable("contacts.last")
let result = try variable.resolve(context) as? String
try expect(result) == "Carlton"
}
$0.it("can get the count") {
let variable = Variable("contacts.count")
let result = try variable.resolve(context) as? Int
try expect(result) == 2
}
}
$0.it("can resolve a property with reflection") {
let variable = Variable("article.author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
#if os(OSX)
$0.it("can resolve a value via KVO") {
let variable = Variable("object.title")
let result = try variable.resolve(context) as? String
try expect(result) == "Hello World"
}
$0.it("can resolve a superclass value via KVO") {
let variable = Variable("object.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Foo"
}
$0.it("does not crash on KVO") {
let variable = Variable("object.fullname")
let result = try variable.resolve(context) as? String
try expect(result).to.beNil()
}
#endif
$0.it("can resolve a value via reflection") {
let variable = Variable("blog.articles.0.author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
$0.it("can resolve a superclass value via reflection") {
let variable = Variable("blog.url")
let result = try variable.resolve(context) as? String
try expect(result) == "blog.com"
}
$0.it("can resolve optional variable property using reflection") {
let variable = Variable("blog.featuring.author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Jhon"
}
$0.it("does not render Optional") {
var array: [Any?] = [1, nil]
array.append(array)
let context = Context(dictionary: ["values": array])
try expect(VariableNode(variable: "values").render(context)) == "[1, nil, [1, nil]]"
try expect(VariableNode(variable: "values.1").render(context)) == ""
}
$0.it("can subscript tuple by index") {
let variable = Variable("tuple.0")
let result = try variable.resolve(context) as? Int
try expect(result) == 1
}
$0.it("can subscript tuple by label") {
let variable = Variable("tuple.two")
let result = try variable.resolve(context) as? Int
try expect(result) == 2
}
$0.describe("Subscripting") {
$0.it("can resolve a property subscript via reflection") {
try context.push(dictionary: ["property": "name"]) {
let variable = Variable("article.author[property]")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
$0.it("can get the characters count") {
let variable = Variable("name.count")
let result = try variable.resolve(context) as? Int
try expect(result) == 4
}
}
$0.it("can subscript an array with a valid index") {
try context.push(dictionary: ["property": 0]) {
let variable = Variable("contacts[property]")
$0.context("given dictionary") {
$0.it("can resolve an item") {
let variable = Variable("profiles.github")
let result = try variable.resolve(context) as? String
try expect(result) == "kylef"
}
$0.it("can get the count") {
let variable = Variable("profiles.count")
let result = try variable.resolve(context) as? Int
try expect(result) == 1
}
}
$0.context("given array") {
$0.it("can resolve an item via it's index") {
let variable = Variable("contacts.0")
let result = try variable.resolve(context) as? String
try expect(result) == "Katie"
let variable1 = Variable("contacts.1")
let result1 = try variable1.resolve(context) as? String
try expect(result1) == "Carlton"
}
$0.it("can resolve an item via unknown index") {
let variable = Variable("contacts.5")
let result = try variable.resolve(context) as? String
try expect(result).to.beNil()
let variable1 = Variable("contacts.-5")
let result1 = try variable1.resolve(context) as? String
try expect(result1).to.beNil()
}
$0.it("can resolve the first item") {
let variable = Variable("contacts.first")
let result = try variable.resolve(context) as? String
try expect(result) == "Katie"
}
}
$0.it("can subscript an array with an unknown index") {
try context.push(dictionary: ["property": 5]) {
let variable = Variable("contacts[property]")
$0.it("can resolve the last item") {
let variable = Variable("contacts.last")
let result = try variable.resolve(context) as? String
try expect(result).to.beNil()
try expect(result) == "Carlton"
}
$0.it("can get the count") {
let variable = Variable("contacts.count")
let result = try variable.resolve(context) as? Int
try expect(result) == 2
}
}
#if os(OSX)
$0.it("can resolve a subscript via KVO") {
try context.push(dictionary: ["property": "name"]) {
let variable = Variable("object[property]")
let result = try variable.resolve(context) as? String
try expect(result) == "Foo"
}
}
#endif
$0.it("can resolve an optional subscript via reflection") {
try context.push(dictionary: ["property": "featuring"]) {
let variable = Variable("blog[property].author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Jhon"
}
$0.it("can resolve a property with reflection") {
let variable = Variable("article.author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
$0.it("can resolve multiple subscripts") {
try context.push(dictionary: [
"prop1": "articles",
"prop2": 0,
"prop3": "name"
]) {
let variable = Variable("blog[prop1][prop2].author[prop3]")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
#if os(OSX)
$0.it("can resolve a value via KVO") {
let variable = Variable("object.title")
let result = try variable.resolve(context) as? String
try expect(result) == "Hello World"
}
$0.it("can resolve nested subscripts") {
try context.push(dictionary: [
"prop1": "prop2",
"ref": ["prop2": "name"]
]) {
let variable = Variable("article.author[ref[prop1]]")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
$0.it("can resolve a superclass value via KVO") {
let variable = Variable("object.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Foo"
}
$0.it("throws for invalid keypath syntax") {
try context.push(dictionary: ["prop": "name"]) {
let samples = [
".",
"..",
".test",
"test..test",
"[prop]",
"article.author[prop",
"article.author[[prop]",
"article.author[prop]]",
"article.author[]",
"article.author[[]]",
"article.author[prop][]",
"article.author[prop]comments",
"article.author[.]"
]
$0.it("does not crash on KVO") {
let variable = Variable("object.fullname")
let result = try variable.resolve(context) as? String
try expect(result).to.beNil()
}
#endif
for lookup in samples {
let variable = Variable(lookup)
try expect(variable.resolve(context)).toThrow()
$0.it("can resolve a value via reflection") {
let variable = Variable("blog.articles.0.author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
$0.it("can resolve a superclass value via reflection") {
let variable = Variable("blog.url")
let result = try variable.resolve(context) as? String
try expect(result) == "blog.com"
}
$0.it("can resolve optional variable property using reflection") {
let variable = Variable("blog.featuring.author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Jhon"
}
$0.it("does not render Optional") {
var array: [Any?] = [1, nil]
array.append(array)
let context = Context(dictionary: ["values": array])
try expect(VariableNode(variable: "values").render(context)) == "[1, nil, [1, nil]]"
try expect(VariableNode(variable: "values.1").render(context)) == ""
}
$0.it("can subscript tuple by index") {
let variable = Variable("tuple.0")
let result = try variable.resolve(context) as? Int
try expect(result) == 1
}
$0.it("can subscript tuple by label") {
let variable = Variable("tuple.two")
let result = try variable.resolve(context) as? Int
try expect(result) == 2
}
$0.describe("Subscripting") {
$0.it("can resolve a property subscript via reflection") {
try context.push(dictionary: ["property": "name"]) {
let variable = Variable("article.author[property]")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
}
$0.it("can subscript an array with a valid index") {
try context.push(dictionary: ["property": 0]) {
let variable = Variable("contacts[property]")
let result = try variable.resolve(context) as? String
try expect(result) == "Katie"
}
}
$0.it("can subscript an array with an unknown index") {
try context.push(dictionary: ["property": 5]) {
let variable = Variable("contacts[property]")
let result = try variable.resolve(context) as? String
try expect(result).to.beNil()
}
}
#if os(OSX)
$0.it("can resolve a subscript via KVO") {
try context.push(dictionary: ["property": "name"]) {
let variable = Variable("object[property]")
let result = try variable.resolve(context) as? String
try expect(result) == "Foo"
}
}
#endif
$0.it("can resolve an optional subscript via reflection") {
try context.push(dictionary: ["property": "featuring"]) {
let variable = Variable("blog[property].author.name")
let result = try variable.resolve(context) as? String
try expect(result) == "Jhon"
}
}
$0.it("can resolve multiple subscripts") {
try context.push(dictionary: [
"prop1": "articles",
"prop2": 0,
"prop3": "name"
]) {
let variable = Variable("blog[prop1][prop2].author[prop3]")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
}
$0.it("can resolve nested subscripts") {
try context.push(dictionary: [
"prop1": "prop2",
"ref": ["prop2": "name"]
]) {
let variable = Variable("article.author[ref[prop1]]")
let result = try variable.resolve(context) as? String
try expect(result) == "Kyle"
}
}
$0.it("throws for invalid keypath syntax") {
try context.push(dictionary: ["prop": "name"]) {
let samples = [
".",
"..",
".test",
"test..test",
"[prop]",
"article.author[prop",
"article.author[[prop]",
"article.author[prop]]",
"article.author[]",
"article.author[[]]",
"article.author[prop][]",
"article.author[prop]comments",
"article.author[.]"
]
for lookup in samples {
let variable = Variable(lookup)
try expect(variable.resolve(context)).toThrow()
}
}
}
}
}
}
describe("RangeVariable") {
describe("RangeVariable") {
let context: Context = {
let ext = Extension()
ext.registerFilter("incr", filter: { (arg: Any?) in toNumber(value: arg!)! + 1 })
let environment = Environment(extensions: [ext])
return Context(dictionary: [:], environment: environment)
}()
let context: Context = {
let ext = Extension()
ext.registerFilter("incr", filter: { (arg: Any?) in toNumber(value: arg!)! + 1 })
let environment = Environment(extensions: [ext])
return Context(dictionary: [:], environment: environment)
}()
func makeVariable(_ token: String) throws -> RangeVariable? {
let token = Token.variable(value: token, at: .unknown)
let parser = TokenParser(tokens: [token], environment: context.environment)
return try RangeVariable(token.contents, parser: parser, containedIn: token)
}
$0.it("can resolve closed range as array") {
let result = try makeVariable("1...3")?.resolve(context) as? [Int]
try expect(result) == [1, 2, 3]
}
$0.it("can resolve decreasing closed range as reversed array") {
let result = try makeVariable("3...1")?.resolve(context) as? [Int]
try expect(result) == [3, 2, 1]
}
$0.it("can use filter on range variables") {
let result = try makeVariable("1|incr...3|incr")?.resolve(context) as? [Int]
try expect(result) == [2, 3, 4]
}
$0.it("throws when left value is not int") {
let template: Template = "{% for i in k...j %}{{ i }}{% endfor %}"
try expect(try template.render(Context(dictionary: ["j": 3, "k": "1"]))).toThrow()
}
$0.it("throws when right value is not int") {
let variable = try makeVariable("k...j")
try expect(try variable?.resolve(Context(dictionary: ["j": "3", "k": 1]))).toThrow()
}
$0.it("throws is left range value is missing") {
try expect(makeVariable("...1")).toThrow()
}
$0.it("throws is right range value is missing") {
try expect(makeVariable("1...")).toThrow()
}
func makeVariable(_ token: String) throws -> RangeVariable? {
let token = Token.variable(value: token, at: .unknown)
let parser = TokenParser(tokens: [token], environment: context.environment)
return try RangeVariable(token.contents, parser: parser, containedIn: token)
}
$0.it("can resolve closed range as array") {
let result = try makeVariable("1...3")?.resolve(context) as? [Int]
try expect(result) == [1, 2, 3]
}
describe("inline if expression") {
$0.it("can resolve decreasing closed range as reversed array") {
let result = try makeVariable("3...1")?.resolve(context) as? [Int]
try expect(result) == [3, 2, 1]
}
$0.it("can conditionally render variable") {
let template: Template = "{{ variable if variable|uppercase == \"A\" }}"
try expect(template.render(Context(dictionary: ["variable": "a"]))) == "a"
try expect(template.render(Context(dictionary: ["variable": "b"]))) == ""
}
$0.it("can use filter on range variables") {
let result = try makeVariable("1|incr...3|incr")?.resolve(context) as? [Int]
try expect(result) == [2, 3, 4]
}
$0.it("can render with else expression") {
let template: Template = "{{ variable if variable|uppercase == \"A\" else fallback|uppercase }}"
try expect(template.render(Context(dictionary: ["variable": "b", "fallback": "c"]))) == "C"
}
$0.it("throws when left value is not int") {
let template: Template = "{% for i in k...j %}{{ i }}{% endfor %}"
try expect(try template.render(Context(dictionary: ["j": 3, "k": "1"]))).toThrow()
}
$0.it("throws when right value is not int") {
let variable = try makeVariable("k...j")
try expect(try variable?.resolve(Context(dictionary: ["j": "3", "k": 1]))).toThrow()
}
$0.it("throws is left range value is missing") {
try expect(makeVariable("...1")).toThrow()
}
$0.it("throws is right range value is missing") {
try expect(makeVariable("1...")).toThrow()
}
}
describe("inline if expression") {
$0.it("can conditionally render variable") {
let template: Template = "{{ variable if variable|uppercase == \"A\" }}"
try expect(template.render(Context(dictionary: ["variable": "a"]))) == "a"
try expect(template.render(Context(dictionary: ["variable": "b"]))) == ""
}
$0.it("can render with else expression") {
let template: Template = "{{ variable if variable|uppercase == \"A\" else fallback|uppercase }}"
try expect(template.render(Context(dictionary: ["variable": "b", "fallback": "c"]))) == "C"
}
$0.it("throws when used invalid condition") {
let template: Template = "{{ variable if variable \"A\" }}"
try expect(template.render(Context(dictionary: ["variable": "a"]))).toThrow()
$0.it("throws when used invalid condition") {
let template: Template = "{{ variable if variable \"A\" }}"
try expect(template.render(Context(dictionary: ["variable": "a"]))).toThrow()
}
}
}
}

View File

@@ -1,30 +0,0 @@
import XCTest
public func stencilTests() {
testContext()
testFilter()
testLexer()
testToken()
testTokenParser()
testTemplateLoader()
testTemplate()
testVariable()
testNode()
testForNode()
testExpressions()
testIfNode()
testNowNode()
testInclude()
testInheritence()
testFilterTag()
testEnvironment()
testStencil()
}
class StencilTests: XCTestCase {
func testRunStencilTests() {
stencilTests()
}
}

View File

@@ -0,0 +1,134 @@
import XCTest
extension ContextTests {
static let __allTests = [
("testContext", testContext),
]
}
extension EnvironmentTests {
static let __allTests = [
("testEnvironment", testEnvironment),
]
}
extension ExpressionsTests {
static let __allTests = [
("testExpressions", testExpressions),
]
}
extension FilterTagTests {
static let __allTests = [
("testFilterTag", testFilterTag),
]
}
extension FilterTests {
static let __allTests = [
("testFilter", testFilter),
]
}
extension ForNodeTests {
static let __allTests = [
("testForNode", testForNode),
]
}
extension IfNodeTests {
static let __allTests = [
("testIfNode", testIfNode),
]
}
extension IncludeTests {
static let __allTests = [
("testInclude", testInclude),
]
}
extension InheritenceTests {
static let __allTests = [
("testInheritence", testInheritence),
]
}
extension LexerTests {
static let __allTests = [
("testLexer", testLexer),
]
}
extension NodeTests {
static let __allTests = [
("testNode", testNode),
]
}
extension NowNodeTests {
static let __allTests = [
("testNowNode", testNowNode),
]
}
extension StencilTests {
static let __allTests = [
("testStencil", testStencil),
]
}
extension TemplateLoaderTests {
static let __allTests = [
("testTemplateLoader", testTemplateLoader),
]
}
extension TemplateTests {
static let __allTests = [
("testTemplate", testTemplate),
]
}
extension TokenParserTests {
static let __allTests = [
("testTokenParser", testTokenParser),
]
}
extension TokenTests {
static let __allTests = [
("testToken", testToken),
]
}
extension VariableTests {
static let __allTests = [
("testVariable", testVariable),
]
}
#if !os(macOS)
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(ContextTests.__allTests),
testCase(EnvironmentTests.__allTests),
testCase(ExpressionsTests.__allTests),
testCase(FilterTagTests.__allTests),
testCase(FilterTests.__allTests),
testCase(ForNodeTests.__allTests),
testCase(IfNodeTests.__allTests),
testCase(IncludeTests.__allTests),
testCase(InheritenceTests.__allTests),
testCase(LexerTests.__allTests),
testCase(NodeTests.__allTests),
testCase(NowNodeTests.__allTests),
testCase(StencilTests.__allTests),
testCase(TemplateLoaderTests.__allTests),
testCase(TemplateTests.__allTests),
testCase(TokenParserTests.__allTests),
testCase(TokenTests.__allTests),
testCase(VariableTests.__allTests),
]
}
#endif