From 2d82dcb003e43b0f0b32214ff15b1db360d2055e Mon Sep 17 00:00:00 2001 From: David Jennes Date: Fri, 21 Sep 2018 00:00:56 +0200 Subject: [PATCH] Fix issues in Tests t --- Tests/StencilTests/ContextSpec.swift | 22 +- Tests/StencilTests/EnvironmentSpec.swift | 725 ++++++++++++---------- Tests/StencilTests/ExpressionSpec.swift | 690 +++++++++++---------- Tests/StencilTests/FilterSpec.swift | 756 ++++++++++++----------- Tests/StencilTests/FilterTagSpec.swift | 77 +-- Tests/StencilTests/ForNodeSpec.swift | 557 ++++++++--------- Tests/StencilTests/IfNodeSpec.swift | 555 +++++++++-------- Tests/StencilTests/IncludeSpec.swift | 116 ++-- Tests/StencilTests/InheritenceSpec.swift | 38 +- Tests/StencilTests/LexerSpec.swift | 194 +++--- Tests/StencilTests/LoaderSpec.swift | 84 ++- Tests/StencilTests/NodeSpec.swift | 82 ++- Tests/StencilTests/NowNodeSpec.swift | 82 +-- Tests/StencilTests/ParserSpec.swift | 99 +-- Tests/StencilTests/StencilSpec.swift | 90 ++- Tests/StencilTests/TemplateSpec.swift | 21 +- Tests/StencilTests/TokenSpec.swift | 46 +- Tests/StencilTests/VariableSpec.swift | 674 +++++++++----------- Tests/StencilTests/XCTestManifests.swift | 119 +++- 19 files changed, 2573 insertions(+), 2454 deletions(-) diff --git a/Tests/StencilTests/ContextSpec.swift b/Tests/StencilTests/ContextSpec.swift index 7e20cbd..191529c 100644 --- a/Tests/StencilTests/ContextSpec.swift +++ b/Tests/StencilTests/ContextSpec.swift @@ -1,14 +1,11 @@ -import XCTest import Spectre @testable import Stencil +import XCTest - -class ContextTests: XCTestCase { - - func testContext() { - describe("Context") { - var context: Context! - +final class ContextTests: XCTestCase { + func testContextSubscripting() { + describe("Context Subscripting") { + var context = Context() $0.before { context = Context(dictionary: ["name": "Kyle"]) } @@ -41,6 +38,15 @@ class ContextTests: XCTestCase { try expect(context["name"] as? String) == "Katie" } } + } + } + + func testContextRestoration() { + describe("Context Restoration") { + var context = Context() + $0.before { + context = Context(dictionary: ["name": "Kyle"]) + } $0.it("allows you to pop to restore previous state") { context.push { diff --git a/Tests/StencilTests/EnvironmentSpec.swift b/Tests/StencilTests/EnvironmentSpec.swift index f2a7d06..2acfa30 100644 --- a/Tests/StencilTests/EnvironmentSpec.swift +++ b/Tests/StencilTests/EnvironmentSpec.swift @@ -1,342 +1,394 @@ -import XCTest -import Spectre import PathKit +import Spectre @testable import Stencil +import XCTest -class EnvironmentTests: XCTestCase { - func testEnvironment() { - describe("Environment") { - var environment: Environment! - var template: Template! +final class EnvironmentTests: XCTestCase { + var environment = Environment(loader: ExampleLoader()) + 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 in [where ]`.", 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") - - try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", - token: """ - include "invalid-include.html" - """, - 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] - - template = Template(templateString: """ - {% include "invalid-include.html" %} - """, environment: environment) - 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!] - } - 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") - - try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", - childToken: "extends \"invalid-base.html\"", - baseToken: "target|unknown") - } - - $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) - } - - $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: """ - {% extends "base.html" %} - {% block body %}Child {{ target|unknown }}{% endblock %} - """, environment: environment, name: nil) - - try expectError(reason: "filter error", - childToken: "target|unknown", - baseToken: nil) - } - - } + override func setUp() { + super.setUp() + let errorExtension = Extension() + errorExtension.registerFilter("throw") { (_: Any?) in + throw TemplateSyntaxError("filter error") } + errorExtension.registerSimpleTag("simpletag") { _ in + throw TemplateSyntaxError("simpletag error") + } + errorExtension.registerTag("customtag") { _, token in + ErrorNode(token: token) + } + + environment = Environment(loader: ExampleLoader()) + environment.extensions += [errorExtension] + template = "" + } + + func testLoading() { + it("can load a template from a name") { + let template = try self.environment.loadTemplate(name: "example.html") + try expect(template.name) == "example.html" + } + + it("can load a template from a names") { + let template = try self.environment.loadTemplate(names: ["first.html", "example.html"]) + try expect(template.name) == "example.html" + } + } + + func testRendering() { + it("can render a template from a string") { + let result = try self.environment.renderTemplate(string: "Hello World") + try expect(result) == "Hello World" + } + + it("can render a template from a file") { + let result = try self.environment.renderTemplate(name: "example.html") + try expect(result) == "Hello World!" + } + + 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 testSyntaxError() { + it("reports syntax error on invalid for tag syntax") { + self.template = "Hello {% for name in %}{{ name }}, {% endfor %}!" + try self.expectError( + reason: "'for' statements should use the syntax: `for in [where ]`.", + token: "for name in" + ) + } + + it("reports syntax error on missing endfor") { + self.template = "{% for name in names %}{{ name }}" + try self.expectError(reason: "`endfor` was not found.", token: "for name in names") + } + + it("reports syntax error on unknown tag") { + self.template = "{% for name in names %}{{ name }}{% end %}" + try self.expectError(reason: "Unknown template tag 'end'", token: "end") + } + } + + func testUnknownFilter() { + it("reports syntax error in for tag") { + self.template = "{% for name in names|unknown %}{{ name }}{% endfor %}" + try self.expectError( + reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: "names|unknown" + ) + } + + it("reports syntax error in for-where tag") { + self.template = "{% for name in names where name|unknown %}{{ name }}{% endfor %}" + try self.expectError( + reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: "name|unknown" + ) + } + + it("reports syntax error in if tag") { + self.template = "{% if name|unknown %}{{ name }}{% endif %}" + try self.expectError( + reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: "name|unknown" + ) + } + + it("reports syntax error in elif tag") { + self.template = "{% if name %}{{ name }}{% elif name|unknown %}{% endif %}" + try self.expectError( + reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: "name|unknown" + ) + } + + it("reports syntax error in ifnot tag") { + self.template = "{% ifnot name|unknown %}{{ name }}{% endif %}" + try self.expectError( + reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: "name|unknown" + ) + } + + it("reports syntax error in filter tag") { + self.template = "{% filter unknown %}Text{% endfilter %}" + try self.expectError( + reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: "filter unknown" + ) + } + + it("reports syntax error in variable tag") { + self.template = "{{ name|unknown }}" + try self.expectError( + reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: "name|unknown" + ) + } + } + + func testRenderingError() { + it("reports rendering error in variable filter") { + self.template = Template(templateString: "{{ name|throw }}", environment: self.environment) + try self.expectError(reason: "filter error", token: "name|throw") + } + + it("reports rendering error in filter tag") { + self.template = Template(templateString: "{% filter throw %}Test{% endfilter %}", environment: self.environment) + try self.expectError(reason: "filter error", token: "filter throw") + } + + it("reports rendering error in simple tag") { + self.template = Template(templateString: "{% simpletag %}", environment: self.environment) + try self.expectError(reason: "simpletag error", token: "simpletag") + } + + it("reports passing argument to simple filter") { + self.template = "{{ name|uppercase:5 }}" + try self.expectError(reason: "cannot invoke filter with an argument", token: "name|uppercase:5") + } + + it("reports rendering error in custom tag") { + self.template = Template(templateString: "{% customtag %}", environment: self.environment) + try self.expectError(reason: "Custom Error", token: "customtag") + } + + it("reports rendering error in for body") { + self.template = Template(templateString: """ + {% for name in names %}{% customtag %}{% endfor %} + """, environment: self.environment) + try self.expectError(reason: "Custom Error", token: "customtag") + } + + it("reports rendering error in block") { + self.template = Template( + templateString: "{% block some %}{% customtag %}{% endblock %}", + environment: self.environment + ) + try self.expectError(reason: "Custom Error", token: "customtag") + } + } + + private 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( + self.environment.render(template: self.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) + } +} + +final class EnvironmentIncludeTemplateTests: XCTestCase { + var environment = Environment(loader: ExampleLoader()) + var template: Template = "" + var includedTemplate: Template = "" + + override func setUp() { + super.setUp() + + let path = Path(#file) + ".." + "fixtures" + let loader = FileSystemLoader(paths: [path]) + environment = Environment(loader: loader) + template = "" + includedTemplate = "" + } + + func testSyntaxError() throws { + template = Template(templateString: """ + {% include "invalid-include.html" %} + """, environment: environment) + includedTemplate = try environment.loadTemplate(name: "invalid-include.html") + + try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: """ + include "invalid-include.html" + """, + includedToken: "target|unknown") + } + + func testRuntimeError() throws { + let filterExtension = Extension() + filterExtension.registerFilter("unknown") { (_: Any?) in + throw TemplateSyntaxError("filter error") + } + environment.extensions += [filterExtension] + + template = Template(templateString: """ + {% include "invalid-include.html" %} + """, environment: environment) + includedTemplate = try environment.loadTemplate(name: "invalid-include.html") + + try expectError(reason: "filter error", + token: "include \"invalid-include.html\"", + includedToken: "target|unknown") + } + + private 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].compactMap { $0 } + + let error = try expect( + self.environment.render(template: self.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) + } +} + +final class EnvironmentBaseAndChildTemplateTests: XCTestCase { + var environment = Environment(loader: ExampleLoader()) + var childTemplate: Template = "" + var baseTemplate: Template = "" + + override func setUp() { + super.setUp() + + let path = Path(#file) + ".." + "fixtures" + let loader = FileSystemLoader(paths: [path]) + environment = Environment(loader: loader) + childTemplate = "" + baseTemplate = "" + } + + func testSyntaxErrorInBaseTemplate() throws { + childTemplate = try environment.loadTemplate(name: "invalid-child-super.html") + baseTemplate = try environment.loadTemplate(name: "invalid-base.html") + + try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + childToken: "extends \"invalid-base.html\"", + baseToken: "target|unknown") + } + + func testRuntimeErrorInBaseTemplate() throws { + let filterExtension = Extension() + filterExtension.registerFilter("unknown") { (_: 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") + } + + func testSyntaxErrorInChildTemplate() throws { + 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) + } + + func testRuntimeErrorInChildTemplate() throws { + let filterExtension = Extension() + filterExtension.registerFilter("unknown") { (_: Any?) in + throw TemplateSyntaxError("filter error") + } + environment.extensions += [filterExtension] + + 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) + } + + private 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].compactMap { $0 } + } + let error = try expect( + self.environment.render(template: self.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) } } extension Expectation { @discardableResult func toThrow() throws -> T { - var thrownError: Error? = nil + var thrownError: Error? do { _ = try expression() @@ -356,7 +408,20 @@ extension Expectation { } } -fileprivate class ExampleLoader: Loader { +extension XCTestCase { + 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: []) + } +} + +private class ExampleLoader: Loader { func loadTemplate(name: String, environment: Environment) throws -> Template { if name == "example.html" { return Template(templateString: "Hello World!", environment: environment, name: name) @@ -366,8 +431,8 @@ fileprivate class ExampleLoader: Loader { } } - -class CustomTemplate: Template { +private class CustomTemplate: Template { + // swiftlint:disable discouraged_optional_collection override func render(_ dictionary: [String: Any]? = nil) throws -> String { return "here" } diff --git a/Tests/StencilTests/ExpressionSpec.swift b/Tests/StencilTests/ExpressionSpec.swift index a115555..6659d39 100644 --- a/Tests/StencilTests/ExpressionSpec.swift +++ b/Tests/StencilTests/ExpressionSpec.swift @@ -1,345 +1,355 @@ -import XCTest import Spectre @testable import Stencil - -class ExpressionsTests: XCTestCase { - func testExpressions() { - describe("Expression") { - - func parseExpression(components: [String]) throws -> Expression { - let parser = try IfExpressionParser.parser(components: components, environment: Environment(), 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() - } - - $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 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 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 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")) - } - } - } +import XCTest + +final class ExpressionsTests: XCTestCase { + let parser = TokenParser(tokens: [], environment: Environment()) + + private func makeExpression(_ components: [String]) -> Expression { + do { + let parser = try IfExpressionParser.parser( + components: components, + environment: Environment(), + token: .text(value: "", at: .unknown) + ) + return try parser.parse() + } catch { + fatalError(error.localizedDescription) } } + + func testTrueExpressions() { + let expression = VariableExpression(variable: Variable("value")) + + it("evaluates to true when value is not nil") { + let context = Context(dictionary: ["value": "known"]) + try expect(try expression.evaluate(context: context)).to.beTrue() + } + + it("evaluates to true when array variable is not empty") { + let items: [[String: Any]] = [["key": "key1", "value": 42], ["key": "key2", "value": 1_337]] + let context = Context(dictionary: ["value": [items]]) + try expect(try expression.evaluate(context: context)).to.beTrue() + } + + 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() + } + + 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() + } + + it("evaluates to true with string") { + let context = Context(dictionary: ["value": "test"]) + try expect(try expression.evaluate(context: context)).to.beTrue() + } + + 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() + } + + 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() + } + } + + func testFalseExpressions() { + let expression = VariableExpression(variable: Variable("value")) + + it("evaluates to false when value is unset") { + let context = Context() + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + 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() + } + + 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() + } + + it("evaluates to false when Array value is empty") { + let context = Context(dictionary: ["value": ([] as [Any])]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + it("evaluates to false when empty string") { + let context = Context(dictionary: ["value": ""]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + 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": -1]) + try expect(try expression.evaluate(context: negativeContext)).to.beFalse() + } + + 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() + } + + 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() + } + + it("evaluates to false when uint is 0") { + let context = Context(dictionary: ["value": UInt(0)]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + } + + func testNotExpression() { + it("returns truthy for positive expressions") { + let expression = NotExpression(expression: StaticExpression(value: true)) + try expect(expression.evaluate(context: Context())).to.beFalse() + } + + it("returns falsy for negative expressions") { + let expression = NotExpression(expression: StaticExpression(value: false)) + try expect(expression.evaluate(context: Context())).to.beTrue() + } + } + + func testExpressionParsing() { + it("can parse a variable expression") { + let expression = self.makeExpression(["value"]) + try expect(expression.evaluate(context: Context())).to.beFalse() + try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue() + } + + it("can parse a not expression") { + let expression = self.makeExpression(["not", "value"]) + try expect(expression.evaluate(context: Context())).to.beTrue() + try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse() + } + } + + func testAndExpression() { + let expression = makeExpression(["lhs", "and", "rhs"]) + + it("evaluates to false with lhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse() + } + + it("evaluates to false with rhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse() + } + + it("evaluates to false with lhs and rhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse() + } + + it("evaluates to true with lhs and rhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() + } + } + + func testOrExpression() { + let expression = makeExpression(["lhs", "or", "rhs"]) + + it("evaluates to true with lhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue() + } + + it("evaluates to true with rhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beTrue() + } + + it("evaluates to true with lhs and rhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() + } + + it("evaluates to false with lhs and rhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse() + } + } + + func testEqualityExpression() { + let expression = makeExpression(["lhs", "==", "rhs"]) + + it("evaluates to true with equal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue() + } + + it("evaluates to false with non equal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beFalse() + } + + it("evaluates to true with nils") { + try expect(expression.evaluate(context: Context(dictionary: [:]))).to.beTrue() + } + + it("evaluates to true with numbers") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.0]))).to.beTrue() + } + + it("evaluates to false with non equal numbers") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.1]))).to.beFalse() + } + + it("evaluates to true with booleans") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() + } + + it("evaluates to false with falsy booleans") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse() + } + + it("evaluates to false with different types") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": 1]))).to.beFalse() + } + } + + func testInequalityExpression() { + let expression = makeExpression(["lhs", "!=", "rhs"]) + + it("evaluates to true with inequal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue() + } + + it("evaluates to false with equal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "b", "rhs": "b"]))).to.beFalse() + } + } + + func testMoreThanExpression() { + let expression = makeExpression(["lhs", ">", "rhs"]) + + it("evaluates to true with lhs > rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue() + } + + it("evaluates to false with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse() + } + } + + func testMoreThanEqualExpression() { + let expression = makeExpression(["lhs", ">=", "rhs"]) + + it("evaluates to true with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() + } + + it("evaluates to false with lhs < rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.1]))).to.beFalse() + } + } + + func testLessThanExpression() { + let expression = makeExpression(["lhs", "<", "rhs"]) + + it("evaluates to true with lhs < rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue() + } + + it("evaluates to false with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse() + } + } + + func testLessThanEqualExpression() { + let expression = makeExpression(["lhs", "<=", "rhs"]) + + it("evaluates to true with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() + } + + it("evaluates to false with lhs > rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.1, "rhs": 5.0]))).to.beFalse() + } + } + + func testMultipleExpressions() { + let expression = makeExpression(["one", "or", "two", "and", "not", "three"]) + + it("evaluates to true with one") { + try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue() + } + + it("evaluates to true with one and three") { + try expect(expression.evaluate(context: Context(dictionary: ["one": true, "three": true]))).to.beTrue() + } + + it("evaluates to true with two") { + try expect(expression.evaluate(context: Context(dictionary: ["two": true]))).to.beTrue() + } + + it("evaluates to false with two and three") { + try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse() + } + + it("evaluates to false with two and three") { + try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse() + } + + it("evaluates to false with nothing") { + try expect(expression.evaluate(context: Context())).to.beFalse() + } + } + + func testTrueInExpression() throws { + let expression = makeExpression(["lhs", "in", "rhs"]) + + 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() + } + + func testFalseInExpression() throws { + let expression = makeExpression(["lhs", "in", "rhs"]) + + 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() + } } diff --git a/Tests/StencilTests/FilterSpec.swift b/Tests/StencilTests/FilterSpec.swift index 4a6dedf..d2ac102 100644 --- a/Tests/StencilTests/FilterSpec.swift +++ b/Tests/StencilTests/FilterSpec.swift @@ -1,413 +1,445 @@ -import XCTest import Spectre @testable import Stencil +import XCTest -class FilterTests: XCTestCase { - func testFilter() { - describe("template filters") { - let context: [String: Any] = ["name": "Kyle"] +final class FilterTests: XCTestCase { + func testRegistration() { + let context: [String: Any] = ["name": "Kyle"] - $0.it("allows you to register a custom filter") { - let template = Template(templateString: "{{ name|repeat }}") + 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)" - } - - return nil + let repeatExtension = Extension() + repeatExtension.registerFilter("repeat") { (value: Any?) in + if let value = value as? String { + return "\(value) \(value)" } - let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) - try expect(result) == "Kyle Kyle" - } - - $0.it("allows you to register boolean filters") { - let repeatExtension = Extension() - repeatExtension.registerFilter(name: "isPositive", negativeFilterName: "isNotPositive") { (value: Any?) in - if let value = value as? Int { - return value > 0 - } - return nil - } - - let result = try Template(templateString: "{{ value|isPositive }}") - .render(Context(dictionary: ["value": 1], environment: Environment(extensions: [repeatExtension]))) - try expect(result) == "true" - - let negativeResult = try Template(templateString: "{{ value|isNotPositive }}") - .render(Context(dictionary: ["value": -1], environment: Environment(extensions: [repeatExtension]))) - try expect(negativeResult) == "true" + return nil } - $0.it("allows you to register a custom filter which accepts single argument") { + let result = try template.render(Context( + dictionary: context, + environment: Environment(extensions: [repeatExtension]) + )) + try expect(result) == "Kyle Kyle" + } + + it("allows you to register boolean filters") { + let repeatExtension = Extension() + repeatExtension.registerFilter(name: "isPositive", negativeFilterName: "isNotPositive") { (value: Any?) in + if let value = value as? Int { + return value > 0 + } + return nil + } + + let result = try Template(templateString: "{{ value|isPositive }}") + .render(Context(dictionary: ["value": 1], environment: Environment(extensions: [repeatExtension]))) + try expect(result) == "true" + + let negativeResult = try Template(templateString: "{{ value|isNotPositive }}") + .render(Context(dictionary: ["value": -1], environment: Environment(extensions: [repeatExtension]))) + try expect(negativeResult) == "true" + } + + it("allows you to register a custom which throws") { + let template = Template(templateString: "{{ name|repeat }}") + let repeatExtension = Extension() + repeatExtension.registerFilter("repeat") { (_: 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)) + } + + 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() + } + } + + func testRegistrationOverrideDefault() throws { + let template = Template(templateString: "{{ name|join }}") + let context: [String: Any] = ["name": "Kyle"] + + let repeatExtension = Extension() + repeatExtension.registerFilter("join") { (_: Any?) in + "joined" + } + + let result = try template.render(Context( + dictionary: context, + environment: Environment(extensions: [repeatExtension]) + )) + try expect(result) == "joined" + } + + func testRegistrationWithArguments() { + let context: [String: Any] = ["name": "Kyle"] + + 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 + guard let value = value, + let argument = arguments.first else { return nil } + + return "\(value) \(value) with args \(argument ?? "")" + } + + let result = try template.render(Context( + dictionary: context, + environment: Environment(extensions: [repeatExtension]) + )) + try expect(result) == """ + Kyle Kyle with args value1, "value2" + """ + } + + it("allows you to register a custom filter which accepts several arguments") { let template = Template(templateString: """ - {{ name|repeat:'value1, "value2"' }} + {{ name|repeat:'value"1"',"value'2'",'(key, value)' }} """) let repeatExtension = Extension() repeatExtension.registerFilter("repeat") { value, arguments in - if !arguments.isEmpty { - return "\(value!) \(value!) with args \(arguments.first!!)" - } - - return nil + guard let value = value else { return nil } + let args = arguments.compactMap { $0 } + return "\(value) \(value) with args 0: \(args[0]), 1: \(args[1]), 2: \(args[2])" } - let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) + let result = try template.render(Context( + dictionary: context, + environment: Environment(extensions: [repeatExtension]) + )) try expect(result) == """ - Kyle Kyle with args value1, "value2" + Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value) """ - } - - $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)' }} - """) - - 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]!)" - } - - 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) - """ - } - - $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() - } } - 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") { + it("allows whitespace in expression") { 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" }} + {{ value | join : ", " }} """) - let result = try template.render(Context(dictionary: [:])) - try expect(result) == "Hello World" - } + let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) + try expect(result) == "One, Two" + } + } - $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" - } + func testStringFilters() { + 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("join filter") { + 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" + } + + 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" + } + } + + func testStringFiltersWithArrays() { + 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"] + """ + } + + 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"] + """ + } + + 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"] + """ + } + } + + func testDefaultFilter() { + let template = Template(templateString: """ + Hello {{ name|default:"World" }} + """) + + it("shows the variable value") { + let result = try template.render(Context(dictionary: ["name": "Kyle"])) + try expect(result) == "Hello Kyle" + } + + it("shows the default value") { + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "Hello World" + } + + it("supports multiple defaults") { let template = Template(templateString: """ - {{ value|join:", " }} + Hello {{ name|default:a,b,c,"World" }} """) - - $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" - } + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "Hello World" } - describe("split filter") { + it("can use int as default") { + let template = Template(templateString: "{{ value|default:1 }}") + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "1" + } + + 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" + } + + it("checks for underlying nil value correctly") { let template = Template(templateString: """ - {{ value|split:", " }} + 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.it("split a string into array") { - let result = try template.render(Context(dictionary: ["value": "One, Two"])) - try expect(result) == """ - ["One", "Two"] - """ - } + func testJoinFilter() { + let template = Template(templateString: """ + {{ value|join:", " }} + """) - $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"] - """ - } + it("joins a collection of strings") { + 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 { - 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 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") - } - + 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" } + 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" + } - 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 - """ - } + 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" + } + } - $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 - """ - } + func testSplitFilter() { + let template = Template(templateString: """ + {{ value|split:", " }} + """) - $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 - """ - } + it("split a string into array") { + 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 + 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"] + """ + } + } + + func testFilterSuggestion() { + it("made for unknown filter") { + let template = Template(templateString: "{{ value|unknownFilter }}") + let filterExtension = Extension() + filterExtension.registerFilter("knownFilter") { value, _ in value } + + try self.expectError( + reason: "Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'.", + token: "value|unknownFilter", + template: template, + extension: filterExtension + ) + } + + it("made for multiple similar filters") { + let template = Template(templateString: "{{ value|lowerFirst }}") + let filterExtension = Extension() + filterExtension.registerFilter("lowerFirstWord") { value, _ in value } + filterExtension.registerFilter("lowerFirstLetter") { value, _ in value } + + try self.expectError( + reason: "Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'.", + token: "value|lowerFirst", + template: template, + extension: filterExtension + ) + } + + it("not made when can't find similar filter") { + let template = Template(templateString: "{{ value|unknownFilter }}") + let filterExtension = Extension() + filterExtension.registerFilter("lowerFirstWord") { value, _ in value } + + try self.expectError( + reason: "Unknown filter 'unknownFilter'. Found similar filters: 'lowerFirstWord'.", + token: "value|unknownFilter", + template: template, + extension: filterExtension + ) + } + } + + func testIndentContent() throws { + let template = Template(templateString: """ + {{ value|indent:2 }} + """) + let result = try template.render(Context(dictionary: ["value": """ + One + Two + """])) + try expect(result) == """ + One + Two + """ + } + + func testIndentWithArbitraryCharacter() throws { + let template = Template(templateString: """ + {{ value|indent:2,"\t" }} + """) + let result = try template.render(Context(dictionary: ["value": """ + One + Two + """])) + try expect(result) == """ + One + \t\tTwo + """ + } + + func testIndentFirstLine() throws { + let template = Template(templateString: """ + {{ value|indent:2," ",true }} + """) + let result = try template.render(Context(dictionary: ["value": """ + One + Two + """])) + try expect(result) == """ + One + Two + """ + } + + func testIndentNotEmptyLines() throws { + let template = Template(templateString: """ + {{ value|indent }} + """) + let result = try template.render(Context(dictionary: ["value": """ + One + + + Two + + + """])) + try expect(result) == """ + One Two - """])) - try expect(result) == """ - One + """ + } - - Two - - - """ - } + func testDynamicFilters() throws { + it("can apply dynamic filter") { + let template = Template(templateString: "{{ name|filter:somefilter }}") + let result = try template.render(Context(dictionary: ["name": "Jhon", "somefilter": "uppercase"])) + try expect(result) == "JHON" } - - describe("dynamic filter") { - - $0.it("can apply dynamic filter") { - let template = Template(templateString: "{{ name|filter:somefilter }}") - let result = try template.render(Context(dictionary: ["name": "Jhon", "somefilter": "uppercase"])) - try expect(result) == "JHON" - } - - $0.it("can apply dynamic filter on array") { - let template = Template(templateString: "{{ values|filter:joinfilter }}") - let result = try template.render(Context(dictionary: ["values": [1, 2, 3], "joinfilter": "join:\", \""])) - try expect(result) == "1, 2, 3" - } - - $0.it("throws on unknown dynamic filter") { - let template = Template(templateString: "{{ values|filter:unknown }}") - let context = Context(dictionary: ["values": [1, 2, 3], "unknown": "absurd"]) - try expect(try template.render(context)).toThrow() - } - + + it("can apply dynamic filter on array") { + let template = Template(templateString: "{{ values|filter:joinfilter }}") + let result = try template.render(Context(dictionary: ["values": [1, 2, 3], "joinfilter": "join:\", \""])) + try expect(result) == "1, 2, 3" } - + + it("throws on unknown dynamic filter") { + let template = Template(templateString: "{{ values|filter:unknown }}") + let context = Context(dictionary: ["values": [1, 2, 3], "unknown": "absurd"]) + try expect(try template.render(context)).toThrow() + } + } + + private func expectError( + reason: String, + token: String, + template: Template, + extension: Extension, + file: String = #file, + line: Int = #line, + function: String = #function + ) throws { + guard let range = template.templateString.range(of: token) else { + fatalError("Can't find '\(token)' in '\(template)'") + } + + let environment = Environment(extensions: [`extension`]) + let expectedError: Error = { + 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: reason, token: token, stackTrace: []) + }() + + 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) } } diff --git a/Tests/StencilTests/FilterTagSpec.swift b/Tests/StencilTests/FilterTagSpec.swift index 5423747..d9a82b8 100644 --- a/Tests/StencilTests/FilterTagSpec.swift +++ b/Tests/StencilTests/FilterTagSpec.swift @@ -1,51 +1,54 @@ -import XCTest import Spectre import Stencil +import XCTest -class FilterTagTests: XCTestCase { +final 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" - } + 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" - } + 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() - } + 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: """ + it("can render filters with arguments") { + let ext = Extension() + ext.registerFilter("split") { + guard let value = $0 as? String, + let argument = $1.first as? String else { return $0 } + return value.components(separatedBy: argument) + } + 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") { - 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" + it("can render filters with quote as an argument") { + let ext = Extension() + ext.registerFilter("replace") { + guard let value = $0 as? String, + $1.count == 2, + let search = $1.first as? String, + let replacement = $1.last as? String else { return $0 } + return value.replacingOccurrences(of: search, with: replacement) } + 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" } } } diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index d3bfa8c..d3fc65e 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -1,358 +1,341 @@ -import XCTest import Spectre @testable import Stencil -import Foundation +import XCTest + +final class ForNodeTests: XCTestCase { + let context = Context(dictionary: [ + "items": [1, 2, 3], + "anyItems": [1, 2, 3] as [Any], + "nsItems": NSArray(array: [1, 2, 3]), + "emptyItems": [Int](), + "dict": [ + "one": "I", + "two": "II" + ], + "tuples": [(1, 2, 3), (4, 5, 6)] + ]) -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)] - ]) + 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(self.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" - } + it("renders the given empty nodes when no items found item") { + let node = ForNode( + resolvable: Variable("emptyItems"), + loopVariables: ["item"], + nodes: [VariableNode(variable: "item")], + emptyNodes: [TextNode(text: "empty")] + ) + try expect(try node.render(self.context)) == "empty" + } - $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" - } + it("renders a context variable of type Array") { + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("anyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(self.context)) == "123" + } - $0.it("renders a context variable of type Array") { - let any_context = Context(dictionary: [ - "items": ([1, 2, 3] as [Any]) - ]) + #if os(OSX) + it("renders a context variable of type NSArray") { + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("nsItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(self.context)) == "123" + } + #endif - 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 a context variable of type CountableClosedRange") { - let context = Context(dictionary: ["range": 1...3]) - let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - - try expect(try node.render(context)) == "123" - } - - $0.it("renders a context variable of type CountableRange") { - let context = Context(dictionary: ["range": 1..<4]) - let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - - try expect(try node.render(context)) == "123" - } - - #if os(OSX) - $0.it("renders a context variable of type NSArray") { - let nsarray_context = Context(dictionary: [ - "items": NSArray(array: [1, 2, 3]) - ]) - - 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 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 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 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 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 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 parser = TokenParser(tokens: [], environment: Environment()) - let `where` = try parser.compileExpression(components: ["item", ">", "1"], 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 parser = TokenParser(tokens: [], environment: Environment()) - let `where` = try parser.compileExpression(components: ["item", "==", "0"], 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 = """ + it("can render a filter with spaces") { + let template = Template(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 result = try template.render(context) - 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) - - 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 = """ - {% for first,second,third in tuples %}\ - {{ first }}, {{ second }}, {{ third }} - {% endfor %} - """ + func testLoopMetadata() { + 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(self.context)) == "1true2false3false" + } - let template = Template(templateString: templateString) - let result = try template.render(context) + 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(self.context)) == "1false2false3true" + } - try expect(result) == """ - 1, 2, 3 - 4, 5, 6 + 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(self.context)) == "112233" + } - """ - } + 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(self.context)) == "102132" + } - $0.it("can iterate with less number of variables") { - let templateString = """ - {% for first,second in tuples %}\ - {{ first }}, {{ second }} - {% endfor %} - """ + 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(self.context)) == "132333" + } + } - let template = Template(templateString: templateString) - let result = try template.render(context) + func testWhereExpression() { + it("renders the given nodes while filtering items using where expression") { + let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] + let parser = TokenParser(tokens: [], environment: Environment()) + let `where` = try parser.compileExpression(components: ["item", ">", "1"], token: .text(value: "", at: .unknown)) + let node = ForNode( + resolvable: Variable("items"), + loopVariables: ["item"], + nodes: nodes, + emptyNodes: [], + where: `where` + ) + try expect(try node.render(self.context)) == "2132" + } - try expect(result) == """ - 1, 2 - 4, 5 + 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 parser = TokenParser(tokens: [], environment: Environment()) + let `where` = try parser.compileExpression(components: ["item", "==", "0"], token: .text(value: "", at: .unknown)) + let node = ForNode( + resolvable: Variable("emptyItems"), + loopVariables: ["item"], + nodes: nodes, + emptyNodes: emptyNodes, + where: `where` + ) + try expect(try node.render(self.context)) == "empty" + } + } - """ - } + func testArrayOfTuples() { + it("can iterate over all tuple values") { + let template = Template(templateString: """ + {% for first,second,third in tuples %}\ + {{ first }}, {{ second }}, {{ third }} + {% endfor %} + """) + try expect(template.render(self.context)) == """ + 1, 2, 3 + 4, 5, 6 - $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) + it("can iterate with less number of variables") { + let template = Template(templateString: """ + {% for first,second in tuples %}\ + {{ first }}, {{ second }} + {% endfor %} + """) + try expect(template.render(self.context)) == """ + 1, 2 + 4, 5 - try expect(result) == """ - 1, 3 - 4, 6 + """ + } - """ - } + it("can use _ to skip variables") { + let template = Template(templateString: """ + {% for first,_,third in tuples %}\ + {{ first }}, {{ third }} + {% endfor %} + """) + try expect(template.render(self.context)) == """ + 1, 3 + 4, 6 - $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() - } + it("throws when number of variables is more than number of tuple values") { + let template = Template(templateString: """ + {% for key,value,smth in dict %}{% endfor %} + """) + try expect(template.render(self.context)).toThrow() + } + } - } - - $0.it("can iterate over dictionary") { - let templateString = """ + func testIterateDictionary() { + it("can iterate over dictionary") { + let template = Template(templateString: """ {% for key, value in dict %}\ {{ key }}: {{ value }},\ {% endfor %} - """ - - let template = Template(templateString: templateString) - let result = try template.render(context) - - try expect(result) == """ + """) + try expect(template.render(self.context)) == """ 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) + 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 + ) - try expect(result) == """ + try expect(node.render(self.context)) == """ 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) + it("renders supports iterating over dictionary with values") { + 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 + ) - try expect(result) == """ + try expect(node.render(self.context)) == """ 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 in [where ]`.", token: token) - try expect(try parser.parse()).toThrow(error) - } + func testIterateUsingMirroring() { + let nodes: [NodeType] = [ + VariableNode(variable: "label"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: "\n") + ] + let node = ForNode( + resolvable: Variable("item"), + loopVariables: ["label", "value"], + nodes: nodes, + emptyNodes: [] + ) - $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) == """ + it("can iterate over struct properties") { + let context = Context(dictionary: [ + "item": MyStruct(string: "abc", number: 123) + ]) + try expect(node.render(context)) == """ string=abc number=123 """ - } + } - $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) == """ + it("can iterate tuple items") { + let context = Context(dictionary: [ + "item": (one: 1, two: "dva") + ]) + try expect(node.render(context)) == """ 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) - } - } - - 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) == """ + it("can iterate over class properties") { + let context = Context(dictionary: [ + "item": MySubclass("child", "base", 1) + ]) + try expect(node.render(context)) == """ 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" - } + func testIterateRange() { + it("renders a context variable of type CountableClosedRange") { + let context = Context(dictionary: ["range": 1...3]) + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(context)) == "123" } + it("renders a context variable of type CountableRange") { + let context = Context(dictionary: ["range": 1..<4]) + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + + try expect(try node.render(context)) == "123" + } + + 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" + } + } + + func testHandleInvalidInput() throws { + 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 in [where ]`.", + token: token + ) + try expect(try parser.parse()).toThrow(error) } } -fileprivate struct Article { +private struct MyStruct { + let string: String + let number: Int +} + +private struct Article { let title: String let author: String } + +private class MyClass { + var baseString: String + var baseInt: Int + init(_ string: String, _ int: Int) { + baseString = string + baseInt = int + } +} + +private class MySubclass: MyClass { + var childString: String + init(_ childString: String, _ string: String, _ int: Int) { + self.childString = childString + super.init(string, int) + } +} diff --git a/Tests/StencilTests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift index 1141b26..e3b3838 100644 --- a/Tests/StencilTests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -1,289 +1,288 @@ -import XCTest import Spectre @testable import Stencil +import XCTest -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) - ] +private struct SomeType { + let value: String? = nil +} - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? IfNode +final class IfNodeTests: XCTestCase { + func testParseIf() { + 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 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 parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? IfNode - $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 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 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 or not (name and surname) or( some )and other )", 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") { - let tokens: [Token] = [ - .block(value: "if value|uppercase == \"TEST\"", 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 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("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" - } + it("can parse an if with complex expression") { + let tokens: [Token] = [ + .block(value: """ + if value == \"test\" and (not name or not (name and surname) or( some )and other ) + """, 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() } } + + func testParseIfWithElse() throws { + 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" + } + + func testParseIfWithElif() throws { + 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" + } + + func testParseIfWithElifWithoutElse() throws { + 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" + } + + func testParseMultipleElif() throws { + 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" + } + + func testParseIfnot() throws { + 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" + } + + func testParsingErrors() { + 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) + } + + 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) + } + } + + func testRendering() { + 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" + } + + 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" + } + + 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" + } + + 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())) == "" + } + } + + func testSupportVariableFilters() throws { + let tokens: [Token] = [ + .block(value: "if value|uppercase == \"TEST\"", 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 result = try renderNodes(nodes, Context(dictionary: ["value": "test"])) + try expect(result) == "true" + } + + func testEvaluatesNilAsFalse() throws { + 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() + + let result = try renderNodes(nodes, Context(dictionary: ["instance": SomeType()])) + try expect(result) == "" + } + + func testSupportsRangeVariables() throws { + 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" + } } diff --git a/Tests/StencilTests/IncludeSpec.swift b/Tests/StencilTests/IncludeSpec.swift index 1b587ae..d7d1ddf 100644 --- a/Tests/StencilTests/IncludeSpec.swift +++ b/Tests/StencilTests/IncludeSpec.swift @@ -1,72 +1,72 @@ -import XCTest +import PathKit import Spectre @testable import Stencil -import PathKit +import XCTest -class IncludeTests: XCTestCase { - func testInclude() { - describe("Include") { - let path = Path(#file) + ".." + "fixtures" - let loader = FileSystemLoader(paths: [path]) - let environment = Environment(loader: loader) +final class IncludeTests: XCTestCase { + let path = Path(#file) + ".." + "fixtures" + lazy var loader = FileSystemLoader(paths: [path]) + lazy var 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()) + func testParsing() { + 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()) + 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\"") - } + let nodes = try parser.parse() + let node = nodes.first as? IncludeNode + try expect(nodes.count) == 1 + try expect(node?.templateName) == Variable("\"test.html\"") + } + } + + func testRendering() { + 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" } + } - $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)) + it("throws an error when it cannot find the included template") { + let node = IncludeNode(templateName: Variable("\"unknown.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" - } - } - - $0.it("throws an error when it cannot find the included template") { - let node = IncludeNode(templateName: Variable("\"unknown.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() - } - } - - $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!" - } + do { + _ = try node.render(Context(environment: self.environment)) + } catch { + try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue() } } + + 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: self.environment) + let value = try node.render(context) + try expect(value) == "Hello World!" + } + + it("successfully passes context") { + let template = Template(templateString: """ + {% include "test.html" child %} + """) + let context = Context(dictionary: ["child": ["target": "World"]], environment: self.environment) + let value = try template.render(context) + try expect(value) == "Hello World!" + } } } diff --git a/Tests/StencilTests/InheritenceSpec.swift b/Tests/StencilTests/InheritenceSpec.swift index d859fb4..c71736a 100644 --- a/Tests/StencilTests/InheritenceSpec.swift +++ b/Tests/StencilTests/InheritenceSpec.swift @@ -1,38 +1,36 @@ -import XCTest +import PathKit import Spectre import Stencil -import PathKit +import XCTest -class InheritenceTests: XCTestCase { - func testInheritence() { - describe("Inheritence") { - let path = Path(#file) + ".." + "fixtures" - let loader = FileSystemLoader(paths: [path]) - let environment = Environment(loader: loader) +final class InheritanceTests: XCTestCase { + let path = Path(#file) + ".." + "fixtures" + lazy var loader = FileSystemLoader(paths: [path]) + lazy var environment = Environment(loader: loader) - $0.it("can inherit from another template") { - let template = try environment.loadTemplate(name: "child.html") - try expect(try template.render()) == """ + func testInheritance() { + it("can inherit from another template") { + let template = try self.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()) == """ + it("can inherit from another template inheriting from another template") { + let template = try self.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()) == """ + it("can inherit from a template that calls a super block") { + let template = try self.environment.loadTemplate(name: "child-super.html") + try expect(try template.render()) == """ Header Child_Body """ - } } } } diff --git a/Tests/StencilTests/LexerSpec.swift b/Tests/StencilTests/LexerSpec.swift index 872fb0e..9a5e880 100644 --- a/Tests/StencilTests/LexerSpec.swift +++ b/Tests/StencilTests/LexerSpec.swift @@ -3,126 +3,117 @@ import Spectre @testable import Stencil import XCTest -class LexerTests: XCTestCase { - func testLexer() { - describe("Lexer") { - func makeSourceMap(_ token: String, for lexer: Lexer, options: String.CompareOptions = []) -> SourceMap { - guard let range = lexer.templateString.range(of: token, options: options) else { fatalError("Token not found") } - return SourceMap(location: lexer.rangeLocation(range)) - } +final class LexerTests: XCTestCase { + func testText() throws { + let lexer = Lexer(templateString: "Hello World") + let tokens = lexer.tokenize() - $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: makeSourceMap("Hello World", for: lexer)) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .text(value: "Hello World", at: makeSourceMap("Hello World", for: lexer)) - } + func testComment() throws { + 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: makeSourceMap("Comment", for: lexer)) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .comment(value: "Comment", at: makeSourceMap("Comment", for: lexer)) - } + func testVariable() throws { + 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: makeSourceMap("Variable", for: lexer)) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .variable(value: "Variable", at: makeSourceMap("Variable", for: lexer)) - } + func testTokenWithoutSpaces() throws { + let lexer = Lexer(templateString: "{{Variable}}") + let tokens = lexer.tokenize() - $0.it("can tokenize a token without spaces") { - let lexer = Lexer(templateString: "{{Variable}}") - let tokens = lexer.tokenize() + try expect(tokens.count) == 1 + try expect(tokens.first) == .variable(value: "Variable", at: makeSourceMap("Variable", for: lexer)) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .variable(value: "Variable", at: makeSourceMap("Variable", for: lexer)) - } + func testUnclosedTag() throws { + 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: makeSourceMap("{{ thing", for: lexer)) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .text(value: "", at: makeSourceMap("{{ thing", for: lexer)) - } + func testContentMixture() throws { + 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: makeSourceMap("My name is ", for: lexer)) + try expect(tokens[1]) == Token.variable(value: "myname", at: makeSourceMap("myname", for: lexer)) + try expect(tokens[2]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer)) + } - try expect(tokens.count) == 3 - try expect(tokens[0]) == Token.text(value: "My name is ", at: makeSourceMap("My name is ", for: lexer)) - try expect(tokens[1]) == Token.variable(value: "myname", at: makeSourceMap("myname", for: lexer)) - try expect(tokens[2]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer)) - } + func testVariablesWithoutBeingGreedy() throws { + 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: makeSourceMap("thing", for: lexer)) + try expect(tokens[1]) == Token.variable(value: "name", at: makeSourceMap("name", for: lexer)) + } - try expect(tokens.count) == 2 - try expect(tokens[0]) == Token.variable(value: "thing", at: makeSourceMap("thing", for: lexer)) - try expect(tokens[1]) == Token.variable(value: "name", at: makeSourceMap("name", for: lexer)) - } + func testUnclosedBlock() throws { + let lexer = Lexer(templateString: "{%}") + _ = lexer.tokenize() + } - $0.it("can tokenize an unclosed block") { - let lexer = Lexer(templateString: "{%}") - _ = lexer.tokenize() - } + func testTokenizeIncorrectSyntaxWithoutCrashing() throws { + let lexer = Lexer(templateString: "func some() {{% if %}") + _ = lexer.tokenize() + } - $0.it("can tokenize incorrect syntax without crashing") { - let lexer = Lexer(templateString: "func some() {{% if %}") - _ = lexer.tokenize() - } + func testEmptyVariable() throws { + let lexer = Lexer(templateString: "{{}}") + _ = lexer.tokenize() + } - $0.it("can tokenize an empty variable") { - let lexer = Lexer(templateString: "{{}}") - _ = lexer.tokenize() - } - - $0.it("can tokenize with new lines") { - let templateString = """ - My name is {% - if name - and - name - %}{{ + func testNewlines() throws { + let templateString = """ + My name is {% + if name + and name - }}{% - endif %}. - """ - let lexer = Lexer(templateString: templateString) - let tokens = lexer.tokenize() + %}{{ + name + }}{% + 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: makeSourceMap("My name is", for: lexer)) - try expect(tokens[1]) == Token.block(value: "if name and name", at: makeSourceMap("{%", for: lexer)) - try expect(tokens[2]) == Token.variable(value: "name", at: makeSourceMap("name", for: lexer, options: .backwards)) - try expect(tokens[3]) == Token.block(value: "endif", at: makeSourceMap("endif", for: lexer)) - try expect(tokens[4]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer)) - } + try expect(tokens.count) == 5 + try expect(tokens[0]) == Token.text(value: "My name is ", at: makeSourceMap("My name is", for: lexer)) + try expect(tokens[1]) == Token.block(value: "if name and name", at: makeSourceMap("{%", for: lexer)) + try expect(tokens[2]) == Token.variable(value: "name", at: makeSourceMap("name", for: lexer, options: .backwards)) + try expect(tokens[3]) == Token.block(value: "endif", at: makeSourceMap("endif", for: lexer)) + try expect(tokens[4]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer)) + } - $0.it("can tokenize escape sequences") { - let templateString = "class Some {{ '{' }}{% if true %}{{ stuff }}{% endif %}" - let lexer = Lexer(templateString: templateString) - let tokens = lexer.tokenize() + func testEscapeSequence() throws { + let templateString = "class Some {{ '{' }}{% if true %}{{ stuff }}{% endif %}" + let lexer = Lexer(templateString: templateString) + let tokens = lexer.tokenize() - try expect(tokens.count) == 5 - try expect(tokens[0]) == Token.text(value: "class Some ", at: makeSourceMap("class Some ", for: lexer)) - try expect(tokens[1]) == Token.variable(value: "'{'", at: makeSourceMap("'{'", for: lexer)) - try expect(tokens[2]) == Token.block(value: "if true", at: makeSourceMap("if true", for: lexer)) - try expect(tokens[3]) == Token.variable(value: "stuff", at: makeSourceMap("stuff", for: lexer)) - try expect(tokens[4]) == Token.block(value: "endif", at: makeSourceMap("endif", for: lexer)) - } - } + try expect(tokens.count) == 5 + try expect(tokens[0]) == Token.text(value: "class Some ", at: makeSourceMap("class Some ", for: lexer)) + try expect(tokens[1]) == Token.variable(value: "'{'", at: makeSourceMap("'{'", for: lexer)) + try expect(tokens[2]) == Token.block(value: "if true", at: makeSourceMap("if true", for: lexer)) + try expect(tokens[3]) == Token.variable(value: "stuff", at: makeSourceMap("stuff", for: lexer)) + try expect(tokens[4]) == Token.block(value: "endif", at: makeSourceMap("endif", for: lexer)) } func testPerformance() throws { @@ -134,4 +125,9 @@ class LexerTests: XCTestCase { _ = lexer.tokenize() } } + + private func makeSourceMap(_ token: String, for lexer: Lexer, options: String.CompareOptions = []) -> SourceMap { + guard let range = lexer.templateString.range(of: token, options: options) else { fatalError("Token not found") } + return SourceMap(location: lexer.rangeLocation(range)) + } } diff --git a/Tests/StencilTests/LoaderSpec.swift b/Tests/StencilTests/LoaderSpec.swift index 91d541f..00a1f43 100644 --- a/Tests/StencilTests/LoaderSpec.swift +++ b/Tests/StencilTests/LoaderSpec.swift @@ -1,57 +1,55 @@ -import XCTest +import PathKit import Spectre import Stencil -import PathKit +import XCTest -class TemplateLoaderTests: XCTestCase { - func testTemplateLoader() { - describe("FileSystemLoader") { - let path = Path(#file) + ".." + "fixtures" - let loader = FileSystemLoader(paths: [path]) - let environment = Environment(loader: loader) +final class TemplateLoaderTests: XCTestCase { + func testFileSystemLoader() { + 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 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() - } + it("errors when a template cannot be found") { + try expect(try environment.loadTemplate(name: "unknown.html")).toThrow() } - describe("DictionaryLoader") { - let loader = DictionaryLoader(templates: [ + it("errors when an array of templates cannot be found") { + try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow() + } + + it("can load a template from a file") { + _ = try environment.loadTemplate(name: "test.html") + } + + it("errors when loading absolute file outside of the selected path") { + try expect(try environment.loadTemplate(name: "/etc/hosts")).toThrow() + } + + it("errors when loading relative file outside of the selected path") { + try expect(try environment.loadTemplate(name: "../LoaderSpec.swift")).toThrow() + } + } + + func testDictionaryLoader() { + 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() - } + 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() - } + 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") - } + 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"]) - } + it("can load a known template from a collection of templates") { + _ = try environment.loadTemplate(names: ["unknown.html", "index.html"]) } } } diff --git a/Tests/StencilTests/NodeSpec.swift b/Tests/StencilTests/NodeSpec.swift index 1987282..0a67cf5 100644 --- a/Tests/StencilTests/NodeSpec.swift +++ b/Tests/StencilTests/NodeSpec.swift @@ -1,8 +1,8 @@ -import XCTest import Spectre @testable import Stencil +import XCTest -class ErrorNode : NodeType { +class ErrorNode: NodeType { let token: Token? init(token: Token? = nil) { self.token = token @@ -13,54 +13,50 @@ class ErrorNode : NodeType { } } -class NodeTests: XCTestCase { - func testNode() { - describe("Node") { - let context = Context(dictionary: [ - "name": "Kyle", - "age": 27, - "items": [1, 2, 3], - ]) +final class NodeTests: XCTestCase { + 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" - } - } + func testTextNode() { + it("renders the given text") { + let node = TextNode(text: "Hello World") + try expect(try node.render(self.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" - } + func testVariableNode() { + it("resolves and renders the variable") { + let node = VariableNode(variable: Variable("name")) + try expect(try node.render(self.context)) == "Kyle" + } - $0.it("resolves and renders a non string variable") { - let node = VariableNode(variable: Variable("age")) - try expect(try node.render(context)) == "27" - } - } + it("resolves and renders a non string variable") { + let node = VariableNode(variable: Variable("age")) + try expect(try node.render(self.context)) == "27" + } + } - $0.describe("rendering nodes") { - $0.it("renders the nodes") { - let nodes: [NodeType] = [ - TextNode(text:"Hello "), - VariableNode(variable: "name"), - ] + func testRendering() { + it("renders the nodes") { + let nodes: [NodeType] = [ + TextNode(text: "Hello "), + VariableNode(variable: "name") + ] - try expect(try renderNodes(nodes, context)) == "Hello Kyle" - } + try expect(try renderNodes(nodes, self.context)) == "Hello Kyle" + } - $0.it("correctly throws a nodes failure") { - let nodes: [NodeType] = [ - TextNode(text:"Hello "), - VariableNode(variable: "name"), - ErrorNode(), - ] + 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")) - } - } + try expect(try renderNodes(nodes, self.context)).toThrow(TemplateSyntaxError("Custom Error")) } } } diff --git a/Tests/StencilTests/NowNodeSpec.swift b/Tests/StencilTests/NowNodeSpec.swift index e9a2a62..7e5d23f 100644 --- a/Tests/StencilTests/NowNodeSpec.swift +++ b/Tests/StencilTests/NowNodeSpec.swift @@ -1,46 +1,50 @@ -import XCTest -import Foundation import Spectre @testable import Stencil +import XCTest +final class NowNodeTests: XCTestCase { + func testParsing() { + it("parses default format without any now arguments") { +#if os(Linux) + throw skip() +#else + 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\"" - } - - $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 - } - } + 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\"" +#endif + } + + it("parses now with a format") { +#if os(Linux) + throw skip() +#else + 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\"" +#endif + } + } + + func testRendering() { + it("renders the date") { +#if os(Linux) + throw skip() +#else + 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 } } diff --git a/Tests/StencilTests/ParserSpec.swift b/Tests/StencilTests/ParserSpec.swift index e8650d3..ce5f8c0 100644 --- a/Tests/StencilTests/ParserSpec.swift +++ b/Tests/StencilTests/ParserSpec.swift @@ -1,63 +1,64 @@ -import XCTest import Spectre @testable import Stencil +import XCTest -class TokenParserTests: XCTestCase { +final 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()) + 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" + try expect(nodes.count) == 1 + try expect(node?.text) == "Hello World" + } + + 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" + } + + 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 + } + + it("can parse a tag token") { + let simpleExtension = Extension() + simpleExtension.registerSimpleTag("known") { _ in + "" } - $0.it("can parse a variable token") { - let parser = TokenParser(tokens: [ - .variable(value: "'name'", at: .unknown) - ], environment: Environment()) + let parser = TokenParser(tokens: [ + .block(value: "known", at: .unknown) + ], environment: Environment(extensions: [simpleExtension])) - 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" - } + let nodes = try parser.parse() + try expect(nodes.count) == 1 + } - $0.it("can parse a comment token") { - let parser = TokenParser(tokens: [ - .comment(value: "Secret stuff!", at: .unknown) - ], environment: Environment()) + it("errors when parsing an unknown tag") { + let tokens: [Token] = [.block(value: "unknown", at: .unknown)] + let parser = TokenParser(tokens: tokens, 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 "" - } - - 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)) - } + try expect(try parser.parse()).toThrow(TemplateSyntaxError( + reason: "Unknown template tag 'unknown'", + token: tokens.first) + ) } } } diff --git a/Tests/StencilTests/StencilSpec.swift b/Tests/StencilTests/StencilSpec.swift index 5ec6094..dc8aa6e 100644 --- a/Tests/StencilTests/StencilSpec.swift +++ b/Tests/StencilTests/StencilSpec.swift @@ -1,72 +1,68 @@ -import XCTest import Spectre import Stencil +import XCTest -fileprivate struct CustomNode : NodeType { +private struct CustomNode: NodeType { let token: Token? - func render(_ context:Context) throws -> String { + func render(_ context: Context) throws -> String { return "Hello World" } } -fileprivate struct Article { +private struct Article { let title: String let author: String } -class StencilTests: XCTestCase { +final class StencilTests: XCTestCase { + lazy var environment: Environment = { + let exampleExtension = Extension() + exampleExtension.registerSimpleTag("simpletag") { _ in + "Hello World" + } + exampleExtension.registerTag("customtag") { _, token in + CustomNode(token: token) + } + return Environment(extensions: [exampleExtension]) + }() + func testStencil() { - describe("Stencil") { - let exampleExtension = Extension() + it("can render the README example") { + let templateString = """ + There are {{ articles.count }} articles. - exampleExtension.registerSimpleTag("simpletag") { context in - return "Hello World" - } + {% for article in articles %}\ + - {{ article.title }} by {{ article.author }}. + {% endfor %} + """ - exampleExtension.registerTag("customtag") { parser, token in - return CustomNode(token: token) - } - - let environment = Environment(extensions: [exampleExtension]) - - $0.it("can render the README example") { - - let templateString = """ - There are {{ articles.count }} articles. - - {% 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" - } + it("can render a custom template tag") { + let result = try self.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" - } + it("can render a simple custom tag") { + let result = try self.environment.renderTemplate(string: "{% simpletag %}") + try expect(result) == "Hello World" } } } diff --git a/Tests/StencilTests/TemplateSpec.swift b/Tests/StencilTests/TemplateSpec.swift index 3d3001b..b1c66a1 100644 --- a/Tests/StencilTests/TemplateSpec.swift +++ b/Tests/StencilTests/TemplateSpec.swift @@ -1,22 +1,19 @@ -import XCTest import Spectre @testable import Stencil +import XCTest -class TemplateTests: XCTestCase { +final 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" - } - - $0.it("can render a template from a string literal") { + 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" + } + + 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" - } - } } } diff --git a/Tests/StencilTests/TokenSpec.swift b/Tests/StencilTests/TokenSpec.swift index ee4be2b..effa884 100644 --- a/Tests/StencilTests/TokenSpec.swift +++ b/Tests/StencilTests/TokenSpec.swift @@ -1,36 +1,34 @@ -import XCTest import Spectre @testable import Stencil +import XCTest -class TokenTests: XCTestCase { +final 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 + 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 + 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 + 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\"" } } } diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index 2f5933d..6445deb 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -1,411 +1,355 @@ -import XCTest -import Foundation import Spectre @testable import Stencil - +import XCTest #if os(OSX) -@objc class Superclass: NSObject { +@objc +class Superclass: NSObject { @objc let name = "Foo" } -@objc class Object : Superclass { +@objc +class Object: Superclass { @objc let title = "Hello World" } #endif -fileprivate struct Person { +private struct Person { let name: String } -fileprivate struct Article { +private struct Article { let author: Person } -fileprivate class WebSite { +private class WebSite { let url: String = "blog.com" } -fileprivate class Blog: WebSite { +private class Blog: WebSite { let articles: [Article] = [Article(author: Person(name: "Kyle"))] let featuring: Article? = Article(author: Person(name: "Jhon")) } -class VariableTests: XCTestCase { +final class VariableTests: XCTestCase { + let context: Context = { + let ext = Extension() + ext.registerFilter("incr") { arg in + (arg.flatMap { toNumber(value: $0) } ?? 0) + 1 + } + let environment = Environment(extensions: [ext]) + + var context = Context(dictionary: [ + "name": "Kyle", + "contacts": ["Katie", "Carlton"], + "profiles": [ + "github": "kylef" + ], + "counter": [ + "count": "kylef" + ], + "article": Article(author: Person(name: "Kyle")), + "blog": Blog(), + "tuple": (one: 1, two: 2) + ], environment: environment) +#if os(OSX) + context["object"] = Object() +#endif + return context + }() + + func testLiterals() { + it("can resolve a string literal with double quotes") { + let variable = Variable("\"name\"") + let result = try variable.resolve(self.context) as? String + try expect(result) == "name" + } + + it("can resolve a string literal with single quotes") { + let variable = Variable("'name'") + let result = try variable.resolve(self.context) as? String + try expect(result) == "name" + } + + it("can resolve an integer literal") { + let variable = Variable("5") + let result = try variable.resolve(self.context) as? Int + try expect(result) == 5 + } + + it("can resolve an float literal") { + let variable = Variable("3.14") + let result = try variable.resolve(self.context) as? Number + try expect(result) == 3.14 + } + + it("can resolve boolean literal") { + try expect(Variable("true").resolve(self.context) as? Bool) == true + try expect(Variable("false").resolve(self.context) as? Bool) == false + try expect(Variable("0").resolve(self.context) as? Int) == 0 + try expect(Variable("1").resolve(self.context) as? Int) == 1 + } + } + func testVariable() { - describe("Variable") { - let context = Context(dictionary: [ - "name": "Kyle", - "contacts": ["Katie", "Carlton"], - "profiles": [ - "github": "kylef", - ], - "counter": [ - "count": "kylef", - ], - "article": Article(author: Person(name: "Kyle")), - "tuple": (one: 1, two: 2) - ]) + it("can resolve a string variable") { + let variable = Variable("name") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Kyle" + } + } - #if os(OSX) - context["object"] = Object() - #endif - context["blog"] = Blog() + func testDictionary() { + it("can resolve an item from a dictionary") { + let variable = Variable("profiles.github") + let result = try variable.resolve(self.context) as? String + try expect(result) == "kylef" + } - $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" - } + it("can get the count of a dictionary") { + let variable = Variable("profiles.count") + let result = try variable.resolve(self.context) as? Int + try expect(result) == 1 + } + } - $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" - } + func testArray() { + it("can resolve an item from an array via it's index") { + let variable = Variable("contacts.0") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Katie" - $0.it("can resolve an integer literal") { - let variable = Variable("5") - let result = try variable.resolve(context) as? Int - try expect(result) == 5 - } + let variable1 = Variable("contacts.1") + let result1 = try variable1.resolve(self.context) as? String + try expect(result1) == "Carlton" + } - $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 - } + it("can resolve an item from an array via unknown index") { + let variable = Variable("contacts.5") + let result = try variable.resolve(self.context) as? String + try expect(result).to.beNil() - $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 - } + let variable1 = Variable("contacts.-5") + let result1 = try variable1.resolve(self.context) as? String + try expect(result1).to.beNil() + } - $0.it("can resolve a string variable") { - let variable = Variable("name") - let result = try variable.resolve(context) as? String + it("can resolve the first item from an array") { + let variable = Variable("contacts.first") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Katie" + } + + it("can resolve the last item from an array") { + let variable = Variable("contacts.last") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Carlton" + } + } + + func testReflection() { + it("can resolve a property with reflection") { + let variable = Variable("article.author.name") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Kyle" + } + + it("can resolve a value via reflection") { + let variable = Variable("blog.articles.0.author.name") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Kyle" + } + + it("can resolve a superclass value via reflection") { + let variable = Variable("blog.url") + let result = try variable.resolve(self.context) as? String + try expect(result) == "blog.com" + } + + it("can resolve optional variable property using reflection") { + let variable = Variable("blog.featuring.author.name") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Jhon" + } + } + + func testKVO() { +#if os(OSX) + it("can resolve a value via KVO") { + let variable = Variable("object.title") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Hello World" + } + + it("can resolve a superclass value via KVO") { + let variable = Variable("object.name") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Foo" + } + + it("does not crash on KVO") { + let variable = Variable("object.fullname") + let result = try variable.resolve(self.context) as? String + try expect(result).to.beNil() + } +#endif + } + + func testTuple() { + it("can resolve tuple by index") { + let variable = Variable("tuple.0") + let result = try variable.resolve(self.context) as? Int + try expect(result) == 1 + } + + it("can resolve tuple by label") { + let variable = Variable("tuple.two") + let result = try variable.resolve(self.context) as? Int + try expect(result) == 2 + } + } + + func testOptional() { + 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)) == "" + } + } + + func testSubscripting() { + it("can resolve a property subscript via reflection") { + try self.context.push(dictionary: ["property": "name"]) { + let variable = Variable("article.author[property]") + let result = try variable.resolve(self.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 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 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") - let result = try variable.resolve(context) as? Int - try expect(result) == 4 - } + it("can subscript an array with a valid index") { + try self.context.push(dictionary: ["property": 0]) { + let variable = Variable("contacts[property]") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Katie" } + } - $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 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 + it("can subscript an array with an unknown index") { + try self.context.push(dictionary: ["property": 5]) { + let variable = Variable("contacts[property]") + let result = try variable.resolve(self.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" +#if os(OSX) + it("can resolve a subscript via KVO") { + try self.context.push(dictionary: ["property": "name"]) { + let variable = Variable("object[property]") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Foo" } + } +#endif - $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 + it("can resolve an optional subscript via reflection") { + try self.context.push(dictionary: ["property": "featuring"]) { + let variable = Variable("blog[property].author.name") + let result = try variable.resolve(self.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") { - - 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) - return try RangeVariable(token.contents, environment: context.environment, 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() - } - - } - - 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() - } + } + } + + func testMultipleSubscripting() { + it("can resolve multiple subscripts") { + try self.context.push(dictionary: [ + "prop1": "articles", + "prop2": 0, + "prop3": "name" + ]) { + let variable = Variable("blog[prop1][prop2].author[prop3]") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Kyle" + } + } + + it("can resolve nested subscripts") { + try self.context.push(dictionary: [ + "prop1": "prop2", + "ref": ["prop2": "name"] + ]) { + let variable = Variable("article.author[ref[prop1]]") + let result = try variable.resolve(self.context) as? String + try expect(result) == "Kyle" + } + } + + it("throws for invalid keypath syntax") { + try self.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(self.context)).toThrow() + } + } + } + } + + func testRangeVariable() { + func makeVariable(_ token: String) throws -> RangeVariable? { + let token = Token.variable(value: token, at: .unknown) + return try RangeVariable(token.contents, environment: context.environment, containedIn: token) + } + + it("can resolve closed range as array") { + let result = try makeVariable("1...3")?.resolve(self.context) as? [Int] + try expect(result) == [1, 2, 3] + } + + it("can resolve decreasing closed range as reversed array") { + let result = try makeVariable("3...1")?.resolve(self.context) as? [Int] + try expect(result) == [3, 2, 1] + } + + it("can use filter on range variables") { + let result = try makeVariable("1|incr...3|incr")?.resolve(self.context) as? [Int] + try expect(result) == [2, 3, 4] + } + + 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() + } + + 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() + } + + it("throws is left range value is missing") { + try expect(makeVariable("...1")).toThrow() + } + + it("throws is right range value is missing") { + try expect(makeVariable("1...")).toThrow() } } } diff --git a/Tests/StencilTests/XCTestManifests.swift b/Tests/StencilTests/XCTestManifests.swift index 73cf026..b31dca7 100644 --- a/Tests/StencilTests/XCTestManifests.swift +++ b/Tests/StencilTests/XCTestManifests.swift @@ -2,19 +2,54 @@ import XCTest extension ContextTests { static let __allTests = [ - ("testContext", testContext), + ("testContextRestoration", testContextRestoration), + ("testContextSubscripting", testContextSubscripting), + ] +} + +extension EnvironmentBaseAndChildTemplateTests { + static let __allTests = [ + ("testRuntimeErrorInBaseTemplate", testRuntimeErrorInBaseTemplate), + ("testRuntimeErrorInChildTemplate", testRuntimeErrorInChildTemplate), + ("testSyntaxErrorInBaseTemplate", testSyntaxErrorInBaseTemplate), + ("testSyntaxErrorInChildTemplate", testSyntaxErrorInChildTemplate), + ] +} + +extension EnvironmentIncludeTemplateTests { + static let __allTests = [ + ("testRuntimeError", testRuntimeError), + ("testSyntaxError", testSyntaxError), ] } extension EnvironmentTests { static let __allTests = [ - ("testEnvironment", testEnvironment), + ("testLoading", testLoading), + ("testRendering", testRendering), + ("testRenderingError", testRenderingError), + ("testSyntaxError", testSyntaxError), + ("testUnknownFilter", testUnknownFilter), ] } extension ExpressionsTests { static let __allTests = [ - ("testExpressions", testExpressions), + ("testAndExpression", testAndExpression), + ("testEqualityExpression", testEqualityExpression), + ("testExpressionParsing", testExpressionParsing), + ("testFalseExpressions", testFalseExpressions), + ("testFalseInExpression", testFalseInExpression), + ("testInequalityExpression", testInequalityExpression), + ("testLessThanEqualExpression", testLessThanEqualExpression), + ("testLessThanExpression", testLessThanExpression), + ("testMoreThanEqualExpression", testMoreThanEqualExpression), + ("testMoreThanExpression", testMoreThanExpression), + ("testMultipleExpressions", testMultipleExpressions), + ("testNotExpression", testNotExpression), + ("testOrExpression", testOrExpression), + ("testTrueExpressions", testTrueExpressions), + ("testTrueInExpression", testTrueInExpression), ] } @@ -26,50 +61,95 @@ extension FilterTagTests { extension FilterTests { static let __allTests = [ - ("testFilter", testFilter), + ("testDefaultFilter", testDefaultFilter), + ("testDynamicFilters", testDynamicFilters), + ("testFilterSuggestion", testFilterSuggestion), + ("testIndentContent", testIndentContent), + ("testIndentFirstLine", testIndentFirstLine), + ("testIndentNotEmptyLines", testIndentNotEmptyLines), + ("testIndentWithArbitraryCharacter", testIndentWithArbitraryCharacter), + ("testJoinFilter", testJoinFilter), + ("testRegistration", testRegistration), + ("testRegistrationOverrideDefault", testRegistrationOverrideDefault), + ("testRegistrationWithArguments", testRegistrationWithArguments), + ("testSplitFilter", testSplitFilter), + ("testStringFilters", testStringFilters), + ("testStringFiltersWithArrays", testStringFiltersWithArrays), ] } extension ForNodeTests { static let __allTests = [ + ("testArrayOfTuples", testArrayOfTuples), ("testForNode", testForNode), + ("testHandleInvalidInput", testHandleInvalidInput), + ("testIterateDictionary", testIterateDictionary), + ("testIterateRange", testIterateRange), + ("testIterateUsingMirroring", testIterateUsingMirroring), + ("testLoopMetadata", testLoopMetadata), + ("testWhereExpression", testWhereExpression), ] } extension IfNodeTests { static let __allTests = [ - ("testIfNode", testIfNode), + ("testEvaluatesNilAsFalse", testEvaluatesNilAsFalse), + ("testParseIf", testParseIf), + ("testParseIfnot", testParseIfnot), + ("testParseIfWithElif", testParseIfWithElif), + ("testParseIfWithElifWithoutElse", testParseIfWithElifWithoutElse), + ("testParseIfWithElse", testParseIfWithElse), + ("testParseMultipleElif", testParseMultipleElif), + ("testParsingErrors", testParsingErrors), + ("testRendering", testRendering), + ("testSupportsRangeVariables", testSupportsRangeVariables), + ("testSupportVariableFilters", testSupportVariableFilters), ] } extension IncludeTests { static let __allTests = [ - ("testInclude", testInclude), + ("testParsing", testParsing), + ("testRendering", testRendering), ] } -extension InheritenceTests { +extension InheritanceTests { static let __allTests = [ - ("testInheritence", testInheritence), + ("testInheritance", testInheritance), ] } extension LexerTests { static let __allTests = [ - ("testLexer", testLexer), + ("testComment", testComment), + ("testContentMixture", testContentMixture), + ("testEmptyVariable", testEmptyVariable), + ("testEscapeSequence", testEscapeSequence), + ("testNewlines", testNewlines), ("testPerformance", testPerformance), + ("testText", testText), + ("testTokenizeIncorrectSyntaxWithoutCrashing", testTokenizeIncorrectSyntaxWithoutCrashing), + ("testTokenWithoutSpaces", testTokenWithoutSpaces), + ("testUnclosedBlock", testUnclosedBlock), + ("testUnclosedTag", testUnclosedTag), + ("testVariable", testVariable), + ("testVariablesWithoutBeingGreedy", testVariablesWithoutBeingGreedy), ] } extension NodeTests { static let __allTests = [ - ("testNode", testNode), + ("testRendering", testRendering), + ("testTextNode", testTextNode), + ("testVariableNode", testVariableNode), ] } extension NowNodeTests { static let __allTests = [ - ("testNowNode", testNowNode), + ("testParsing", testParsing), + ("testRendering", testRendering), ] } @@ -81,7 +161,8 @@ extension StencilTests { extension TemplateLoaderTests { static let __allTests = [ - ("testTemplateLoader", testTemplateLoader), + ("testDictionaryLoader", testDictionaryLoader), + ("testFileSystemLoader", testFileSystemLoader), ] } @@ -105,6 +186,16 @@ extension TokenTests { extension VariableTests { static let __allTests = [ + ("testArray", testArray), + ("testDictionary", testDictionary), + ("testKVO", testKVO), + ("testLiterals", testLiterals), + ("testMultipleSubscripting", testMultipleSubscripting), + ("testOptional", testOptional), + ("testRangeVariable", testRangeVariable), + ("testReflection", testReflection), + ("testSubscripting", testSubscripting), + ("testTuple", testTuple), ("testVariable", testVariable), ] } @@ -113,6 +204,8 @@ extension VariableTests { public func __allTests() -> [XCTestCaseEntry] { return [ testCase(ContextTests.__allTests), + testCase(EnvironmentBaseAndChildTemplateTests.__allTests), + testCase(EnvironmentIncludeTemplateTests.__allTests), testCase(EnvironmentTests.__allTests), testCase(ExpressionsTests.__allTests), testCase(FilterTagTests.__allTests), @@ -120,7 +213,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(ForNodeTests.__allTests), testCase(IfNodeTests.__allTests), testCase(IncludeTests.__allTests), - testCase(InheritenceTests.__allTests), + testCase(InheritanceTests.__allTests), testCase(LexerTests.__allTests), testCase(NodeTests.__allTests), testCase(NowNodeTests.__allTests),