Merge branch 'master' into errors-logs-improvements
This commit is contained in:
@@ -48,18 +48,20 @@ func testEnvironment() {
|
||||
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
|
||||
}
|
||||
|
||||
func expectError(reason: String, token: String) throws {
|
||||
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"])).toThrow() as TemplateSyntaxError
|
||||
try expect(environment.errorReporter.renderError(error)) == environment.errorReporter.renderError(expectedError)
|
||||
let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]),
|
||||
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
|
||||
try expect(environment.errorReporter.renderError(error), file: file, line: line, function: function) == environment.errorReporter.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 following syntax 'for x in y where condition'.", token: "for name in")
|
||||
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") {
|
||||
@@ -78,37 +80,37 @@ func testEnvironment() {
|
||||
|
||||
$0.it("reports syntax error in for tag") {
|
||||
template = "{% for name in names|unknown %}{{ name }}{% endfor %}"
|
||||
try expectError(reason: "Unknown filter 'unknown'", token: "names|unknown")
|
||||
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'", token: "name|unknown")
|
||||
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'", token: "name|unknown")
|
||||
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'", token: "name|unknown")
|
||||
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'", token: "name|unknown")
|
||||
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'", token: "filter unknown")
|
||||
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'", token: "name|unknown")
|
||||
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -200,20 +202,21 @@ func testEnvironment() {
|
||||
includedTemplate = nil
|
||||
}
|
||||
|
||||
func expectError(reason: String, token: String, includedToken: String) throws {
|
||||
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"]))
|
||||
.toThrow() as TemplateSyntaxError
|
||||
try expect(environment.errorReporter.renderError(error)) == environment.errorReporter.renderError(expectedError)
|
||||
let error = try expect(environment.render(template: template, context: ["target": "World"]),
|
||||
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
|
||||
try expect(environment.errorReporter.renderError(error), file: file, line: line, function: function) == environment.errorReporter.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")
|
||||
|
||||
try expectError(reason: "Unknown filter 'unknown'",
|
||||
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
token: "include \"invalid-include.html\"",
|
||||
includedToken: "target|unknown")
|
||||
}
|
||||
@@ -248,21 +251,22 @@ func testEnvironment() {
|
||||
baseTemplate = nil
|
||||
}
|
||||
|
||||
func expectError(reason: String, childToken: String, baseToken: String?) throws {
|
||||
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"]))
|
||||
.toThrow() as TemplateSyntaxError
|
||||
try expect(environment.errorReporter.renderError(error)) == environment.errorReporter.renderError(expectedError)
|
||||
let error = try expect(environment.render(template: childTemplate, context: ["target": "World"]),
|
||||
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
|
||||
try expect(environment.errorReporter.renderError(error), file: file, line: line, function: function) == environment.errorReporter.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")
|
||||
|
||||
try expectError(reason: "Unknown filter 'unknown'",
|
||||
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
childToken: "extends \"invalid-base.html\"",
|
||||
baseToken: "target|unknown")
|
||||
}
|
||||
@@ -286,7 +290,7 @@ func testEnvironment() {
|
||||
childTemplate = Template(templateString: "{% extends \"base.html\" %}\n" +
|
||||
"{% block body %}Child {{ target|unknown }}{% endblock %}", environment: environment, name: nil)
|
||||
|
||||
try expectError(reason: "Unknown filter 'unknown'",
|
||||
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
childToken: "target|unknown",
|
||||
baseToken: nil)
|
||||
}
|
||||
@@ -311,7 +315,7 @@ func testEnvironment() {
|
||||
}
|
||||
}
|
||||
|
||||
private extension Expectation {
|
||||
extension Expectation {
|
||||
@discardableResult
|
||||
func toThrow<T: Error>() throws -> T {
|
||||
var thrownError: Error? = nil
|
||||
|
||||
@@ -287,12 +287,16 @@ func testExpressions() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +78,9 @@ func testFilter() {
|
||||
}
|
||||
|
||||
$0.it("allows whitespace in expression") {
|
||||
let template = Template(templateString: "{{ name | uppercase }}")
|
||||
let result = try template.render(Context(dictionary: ["name": "kyle"]))
|
||||
try expect(result) == "KYLE"
|
||||
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") {
|
||||
@@ -89,32 +89,45 @@ func testFilter() {
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
describe("capitalize filter") {
|
||||
let template = Template(templateString: "{{ name|capitalize }}")
|
||||
$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("capitalizes a string") {
|
||||
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\"]"
|
||||
}
|
||||
|
||||
describe("uppercase filter") {
|
||||
let template = Template(templateString: "{{ name|uppercase }}")
|
||||
$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 uppercase") {
|
||||
let result = try template.render(Context(dictionary: ["name": "kyle"]))
|
||||
try expect(result) == "KYLE"
|
||||
}
|
||||
}
|
||||
|
||||
describe("lowercase filter") {
|
||||
let template = Template(templateString: "{{ name|lowercase }}")
|
||||
|
||||
$0.it("transforms a string to be lowercase") {
|
||||
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: "{{ names|lowercase }}")
|
||||
let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]]))
|
||||
try expect(result) == "[\"kyle\", \"kyle\"]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,4 +196,96 @@ func testFilter() {
|
||||
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!
|
||||
|
||||
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
|
||||
let range = template.templateString.range(of: token)!
|
||||
let rangeLine = template.templateString.rangeLine(range)
|
||||
let sourceMap = SourceMap(filename: template.name, line: rangeLine)
|
||||
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
|
||||
|
||||
try expect(environment.errorReporter.renderError(error), file: file, line: line, function: function) == environment.errorReporter.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\nTwo"]))
|
||||
try expect(result) == "One\n 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\nTwo"]))
|
||||
try expect(result) == "One\n\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\nTwo"]))
|
||||
try expect(result) == " One\n Two"
|
||||
}
|
||||
|
||||
$0.it("does not indent empty lines") {
|
||||
let template = Template(templateString: "{{ value|indent }}")
|
||||
let result = try template.render(Context(dictionary: ["value": "One\n\n\nTwo\n\n"]))
|
||||
try expect(result) == "One\n\n\n Two\n\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,5 +21,25 @@ func testFilterTag() {
|
||||
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: "{% filter split:\",\"|join:\";\" %}{{ items|join:\",\" }}{% endfilter %}", context: ["items": [1, 2]])
|
||||
try expect(result) == "1;2"
|
||||
}
|
||||
|
||||
$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)
|
||||
})
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ func testForNode() {
|
||||
"dict": [
|
||||
"one": "I",
|
||||
"two": "II",
|
||||
]
|
||||
],
|
||||
"tuples": [(1, 2, 3), (4, 5, 6)]
|
||||
])
|
||||
|
||||
$0.it("renders the given nodes for each item") {
|
||||
@@ -89,6 +90,12 @@ func testForNode() {
|
||||
try expect(try node.render(context)) == "102132"
|
||||
}
|
||||
|
||||
$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))
|
||||
@@ -104,8 +111,8 @@ func testForNode() {
|
||||
try expect(try node.render(context)) == "empty"
|
||||
}
|
||||
|
||||
$0.it("can render a filter") {
|
||||
let templateString = "{% for article in ars|default:articles %}" +
|
||||
$0.it("can render a filter with spaces") {
|
||||
let templateString = "{% for article in ars | default: a, b , articles %}" +
|
||||
"- {{ article.title }} by {{ article.author }}.\n" +
|
||||
"{% endfor %}\n"
|
||||
|
||||
@@ -127,8 +134,55 @@ func testForNode() {
|
||||
try expect(result) == fixture
|
||||
}
|
||||
|
||||
$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 }}\n" +
|
||||
"{% endfor %}\n"
|
||||
|
||||
let template = Template(templateString: templateString)
|
||||
let result = try template.render(context)
|
||||
|
||||
let fixture = "1, 2, 3\n4, 5, 6\n\n"
|
||||
try expect(result) == fixture
|
||||
}
|
||||
|
||||
$0.it("can iterate with less number of variables") {
|
||||
let templateString = "{% for first,second in tuples %}" +
|
||||
"{{ first }}, {{ second }}\n" +
|
||||
"{% endfor %}\n"
|
||||
|
||||
let template = Template(templateString: templateString)
|
||||
let result = try template.render(context)
|
||||
|
||||
let fixture = "1, 2\n4, 5\n\n"
|
||||
try expect(result) == fixture
|
||||
}
|
||||
|
||||
$0.it("can use _ to skip variables") {
|
||||
let templateString = "{% for first,_,third in tuples %}" +
|
||||
"{{ first }}, {{ third }}\n" +
|
||||
"{% endfor %}\n"
|
||||
|
||||
let template = Template(templateString: templateString)
|
||||
let result = try template.render(context)
|
||||
|
||||
let fixture = "1, 3\n4, 6\n\n"
|
||||
try expect(result) == fixture
|
||||
}
|
||||
|
||||
$0.it("throws when number of variables is more than number of tuple values") {
|
||||
let templateString = "{% for key,value,smth in dict %}" +
|
||||
"{% endfor %}\n"
|
||||
|
||||
let template = Template(templateString: templateString)
|
||||
try expect(template.render(context)).toThrow()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$0.it("can iterate over dictionary") {
|
||||
let templateString = "{% for key,value in dict %}" +
|
||||
let templateString = "{% for key, value in dict %}" +
|
||||
"{{ key }}: {{ value }}," +
|
||||
"{% endfor %}"
|
||||
|
||||
@@ -169,12 +223,92 @@ func testForNode() {
|
||||
}
|
||||
|
||||
$0.it("handles invalid input") {
|
||||
let tokens: [Token] = [.block(value: "for i", at: .unknown)]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
let error = TemplateSyntaxError(reason: "'for' statements should use the following syntax 'for x in y where condition'.", token: tokens.first)
|
||||
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\nnumber=123\n"
|
||||
}
|
||||
|
||||
$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 node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
|
||||
let result = try node.render(context)
|
||||
|
||||
try expect(result) == "one=1\ntwo=dva\n"
|
||||
}
|
||||
|
||||
$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)
|
||||
}
|
||||
}
|
||||
|
||||
let context = Context(dictionary: [
|
||||
"class": MySubclass("child", "base", 1)
|
||||
])
|
||||
|
||||
let nodes: [NodeType] = [
|
||||
VariableNode(variable: "label"),
|
||||
TextNode(text: "="),
|
||||
VariableNode(variable: "value"),
|
||||
TextNode(text: "\n"),
|
||||
]
|
||||
|
||||
let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
|
||||
let result = try node.render(context)
|
||||
|
||||
try expect(result) == "childString=child\nbaseString=base\nbaseInt=1\n"
|
||||
}
|
||||
|
||||
$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"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -266,5 +266,22 @@ func testIfNode() {
|
||||
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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func testInclude() {
|
||||
let tokens: [Token] = [ .block(value: "include", at: .unknown) ]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
|
||||
let error = TemplateSyntaxError(reason: "'include' tag takes one argument, the template file to be included", token: tokens.first)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,13 @@ func testInclude() {
|
||||
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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,28 @@ func testLexer() {
|
||||
let lexer = Lexer(templateString: "{{}}")
|
||||
let _ = lexer.tokenize()
|
||||
}
|
||||
|
||||
$0.it("can tokenize with new lines") {
|
||||
let templateString =
|
||||
"My name is {%\n" +
|
||||
" if name\n" +
|
||||
" and\n" +
|
||||
" name\n" +
|
||||
"%}{{\n" +
|
||||
"name\n" +
|
||||
"}}{%\n" +
|
||||
"endif %}."
|
||||
|
||||
let lexer = Lexer(templateString: templateString)
|
||||
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
try expect(tokens.count) == 5
|
||||
try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "My name is")!)))
|
||||
try expect(tokens[1]) == Token.block(value: "if name and name", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "{%")!)))
|
||||
try expect(tokens[2]) == Token.variable(value: "name", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "name", options: [.backwards])!)))
|
||||
try expect(tokens[3]) == Token.block(value: "endif", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "endif")!)))
|
||||
try expect(tokens[4]) == Token.text(value: ".", at: SourceMap(line: templateString.rangeLine(templateString.range(of: ".")!)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import Spectre
|
||||
|
||||
#if os(OSX)
|
||||
@objc class Superclass: NSObject {
|
||||
let name = "Foo"
|
||||
@objc let name = "Foo"
|
||||
}
|
||||
@objc class Object : Superclass {
|
||||
let title = "Hello World"
|
||||
@objc let title = "Hello World"
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -26,6 +26,7 @@ fileprivate class WebSite {
|
||||
|
||||
fileprivate class Blog: WebSite {
|
||||
let articles: [Article] = [Article(author: Person(name: "Kyle"))]
|
||||
let featuring: Article? = Article(author: Person(name: "Jhon"))
|
||||
}
|
||||
|
||||
func testVariable() {
|
||||
@@ -39,7 +40,8 @@ func testVariable() {
|
||||
"counter": [
|
||||
"count": "kylef",
|
||||
],
|
||||
"article": Article(author: Person(name: "Kyle"))
|
||||
"article": Article(author: Person(name: "Kyle")),
|
||||
"tuple": (one: 1, two: 2)
|
||||
])
|
||||
|
||||
#if os(OSX)
|
||||
@@ -71,6 +73,13 @@ func testVariable() {
|
||||
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
|
||||
@@ -140,7 +149,7 @@ func testVariable() {
|
||||
try expect(result) == "Foo"
|
||||
}
|
||||
#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
|
||||
@@ -153,5 +162,171 @@ func testVariable() {
|
||||
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("Subrscripting") {
|
||||
$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") {
|
||||
|
||||
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? {
|
||||
return try RangeVariable(token, parser: TokenParser(tokens: [], environment: context.environment))
|
||||
}
|
||||
|
||||
$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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user