From f7bda226e81de06b330ec5c1e341601769d42d3f Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sun, 23 Sep 2018 03:46:27 +0300 Subject: [PATCH] Update to Spectre 0.9.0 (#247) * update to Spectre 0.9.0 * fix variable spec tests * fix flatMap warning * updated CHANGELOG --- .gitignore | 1 - CHANGELOG.md | 4 +- Package.resolved | 25 + Package.swift | 2 +- Sources/IfTag.swift | 2 +- Tests/LinuxMain.swift | 7 +- Tests/StencilTests/ContextSpec.swift | 116 ++-- Tests/StencilTests/EnvironmentSpec.swift | 570 ++++++++++---------- Tests/StencilTests/ExpressionSpec.swift | 634 +++++++++++----------- Tests/StencilTests/FilterSpec.swift | 626 +++++++++++----------- Tests/StencilTests/FilterTagSpec.swift | 60 ++- Tests/StencilTests/ForNodeSpec.swift | 476 ++++++++-------- Tests/StencilTests/IfNodeSpec.swift | 518 +++++++++--------- Tests/StencilTests/IncludeSpec.swift | 100 ++-- Tests/StencilTests/InheritenceSpec.swift | 36 +- Tests/StencilTests/LexerSpec.swift | 128 ++--- Tests/StencilTests/LoaderSpec.swift | 80 +-- Tests/StencilTests/NodeSpec.swift | 81 +-- Tests/StencilTests/NowNodeSpec.swift | 65 +-- Tests/StencilTests/ParserSpec.swift | 98 ++-- Tests/StencilTests/StencilSpec.swift | 80 +-- Tests/StencilTests/TemplateSpec.swift | 24 +- Tests/StencilTests/TokenSpec.swift | 48 +- Tests/StencilTests/VariableSpec.swift | 655 ++++++++++++----------- Tests/StencilTests/XCTest.swift | 30 -- Tests/StencilTests/XCTestManifests.swift | 134 +++++ 26 files changed, 2386 insertions(+), 2214 deletions(-) create mode 100644 Package.resolved delete mode 100644 Tests/StencilTests/XCTest.swift create mode 100644 Tests/StencilTests/XCTestManifests.swift diff --git a/.gitignore b/.gitignore index 2dcf7f5..088c653 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .conche/ .build/ Packages/ -Package.resolved Package.pins *.xcodeproj diff --git a/CHANGELOG.md b/CHANGELOG.md index 11c1762..02fb49b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,6 @@ - Now you can conditionally render variables with `{{ variable if condition }}`, which is a shorthand for `{% if condition %}{{ variable }}{% endif %}`. You can also use `else` like `{{ variable1 if condition else variable2 }}`, which is a shorthand for `{% if condition %}{{ variable1 }}{% else %}{{ variable2 }}{% endif %}` [Ilya Puchka](https://github.com/ilyapuchka) [#243](https://github.com/stencilproject/Stencil/pull/243) - - Now you can access string characters by index or get string length the same was as if it was an array, i.e. `{{ 'string'.first }}`, `{{ 'string'.last }}`, `{{ 'string'.1 }}`, `{{ 'string'.count }}`. [Ilya Puchka](https://github.com/ilyapuchka) [#245](https://github.com/stencilproject/Stencil/pull/245) @@ -35,6 +34,9 @@ - Updated the codebase to use Swift 4 features. [David Jennes](https://github.com/djbe) [#239](https://github.com/stencilproject/Stencil/pull/239) +- Update to Spectre 0.9.0. + [Ilya Puchka](https://github.com/ilyapuchka) + [#247](https://github.com/stencilproject/Stencil/pull/247) ## 0.12.1 diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..ff6830b --- /dev/null +++ b/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "PathKit", + "repositoryURL": "https://github.com/kylef/PathKit.git", + "state": { + "branch": null, + "revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0", + "version": "0.9.2" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", + "version": "0.9.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 01cf4c6..a0b49da 100644 --- a/Package.swift +++ b/Package.swift @@ -8,7 +8,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/kylef/PathKit.git", from: "0.9.0"), - .package(url: "https://github.com/kylef/Spectre.git", from: "0.8.0"), + .package(url: "https://github.com/kylef/Spectre.git", from: "0.9.0"), ], targets: [ .target(name: "Stencil", dependencies: [ diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index 997f4fb..7376054 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -118,7 +118,7 @@ final class IfExpressionParser { private init(components: ArraySlice, tokenParser: TokenParser, token: Token) throws { var parsedComponents = Set() var bracketsBalance = 0 - self.tokens = try zip(components.indices, components).flatMap { (index, component) in + self.tokens = try zip(components.indices, components).compactMap { (index, component) in guard !parsedComponents.contains(index) else { return nil } if component == "(" { diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 294b797..170a024 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,3 +1,8 @@ +import XCTest + import StencilTests -stencilTests() +var tests = [XCTestCaseEntry]() +tests += StencilTests.__allTests() + +XCTMain(tests) diff --git a/Tests/StencilTests/ContextSpec.swift b/Tests/StencilTests/ContextSpec.swift index d33e1d8..7e20cbd 100644 --- a/Tests/StencilTests/ContextSpec.swift +++ b/Tests/StencilTests/ContextSpec.swift @@ -1,80 +1,84 @@ +import XCTest import Spectre @testable import Stencil -func testContext() { - describe("Context") { - var context: Context! +class ContextTests: XCTestCase { + + func testContext() { + describe("Context") { + var context: Context! - $0.before { - context = Context(dictionary: ["name": "Kyle"]) - } + $0.before { + context = Context(dictionary: ["name": "Kyle"]) + } - $0.it("allows you to get a value via subscripting") { - try expect(context["name"] as? String) == "Kyle" - } - - $0.it("allows you to set a value via subscripting") { - context["name"] = "Katie" - - try expect(context["name"] as? String) == "Katie" - } - - $0.it("allows you to remove a value via subscripting") { - context["name"] = nil - - try expect(context["name"]).to.beNil() - } - - $0.it("allows you to retrieve a value from a parent") { - try context.push { + $0.it("allows you to get a value via subscripting") { try expect(context["name"] as? String) == "Kyle" } - } - $0.it("allows you to override a parent's value") { - try context.push { + $0.it("allows you to set a value via subscripting") { context["name"] = "Katie" + try expect(context["name"] as? String) == "Katie" } - } - $0.it("allows you to pop to restore previous state") { - context.push { - context["name"] = "Katie" - } - - try expect(context["name"] as? String) == "Kyle" - } - - $0.it("allows you to remove a parent's value in a level") { - try context.push { + $0.it("allows you to remove a value via subscripting") { context["name"] = nil + try expect(context["name"]).to.beNil() } - try expect(context["name"] as? String) == "Kyle" - } - - $0.it("allows you to push a dictionary and run a closure then restoring previous state") { - var didRun = false - - try context.push(dictionary: ["name": "Katie"]) { - didRun = true - try expect(context["name"] as? String) == "Katie" + $0.it("allows you to retrieve a value from a parent") { + try context.push { + try expect(context["name"] as? String) == "Kyle" + } } - try expect(didRun).to.beTrue() - try expect(context["name"] as? String) == "Kyle" - } + $0.it("allows you to override a parent's value") { + try context.push { + context["name"] = "Katie" + try expect(context["name"] as? String) == "Katie" + } + } - $0.it("allows you to flatten the context contents") { - try context.push(dictionary: ["test": "abc"]) { - let flattened = context.flatten() + $0.it("allows you to pop to restore previous state") { + context.push { + context["name"] = "Katie" + } - try expect(flattened.count) == 2 - try expect(flattened["name"] as? String) == "Kyle" - try expect(flattened["test"] as? String) == "abc" + try expect(context["name"] as? String) == "Kyle" + } + + $0.it("allows you to remove a parent's value in a level") { + try context.push { + context["name"] = nil + try expect(context["name"]).to.beNil() + } + + try expect(context["name"] as? String) == "Kyle" + } + + $0.it("allows you to push a dictionary and run a closure then restoring previous state") { + var didRun = false + + try context.push(dictionary: ["name": "Katie"]) { + didRun = true + try expect(context["name"] as? String) == "Katie" + } + + try expect(didRun).to.beTrue() + try expect(context["name"] as? String) == "Kyle" + } + + $0.it("allows you to flatten the context contents") { + try context.push(dictionary: ["test": "abc"]) { + let flattened = context.flatten() + + try expect(flattened.count) == 2 + try expect(flattened["name"] as? String) == "Kyle" + try expect(flattened["test"] as? String) == "abc" + } } } } diff --git a/Tests/StencilTests/EnvironmentSpec.swift b/Tests/StencilTests/EnvironmentSpec.swift index 04ce6c1..f2a7d06 100644 --- a/Tests/StencilTests/EnvironmentSpec.swift +++ b/Tests/StencilTests/EnvironmentSpec.swift @@ -1,333 +1,335 @@ +import XCTest import Spectre import PathKit @testable import Stencil - -func testEnvironment() { - describe("Environment") { - var environment: Environment! - var template: Template! - - $0.before { - environment = Environment(loader: ExampleLoader()) - template = nil - } - - $0.it("can load a template from a name") { - let template = try environment.loadTemplate(name: "example.html") - try expect(template.name) == "example.html" - } - - $0.it("can load a template from a names") { - let template = try environment.loadTemplate(names: ["first.html", "example.html"]) - try expect(template.name) == "example.html" - } - - $0.it("can render a template from a string") { - let result = try environment.renderTemplate(string: "Hello World") - try expect(result) == "Hello World" - } - - $0.it("can render a template from a file") { - let result = try environment.renderTemplate(name: "example.html") - try expect(result) == "Hello World!" - } - - $0.it("allows you to provide a custom template class") { - let environment = Environment(loader: ExampleLoader(), templateClass: CustomTemplate.self) - let result = try environment.renderTemplate(string: "Hello World") - - try expect(result) == "here" - } - - func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError { - guard let range = template.templateString.range(of: token) else { - fatalError("Can't find '\(token)' in '\(template)'") - } - let lexer = Lexer(templateString: template.templateString) - let location = lexer.rangeLocation(range) - let sourceMap = SourceMap(filename: template.name, location: location) - let token = Token.block(value: token, at: sourceMap) - return TemplateSyntaxError(reason: description, token: token, stackTrace: []) - } - - func expectError(reason: String, token: String, - file: String = #file, line: Int = #line, function: String = #function) throws { - let expectedError = expectedSyntaxError(token: token, template: template, description: reason) - - let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]), - file: file, line: line, function: function).toThrow() as TemplateSyntaxError - let reporter = SimpleErrorReporter() - try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError) - } - - $0.context("given syntax error") { - - $0.it("reports syntax error on invalid for tag syntax") { - template = "Hello {% for name in %}{{ name }}, {% endfor %}!" - try expectError(reason: "'for' statements should use the syntax: `for 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) +class EnvironmentTests: XCTestCase { + func testEnvironment() { + describe("Environment") { + var environment: Environment! var template: Template! - var includedTemplate: Template! $0.before { - environment = Environment(loader: loader) + environment = Environment(loader: ExampleLoader()) template = nil - includedTemplate = nil } - func expectError(reason: String, token: String, includedToken: String, - file: String = #file, line: Int = #line, function: String = #function) throws { - var expectedError = expectedSyntaxError(token: token, template: template, description: reason) - expectedError.stackTrace = [expectedSyntaxError(token: includedToken, template: includedTemplate, description: reason).token!] + $0.it("can load a template from a name") { + let template = try environment.loadTemplate(name: "example.html") + try expect(template.name) == "example.html" + } - let error = try expect(environment.render(template: template, context: ["target": "World"]), + $0.it("can load a template from a names") { + let template = try environment.loadTemplate(names: ["first.html", "example.html"]) + try expect(template.name) == "example.html" + } + + $0.it("can render a template from a string") { + let result = try environment.renderTemplate(string: "Hello World") + try expect(result) == "Hello World" + } + + $0.it("can render a template from a file") { + let result = try environment.renderTemplate(name: "example.html") + try expect(result) == "Hello World!" + } + + $0.it("allows you to provide a custom template class") { + let environment = Environment(loader: ExampleLoader(), templateClass: CustomTemplate.self) + let result = try environment.renderTemplate(string: "Hello World") + + try expect(result) == "here" + } + + func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError { + guard let range = template.templateString.range(of: token) else { + fatalError("Can't find '\(token)' in '\(template)'") + } + let lexer = Lexer(templateString: template.templateString) + let location = lexer.rangeLocation(range) + let sourceMap = SourceMap(filename: template.name, location: location) + let token = Token.block(value: token, at: sourceMap) + return TemplateSyntaxError(reason: description, token: token, stackTrace: []) + } + + func expectError(reason: String, token: String, + file: String = #file, line: Int = #line, function: String = #function) throws { + let expectedError = expectedSyntaxError(token: token, template: template, description: reason) + + let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]), file: file, line: line, function: function).toThrow() as TemplateSyntaxError let reporter = SimpleErrorReporter() try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError) } - $0.it("reports syntax error in included template") { - template = Template(templateString: """ + $0.context("given syntax error") { + + $0.it("reports syntax error on invalid for tag syntax") { + template = "Hello {% for name in %}{{ name }}, {% endfor %}!" + try expectError(reason: "'for' statements should use the syntax: `for 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") + includedTemplate = try environment.loadTemplate(name: "invalid-include.html") - try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", - token: """ + try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + token: """ include "invalid-include.html" """, - includedToken: "target|unknown") - } + includedToken: "target|unknown") + } - $0.it("reports runtime error in included template") { - let filterExtension = Extension() - filterExtension.registerFilter("unknown", filter: { (_: Any?) in - throw TemplateSyntaxError("filter error") - }) - environment.extensions += [filterExtension] + $0.it("reports runtime error in included template") { + let filterExtension = Extension() + filterExtension.registerFilter("unknown", filter: { (_: Any?) in + throw TemplateSyntaxError("filter error") + }) + environment.extensions += [filterExtension] - template = Template(templateString: """ + template = Template(templateString: """ {% include "invalid-include.html" %} """, environment: environment) - includedTemplate = try environment.loadTemplate(name: "invalid-include.html") + includedTemplate = try environment.loadTemplate(name: "invalid-include.html") - try expectError(reason: "filter error", - token: "include \"invalid-include.html\"", - includedToken: "target|unknown") - } - - } - - $0.context("given base and child templates") { - let path = Path(#file) + ".." + "fixtures" - let loader = FileSystemLoader(paths: [path]) - var environment: Environment! - var childTemplate: Template! - var baseTemplate: Template! - - $0.before { - environment = Environment(loader: loader) - childTemplate = nil - baseTemplate = nil - } - - func expectError(reason: String, childToken: String, baseToken: String?, - file: String = #file, line: Int = #line, function: String = #function) throws { - var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason) - if let baseToken = baseToken { - expectedError.stackTrace = [expectedSyntaxError(token: baseToken, template: baseTemplate, description: reason).token!] + try expectError(reason: "filter error", + token: "include \"invalid-include.html\"", + includedToken: "target|unknown") } - let error = try expect(environment.render(template: childTemplate, context: ["target": "World"]), - file: file, line: line, function: function).toThrow() as TemplateSyntaxError - let reporter = SimpleErrorReporter() - try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError) + } - $0.it("reports syntax error in base template") { - childTemplate = try environment.loadTemplate(name: "invalid-child-super.html") - baseTemplate = try environment.loadTemplate(name: "invalid-base.html") + $0.context("given base and child templates") { + let path = Path(#file) + ".." + "fixtures" + let loader = FileSystemLoader(paths: [path]) + var environment: Environment! + var childTemplate: Template! + var baseTemplate: Template! - try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", - childToken: "extends \"invalid-base.html\"", - baseToken: "target|unknown") - } + $0.before { + environment = Environment(loader: loader) + childTemplate = nil + baseTemplate = nil + } - $0.it("reports runtime error in base template") { - let filterExtension = Extension() - filterExtension.registerFilter("unknown", filter: { (_: Any?) in - throw TemplateSyntaxError("filter error") - }) - environment.extensions += [filterExtension] + func expectError(reason: String, childToken: String, baseToken: String?, + file: String = #file, line: Int = #line, function: String = #function) throws { + var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason) + if let baseToken = baseToken { + expectedError.stackTrace = [expectedSyntaxError(token: baseToken, template: baseTemplate, description: reason).token!] + } + let error = try expect(environment.render(template: childTemplate, context: ["target": "World"]), + file: file, line: line, function: function).toThrow() as TemplateSyntaxError + let reporter = SimpleErrorReporter() + try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError) + } - childTemplate = try environment.loadTemplate(name: "invalid-child-super.html") - baseTemplate = try environment.loadTemplate(name: "invalid-base.html") + $0.it("reports syntax error in base template") { + childTemplate = try environment.loadTemplate(name: "invalid-child-super.html") + baseTemplate = try environment.loadTemplate(name: "invalid-base.html") - try expectError(reason: "filter error", - childToken: "block.super", - baseToken: "target|unknown") - } + try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + childToken: "extends \"invalid-base.html\"", + baseToken: "target|unknown") + } - $0.it("reports syntax error in child template") { - childTemplate = Template(templateString: """ + $0.it("reports runtime error in base template") { + let filterExtension = Extension() + filterExtension.registerFilter("unknown", filter: { (_: Any?) in + throw TemplateSyntaxError("filter error") + }) + environment.extensions += [filterExtension] + + childTemplate = try environment.loadTemplate(name: "invalid-child-super.html") + baseTemplate = try environment.loadTemplate(name: "invalid-base.html") + + try expectError(reason: "filter error", + childToken: "block.super", + baseToken: "target|unknown") + } + + $0.it("reports syntax error in child template") { + childTemplate = Template(templateString: """ {% extends "base.html" %} {% block body %}Child {{ target|unknown }}{% endblock %} """, environment: environment, name: nil) - try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", - childToken: "target|unknown", - baseToken: nil) - } + try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", + childToken: "target|unknown", + baseToken: nil) + } - $0.it("reports runtime error in child template") { - let filterExtension = Extension() - filterExtension.registerFilter("unknown", filter: { (_: Any?) in - throw TemplateSyntaxError("filter error") - }) - environment.extensions += [filterExtension] + $0.it("reports runtime error in child template") { + let filterExtension = Extension() + filterExtension.registerFilter("unknown", filter: { (_: Any?) in + throw TemplateSyntaxError("filter error") + }) + environment.extensions += [filterExtension] - childTemplate = Template(templateString: """ + childTemplate = Template(templateString: """ {% extends "base.html" %} {% block body %}Child {{ target|unknown }}{% endblock %} """, environment: environment, name: nil) - try expectError(reason: "filter error", - childToken: "target|unknown", - baseToken: nil) + try expectError(reason: "filter error", + childToken: "target|unknown", + baseToken: nil) + } + } - + } - } } diff --git a/Tests/StencilTests/ExpressionSpec.swift b/Tests/StencilTests/ExpressionSpec.swift index e04408b..0109665 100644 --- a/Tests/StencilTests/ExpressionSpec.swift +++ b/Tests/StencilTests/ExpressionSpec.swift @@ -1,342 +1,344 @@ +import XCTest import Spectre @testable import Stencil +class ExpressionsTests: XCTestCase { + func testExpressions() { + describe("Expression") { + let parser = TokenParser(tokens: [], environment: Environment()) -func testExpressions() { - describe("Expression") { - let parser = TokenParser(tokens: [], environment: Environment()) - - func parseExpression(components: [String]) throws -> Expression { - let parser = try IfExpressionParser.parser(components: components, tokenParser: parser, token: .text(value: "", at: .unknown)) - return try parser.parse() - } - - $0.describe("VariableExpression") { - let expression = VariableExpression(variable: Variable("value")) - - $0.it("evaluates to true when value is not nil") { - let context = Context(dictionary: ["value": "known"]) - try expect(try expression.evaluate(context: context)).to.beTrue() + func parseExpression(components: [String]) throws -> Expression { + let parser = try IfExpressionParser.parser(components: components, tokenParser: parser, token: .text(value: "", at: .unknown)) + return try parser.parse() } - $0.it("evaluates to false when value is unset") { - let context = Context() - try expect(try expression.evaluate(context: context)).to.beFalse() - } + $0.describe("VariableExpression") { + let expression = VariableExpression(variable: Variable("value")) - $0.it("evaluates to true when array variable is not empty") { - let items: [[String: Any]] = [["key":"key1","value":42],["key":"key2","value":1337]] - let context = Context(dictionary: ["value": [items]]) - try expect(try expression.evaluate(context: context)).to.beTrue() - } - - $0.it("evaluates to false when array value is empty") { - let emptyItems = [[String: Any]]() - let context = Context(dictionary: ["value": emptyItems]) - try expect(try expression.evaluate(context: context)).to.beFalse() - } - - $0.it("evaluates to false when dictionary value is empty") { - let emptyItems = [String:Any]() - let context = Context(dictionary: ["value": emptyItems]) - try expect(try expression.evaluate(context: context)).to.beFalse() - } - - $0.it("evaluates to false when Array value is empty") { - let context = Context(dictionary: ["value": ([] as [Any])]) - try expect(try expression.evaluate(context: context)).to.beFalse() - } - - $0.it("evaluates to true when integer value is above 0") { - let context = Context(dictionary: ["value": 1]) - try expect(try expression.evaluate(context: context)).to.beTrue() - } - - $0.it("evaluates to true with string") { - let context = Context(dictionary: ["value": "test"]) - try expect(try expression.evaluate(context: context)).to.beTrue() - } - - $0.it("evaluates to false when empty string") { - let context = Context(dictionary: ["value": ""]) - try expect(try expression.evaluate(context: context)).to.beFalse() - } - - $0.it("evaluates to false when integer value is below 0 or below") { - let context = Context(dictionary: ["value": 0]) - try expect(try expression.evaluate(context: context)).to.beFalse() - - let negativeContext = Context(dictionary: ["value": 0]) - try expect(try expression.evaluate(context: negativeContext)).to.beFalse() - } - - $0.it("evaluates to true when float value is above 0") { - let context = Context(dictionary: ["value": Float(0.5)]) - try expect(try expression.evaluate(context: context)).to.beTrue() - } - - $0.it("evaluates to false when float is 0 or below") { - let context = Context(dictionary: ["value": Float(0)]) - try expect(try expression.evaluate(context: context)).to.beFalse() - } - - $0.it("evaluates to true when double value is above 0") { - let context = Context(dictionary: ["value": Double(0.5)]) - try expect(try expression.evaluate(context: context)).to.beTrue() - } - - $0.it("evaluates to false when double is 0 or below") { - let context = Context(dictionary: ["value": Double(0)]) - try expect(try expression.evaluate(context: context)).to.beFalse() - } - - $0.it("evaluates to false when uint is 0") { - let context = Context(dictionary: ["value": UInt(0)]) - try expect(try expression.evaluate(context: context)).to.beFalse() - } - } - - $0.describe("NotExpression") { - $0.it("returns truthy for positive expressions") { - let expression = NotExpression(expression: StaticExpression(value: true)) - try expect(expression.evaluate(context: Context())).to.beFalse() - } - - $0.it("returns falsy for negative expressions") { - let expression = NotExpression(expression: StaticExpression(value: false)) - try expect(expression.evaluate(context: Context())).to.beTrue() - } - } - - $0.describe("expression parsing") { - $0.it("can parse a variable expression") { - let expression = try parseExpression(components: ["value"]) - try expect(expression.evaluate(context: Context())).to.beFalse() - try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue() - } - - $0.it("can parse a not expression") { - let expression = try parseExpression(components: ["not", "value"]) - try expect(expression.evaluate(context: Context())).to.beTrue() - try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse() - } - - $0.describe("and expression") { - let expression = try! parseExpression(components: ["lhs", "and", "rhs"]) - - $0.it("evaluates to false with lhs false") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse() + $0.it("evaluates to true when value is not nil") { + let context = Context(dictionary: ["value": "known"]) + try expect(try expression.evaluate(context: context)).to.beTrue() } - $0.it("evaluates to false with rhs false") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse() + $0.it("evaluates to false when value is unset") { + let context = Context() + try expect(try expression.evaluate(context: context)).to.beFalse() } - $0.it("evaluates to false with lhs and rhs false") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse() + $0.it("evaluates to true when array variable is not empty") { + let items: [[String: Any]] = [["key":"key1","value":42],["key":"key2","value":1337]] + let context = Context(dictionary: ["value": [items]]) + try expect(try expression.evaluate(context: context)).to.beTrue() } - $0.it("evaluates to true with lhs and rhs true") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() + $0.it("evaluates to false when array value is empty") { + let emptyItems = [[String: Any]]() + let context = Context(dictionary: ["value": emptyItems]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + $0.it("evaluates to false when dictionary value is empty") { + let emptyItems = [String:Any]() + let context = Context(dictionary: ["value": emptyItems]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + $0.it("evaluates to false when Array value is empty") { + let context = Context(dictionary: ["value": ([] as [Any])]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + $0.it("evaluates to true when integer value is above 0") { + let context = Context(dictionary: ["value": 1]) + try expect(try expression.evaluate(context: context)).to.beTrue() + } + + $0.it("evaluates to true with string") { + let context = Context(dictionary: ["value": "test"]) + try expect(try expression.evaluate(context: context)).to.beTrue() + } + + $0.it("evaluates to false when empty string") { + let context = Context(dictionary: ["value": ""]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + $0.it("evaluates to false when integer value is below 0 or below") { + let context = Context(dictionary: ["value": 0]) + try expect(try expression.evaluate(context: context)).to.beFalse() + + let negativeContext = Context(dictionary: ["value": 0]) + try expect(try expression.evaluate(context: negativeContext)).to.beFalse() + } + + $0.it("evaluates to true when float value is above 0") { + let context = Context(dictionary: ["value": Float(0.5)]) + try expect(try expression.evaluate(context: context)).to.beTrue() + } + + $0.it("evaluates to false when float is 0 or below") { + let context = Context(dictionary: ["value": Float(0)]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + $0.it("evaluates to true when double value is above 0") { + let context = Context(dictionary: ["value": Double(0.5)]) + try expect(try expression.evaluate(context: context)).to.beTrue() + } + + $0.it("evaluates to false when double is 0 or below") { + let context = Context(dictionary: ["value": Double(0)]) + try expect(try expression.evaluate(context: context)).to.beFalse() + } + + $0.it("evaluates to false when uint is 0") { + let context = Context(dictionary: ["value": UInt(0)]) + try expect(try expression.evaluate(context: context)).to.beFalse() } } - $0.describe("or expression") { - let expression = try! parseExpression(components: ["lhs", "or", "rhs"]) - - $0.it("evaluates to true with lhs true") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue() - } - - $0.it("evaluates to true with rhs true") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beTrue() - } - - $0.it("evaluates to true with lhs and rhs true") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() - } - - $0.it("evaluates to false with lhs and rhs false") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse() - } - } - - $0.describe("equality expression") { - let expression = try! parseExpression(components: ["lhs", "==", "rhs"]) - - $0.it("evaluates to true with equal lhs/rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue() - } - - $0.it("evaluates to false with non equal lhs/rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beFalse() - } - - $0.it("evaluates to true with nils") { - try expect(expression.evaluate(context: Context(dictionary: [:]))).to.beTrue() - } - - $0.it("evaluates to true with numbers") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.0]))).to.beTrue() - } - - $0.it("evaluates to false with non equal numbers") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.1]))).to.beFalse() - } - - $0.it("evaluates to true with booleans") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() - } - - $0.it("evaluates to false with falsy booleans") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse() - } - - $0.it("evaluates to false with different types") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": 1]))).to.beFalse() - } - } - - $0.describe("inequality expression") { - let expression = try! parseExpression(components: ["lhs", "!=", "rhs"]) - - $0.it("evaluates to true with inequal lhs/rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue() - } - - $0.it("evaluates to false with equal lhs/rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "b", "rhs": "b"]))).to.beFalse() - } - } - - $0.describe("more than expression") { - let expression = try! parseExpression(components: ["lhs", ">", "rhs"]) - - $0.it("evaluates to true with lhs > rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue() - } - - $0.it("evaluates to false with lhs == rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse() - } - } - - $0.describe("more than equal expression") { - let expression = try! parseExpression(components: ["lhs", ">=", "rhs"]) - - $0.it("evaluates to true with lhs == rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() - } - - $0.it("evaluates to false with lhs < rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.1]))).to.beFalse() - } - } - - $0.describe("less than expression") { - let expression = try! parseExpression(components: ["lhs", "<", "rhs"]) - - $0.it("evaluates to true with lhs < rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue() - } - - $0.it("evaluates to false with lhs == rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse() - } - } - - $0.describe("less than equal expression") { - let expression = try! parseExpression(components: ["lhs", "<=", "rhs"]) - - $0.it("evaluates to true with lhs == rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() - } - - $0.it("evaluates to false with lhs > rhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.1, "rhs": 5.0]))).to.beFalse() - } - } - - $0.describe("multiple expression") { - let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"]) - - $0.it("evaluates to true with one") { - try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue() - } - - $0.it("evaluates to true with one and three") { - try expect(expression.evaluate(context: Context(dictionary: ["one": true, "three": true]))).to.beTrue() - } - - $0.it("evaluates to true with two") { - try expect(expression.evaluate(context: Context(dictionary: ["two": true]))).to.beTrue() - } - - $0.it("evaluates to false with two and three") { - try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse() - } - - $0.it("evaluates to false with two and three") { - try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse() - } - - $0.it("evaluates to false with nothing") { + $0.describe("NotExpression") { + $0.it("returns truthy for positive expressions") { + let expression = NotExpression(expression: StaticExpression(value: true)) try expect(expression.evaluate(context: Context())).to.beFalse() } - } - - $0.describe("in expression") { - let expression = try! parseExpression(components: ["lhs", "in", "rhs"]) - - $0.it("evaluates to true when rhs contains lhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [1, 2, 3]]))).to.beTrue() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["a", "b", "c"]]))).to.beTrue() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "abc"]))).to.beTrue() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1...3]))).to.beTrue() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1..<3]))).to.beTrue() - } - - $0.it("evaluates to false when rhs does not contain lhs") { - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [2, 3, 4]]))).to.beFalse() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["b", "c", "d"]]))).to.beFalse() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "bcd"]))).to.beFalse() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 1...3]))).to.beFalse() - try expect(expression.evaluate(context: Context(dictionary: ["lhs": 3, "rhs": 1..<3]))).to.beFalse() + + $0.it("returns falsy for negative expressions") { + let expression = NotExpression(expression: StaticExpression(value: false)) + try expect(expression.evaluate(context: Context())).to.beTrue() } } - $0.describe("sub expression") { - $0.it("evaluates correctly") { - let context = Context(dictionary: ["one": false, "two": false, "three": true, "four": true]) - - let expression = try! parseExpression(components: ["one", "and", "two", "or", "three", "and", "four"]) - let expressionWithBrackets = try! parseExpression(components: ["one", "and", "(", "(", "two", ")", "or", "(", "three", "and", "four", ")", ")"]) - - try expect(expression.evaluate(context: context)).to.beTrue() - try expect(expressionWithBrackets.evaluate(context: context)).to.beFalse() - - let notExpression = try! parseExpression(components: ["not", "one", "or", "three"]) - let notExpressionWithBrackets = try! parseExpression(components: ["not", "(", "one", "or", "three", ")"]) - - try expect(notExpression.evaluate(context: context)).to.beTrue() - try expect(notExpressionWithBrackets.evaluate(context: context)).to.beFalse() + $0.describe("expression parsing") { + $0.it("can parse a variable expression") { + let expression = try parseExpression(components: ["value"]) + try expect(expression.evaluate(context: Context())).to.beFalse() + try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue() } - $0.it("fails when brackets are not balanced") { - try expect(parseExpression(components: ["(", "lhs", "and", "rhs"])) - .toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket")) - try expect(parseExpression(components: [")", "lhs", "and", "rhs"])) - .toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket")) - try expect(parseExpression(components: ["lhs", "and", "rhs", ")"])) - .toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket")) - try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", "("])) - .toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket")) - try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", ")"])) - .toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket")) - try expect(parseExpression(components: ["(", "lhs", "and", ")"])) - .toThrow(TemplateSyntaxError("'if' expression error: end")) - try expect(parseExpression(components: ["(", "and", "rhs", ")"])) - .toThrow(TemplateSyntaxError("'if' expression error: infix operator 'and' doesn't have a left hand side")) + $0.it("can parse a not expression") { + let expression = try parseExpression(components: ["not", "value"]) + try expect(expression.evaluate(context: Context())).to.beTrue() + try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse() + } + + $0.describe("and expression") { + let expression = try! parseExpression(components: ["lhs", "and", "rhs"]) + + $0.it("evaluates to false with lhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse() + } + + $0.it("evaluates to false with rhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse() + } + + $0.it("evaluates to false with lhs and rhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse() + } + + $0.it("evaluates to true with lhs and rhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() + } + } + + $0.describe("or expression") { + let expression = try! parseExpression(components: ["lhs", "or", "rhs"]) + + $0.it("evaluates to true with lhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue() + } + + $0.it("evaluates to true with rhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beTrue() + } + + $0.it("evaluates to true with lhs and rhs true") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() + } + + $0.it("evaluates to false with lhs and rhs false") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse() + } + } + + $0.describe("equality expression") { + let expression = try! parseExpression(components: ["lhs", "==", "rhs"]) + + $0.it("evaluates to true with equal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue() + } + + $0.it("evaluates to false with non equal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beFalse() + } + + $0.it("evaluates to true with nils") { + try expect(expression.evaluate(context: Context(dictionary: [:]))).to.beTrue() + } + + $0.it("evaluates to true with numbers") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.0]))).to.beTrue() + } + + $0.it("evaluates to false with non equal numbers") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1.1]))).to.beFalse() + } + + $0.it("evaluates to true with booleans") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue() + } + + $0.it("evaluates to false with falsy booleans") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse() + } + + $0.it("evaluates to false with different types") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": 1]))).to.beFalse() + } + } + + $0.describe("inequality expression") { + let expression = try! parseExpression(components: ["lhs", "!=", "rhs"]) + + $0.it("evaluates to true with inequal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue() + } + + $0.it("evaluates to false with equal lhs/rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "b", "rhs": "b"]))).to.beFalse() + } + } + + $0.describe("more than expression") { + let expression = try! parseExpression(components: ["lhs", ">", "rhs"]) + + $0.it("evaluates to true with lhs > rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue() + } + + $0.it("evaluates to false with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse() + } + } + + $0.describe("more than equal expression") { + let expression = try! parseExpression(components: ["lhs", ">=", "rhs"]) + + $0.it("evaluates to true with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() + } + + $0.it("evaluates to false with lhs < rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.1]))).to.beFalse() + } + } + + $0.describe("less than expression") { + let expression = try! parseExpression(components: ["lhs", "<", "rhs"]) + + $0.it("evaluates to true with lhs < rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue() + } + + $0.it("evaluates to false with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5.0]))).to.beFalse() + } + } + + $0.describe("less than equal expression") { + let expression = try! parseExpression(components: ["lhs", "<=", "rhs"]) + + $0.it("evaluates to true with lhs == rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() + } + + $0.it("evaluates to false with lhs > rhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.1, "rhs": 5.0]))).to.beFalse() + } + } + + $0.describe("multiple expression") { + let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"]) + + $0.it("evaluates to true with one") { + try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue() + } + + $0.it("evaluates to true with one and three") { + try expect(expression.evaluate(context: Context(dictionary: ["one": true, "three": true]))).to.beTrue() + } + + $0.it("evaluates to true with two") { + try expect(expression.evaluate(context: Context(dictionary: ["two": true]))).to.beTrue() + } + + $0.it("evaluates to false with two and three") { + try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse() + } + + $0.it("evaluates to false with two and three") { + try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse() + } + + $0.it("evaluates to false with nothing") { + try expect(expression.evaluate(context: Context())).to.beFalse() + } + } + + $0.describe("in expression") { + let expression = try! parseExpression(components: ["lhs", "in", "rhs"]) + + $0.it("evaluates to true when rhs contains lhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [1, 2, 3]]))).to.beTrue() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["a", "b", "c"]]))).to.beTrue() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "abc"]))).to.beTrue() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1...3]))).to.beTrue() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": 1..<3]))).to.beTrue() + } + + $0.it("evaluates to false when rhs does not contain lhs") { + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [2, 3, 4]]))).to.beFalse() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": ["b", "c", "d"]]))).to.beFalse() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "bcd"]))).to.beFalse() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 1...3]))).to.beFalse() + try expect(expression.evaluate(context: Context(dictionary: ["lhs": 3, "rhs": 1..<3]))).to.beFalse() + } + } + + $0.describe("sub expression") { + $0.it("evaluates correctly") { + let context = Context(dictionary: ["one": false, "two": false, "three": true, "four": true]) + + let expression = try! parseExpression(components: ["one", "and", "two", "or", "three", "and", "four"]) + let expressionWithBrackets = try! parseExpression(components: ["one", "and", "(", "(", "two", ")", "or", "(", "three", "and", "four", ")", ")"]) + + try expect(expression.evaluate(context: context)).to.beTrue() + try expect(expressionWithBrackets.evaluate(context: context)).to.beFalse() + + let notExpression = try! parseExpression(components: ["not", "one", "or", "three"]) + let notExpressionWithBrackets = try! parseExpression(components: ["not", "(", "one", "or", "three", ")"]) + + try expect(notExpression.evaluate(context: context)).to.beTrue() + try expect(notExpressionWithBrackets.evaluate(context: context)).to.beFalse() + } + + $0.it("fails when brackets are not balanced") { + try expect(parseExpression(components: ["(", "lhs", "and", "rhs"])) + .toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket")) + try expect(parseExpression(components: [")", "lhs", "and", "rhs"])) + .toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket")) + try expect(parseExpression(components: ["lhs", "and", "rhs", ")"])) + .toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket")) + try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", "("])) + .toThrow(TemplateSyntaxError("'if' expression error: missing closing bracket")) + try expect(parseExpression(components: ["(", "lhs", "and", "rhs", ")", ")"])) + .toThrow(TemplateSyntaxError("'if' expression error: missing opening bracket")) + try expect(parseExpression(components: ["(", "lhs", "and", ")"])) + .toThrow(TemplateSyntaxError("'if' expression error: end")) + try expect(parseExpression(components: ["(", "and", "rhs", ")"])) + .toThrow(TemplateSyntaxError("'if' expression error: infix operator 'and' doesn't have a left hand side")) + } } } } diff --git a/Tests/StencilTests/FilterSpec.swift b/Tests/StencilTests/FilterSpec.swift index d798632..9e4a58a 100644 --- a/Tests/StencilTests/FilterSpec.swift +++ b/Tests/StencilTests/FilterSpec.swift @@ -1,370 +1,372 @@ +import XCTest import Spectre @testable import Stencil +class FilterTests: XCTestCase { + func testFilter() { + describe("template filters") { + let context: [String: Any] = ["name": "Kyle"] -func testFilter() { - describe("template filters") { - let context: [String: Any] = ["name": "Kyle"] + $0.it("allows you to register a custom filter") { + let template = Template(templateString: "{{ name|repeat }}") - $0.it("allows you to register a custom filter") { - let template = Template(templateString: "{{ name|repeat }}") + let repeatExtension = Extension() + repeatExtension.registerFilter("repeat") { (value: Any?) in + if let value = value as? String { + return "\(value) \(value)" + } - let repeatExtension = Extension() - repeatExtension.registerFilter("repeat") { (value: Any?) in - if let value = value as? String { - return "\(value) \(value)" + return nil } - return nil + let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) + try expect(result) == "Kyle Kyle" } - let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) - try expect(result) == "Kyle Kyle" - } - - $0.it("allows you to register a custom filter which accepts single argument") { - let template = Template(templateString: """ - {{ name|repeat:'value1, "value2"' }} - """) - - let repeatExtension = Extension() - repeatExtension.registerFilter("repeat") { value, arguments in - if !arguments.isEmpty { - return "\(value!) \(value!) with args \(arguments.first!!)" - } - - return nil - } - - let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) - try expect(result) == """ - Kyle Kyle with args value1, "value2" - """ - } - - $0.it("allows you to register a custom filter which accepts several arguments") { + $0.it("allows you to register a custom filter which accepts single argument") { let template = Template(templateString: """ - {{ name|repeat:'value"1"',"value'2'",'(key, value)' }} + {{ name|repeat:'value1, "value2"' }} """) let repeatExtension = Extension() repeatExtension.registerFilter("repeat") { value, arguments in - if !arguments.isEmpty { - return "\(value!) \(value!) with args 0: \(arguments[0]!), 1: \(arguments[1]!), 2: \(arguments[2]!)" - } + if !arguments.isEmpty { + return "\(value!) \(value!) with args \(arguments.first!!)" + } - return nil + return nil } let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) try expect(result) == """ - Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value) + Kyle Kyle with args value1, "value2" """ - } - - $0.it("allows you to register a custom which throws") { - let template = Template(templateString: "{{ name|repeat }}") - let repeatExtension = Extension() - repeatExtension.registerFilter("repeat") { (value: Any?) in - throw TemplateSyntaxError("No Repeat") } - let context = Context(dictionary: context, environment: Environment(extensions: [repeatExtension])) - try expect(try template.render(context)).toThrow(TemplateSyntaxError(reason: "No Repeat", token: template.tokens.first)) - } + $0.it("allows you to register a custom filter which accepts several arguments") { + let template = Template(templateString: """ + {{ name|repeat:'value"1"',"value'2'",'(key, value)' }} + """) - $0.it("allows you to override a default filter") { - let template = Template(templateString: "{{ name|join }}") + let repeatExtension = Extension() + repeatExtension.registerFilter("repeat") { value, arguments in + if !arguments.isEmpty { + return "\(value!) \(value!) with args 0: \(arguments[0]!), 1: \(arguments[1]!), 2: \(arguments[2]!)" + } - let repeatExtension = Extension() - repeatExtension.registerFilter("join") { (value: Any?) in - return "joined" + return nil + } + + let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) + try expect(result) == """ + Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value) + """ } - let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) - try expect(result) == "joined" + $0.it("allows you to register a custom which throws") { + let template = Template(templateString: "{{ name|repeat }}") + let repeatExtension = Extension() + repeatExtension.registerFilter("repeat") { (value: Any?) in + throw TemplateSyntaxError("No Repeat") + } + + let context = Context(dictionary: context, environment: Environment(extensions: [repeatExtension])) + try expect(try template.render(context)).toThrow(TemplateSyntaxError(reason: "No Repeat", token: template.tokens.first)) + } + + $0.it("allows you to override a default filter") { + let template = Template(templateString: "{{ name|join }}") + + let repeatExtension = Extension() + repeatExtension.registerFilter("join") { (value: Any?) in + return "joined" + } + + let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) + try expect(result) == "joined" + } + + $0.it("allows whitespace in expression") { + let template = Template(templateString: """ + {{ value | join : ", " }} + """) + let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) + try expect(result) == "One, Two" + } + + $0.it("throws when you pass arguments to simple filter") { + let template = Template(templateString: "{{ name|uppercase:5 }}") + try expect(try template.render(Context(dictionary: ["name": "kyle"]))).toThrow() + } } - $0.it("allows whitespace in expression") { + describe("string filters") { + $0.context("given string") { + $0.it("transforms a string to be capitalized") { + let template = Template(templateString: "{{ name|capitalize }}") + let result = try template.render(Context(dictionary: ["name": "kyle"])) + try expect(result) == "Kyle" + } + + $0.it("transforms a string to be uppercase") { + let template = Template(templateString: "{{ name|uppercase }}") + let result = try template.render(Context(dictionary: ["name": "kyle"])) + try expect(result) == "KYLE" + } + + $0.it("transforms a string to be lowercase") { + let template = Template(templateString: "{{ name|lowercase }}") + let result = try template.render(Context(dictionary: ["name": "Kyle"])) + try expect(result) == "kyle" + } + } + + $0.context("given array of strings") { + $0.it("transforms a string to be capitalized") { + let template = Template(templateString: "{{ names|capitalize }}") + let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]])) + try expect(result) == """ + ["Kyle", "Kyle"] + """ + } + + $0.it("transforms a string to be uppercase") { + let template = Template(templateString: "{{ names|uppercase }}") + let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]])) + try expect(result) == """ + ["KYLE", "KYLE"] + """ + } + + $0.it("transforms a string to be lowercase") { + let template = Template(templateString: "{{ names|lowercase }}") + let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]])) + try expect(result) == """ + ["kyle", "kyle"] + """ + } + } + } + + describe("default filter") { let template = Template(templateString: """ - {{ value | join : ", " }} - """) - let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) - try expect(result) == "One, Two" - } + Hello {{ name|default:"World" }} + """) - $0.it("throws when you pass arguments to simple filter") { - let template = Template(templateString: "{{ name|uppercase:5 }}") - try expect(try template.render(Context(dictionary: ["name": "kyle"]))).toThrow() - } - } - - describe("string filters") { - $0.context("given string") { - $0.it("transforms a string to be capitalized") { - let template = Template(templateString: "{{ name|capitalize }}") - let result = try template.render(Context(dictionary: ["name": "kyle"])) - try expect(result) == "Kyle" - } - - $0.it("transforms a string to be uppercase") { - let template = Template(templateString: "{{ name|uppercase }}") - let result = try template.render(Context(dictionary: ["name": "kyle"])) - try expect(result) == "KYLE" - } - - $0.it("transforms a string to be lowercase") { - let template = Template(templateString: "{{ name|lowercase }}") + $0.it("shows the variable value") { let result = try template.render(Context(dictionary: ["name": "Kyle"])) - try expect(result) == "kyle" + try expect(result) == "Hello Kyle" + } + + $0.it("shows the default value") { + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "Hello World" + } + + $0.it("supports multiple defaults") { + let template = Template(templateString: """ + Hello {{ name|default:a,b,c,"World" }} + """) + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "Hello World" + } + + $0.it("can use int as default") { + let template = Template(templateString: "{{ value|default:1 }}") + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "1" + } + + $0.it("can use float as default") { + let template = Template(templateString: "{{ value|default:1.5 }}") + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "1.5" + } + + $0.it("checks for underlying nil value correctly") { + let template = Template(templateString: """ + Hello {{ user.name|default:"anonymous" }} + """) + let nilName: String? = nil + let user: [String: Any?] = ["name": nilName] + let result = try template.render(Context(dictionary: ["user": user])) + try expect(result) == "Hello anonymous" } } - $0.context("given array of strings") { - $0.it("transforms a string to be capitalized") { - let template = Template(templateString: "{{ names|capitalize }}") - let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]])) + describe("join filter") { + let template = Template(templateString: """ + {{ value|join:", " }} + """) + + $0.it("joins a collection of strings") { + let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) + try expect(result) == "One, Two" + } + + $0.it("joins a mixed-type collection") { + let result = try template.render(Context(dictionary: ["value": ["One", 2, true, 10.5, "Five"]])) + try expect(result) == "One, 2, true, 10.5, Five" + } + + $0.it("can join by non string") { + let template = Template(templateString: """ + {{ value|join:separator }} + """) + let result = try template.render(Context(dictionary: ["value": ["One", "Two"], "separator": true])) + try expect(result) == "OnetrueTwo" + } + + $0.it("can join without arguments") { + let template = Template(templateString: """ + {{ value|join }} + """) + let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) + try expect(result) == "OneTwo" + } + } + + describe("split filter") { + let template = Template(templateString: """ + {{ value|split:", " }} + """) + + $0.it("split a string into array") { + let result = try template.render(Context(dictionary: ["value": "One, Two"])) try expect(result) == """ - ["Kyle", "Kyle"] + ["One", "Two"] """ } - $0.it("transforms a string to be uppercase") { - let template = Template(templateString: "{{ names|uppercase }}") - let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]])) + $0.it("can split without arguments") { + let template = Template(templateString: """ + {{ value|split }} + """) + let result = try template.render(Context(dictionary: ["value": "One, Two"])) try expect(result) == """ - ["KYLE", "KYLE"] - """ - } - - $0.it("transforms a string to be lowercase") { - let template = Template(templateString: "{{ names|lowercase }}") - let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]])) - try expect(result) == """ - ["kyle", "kyle"] + ["One,", "Two"] """ } } - } - - describe("default filter") { - let template = Template(templateString: """ - Hello {{ name|default:"World" }} - """) - - $0.it("shows the variable value") { - let result = try template.render(Context(dictionary: ["name": "Kyle"])) - try expect(result) == "Hello Kyle" - } - - $0.it("shows the default value") { - let result = try template.render(Context(dictionary: [:])) - try expect(result) == "Hello World" - } - - $0.it("supports multiple defaults") { - let template = Template(templateString: """ - Hello {{ name|default:a,b,c,"World" }} - """) - let result = try template.render(Context(dictionary: [:])) - try expect(result) == "Hello World" - } - - $0.it("can use int as default") { - let template = Template(templateString: "{{ value|default:1 }}") - let result = try template.render(Context(dictionary: [:])) - try expect(result) == "1" - } - - $0.it("can use float as default") { - let template = Template(templateString: "{{ value|default:1.5 }}") - let result = try template.render(Context(dictionary: [:])) - try expect(result) == "1.5" - } - - $0.it("checks for underlying nil value correctly") { - let template = Template(templateString: """ - Hello {{ user.name|default:"anonymous" }} - """) - let nilName: String? = nil - let user: [String: Any?] = ["name": nilName] - let result = try template.render(Context(dictionary: ["user": user])) - try expect(result) == "Hello anonymous" - } - } - - describe("join filter") { - let template = Template(templateString: """ - {{ value|join:", " }} - """) - - $0.it("joins a collection of strings") { - let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) - try expect(result) == "One, Two" - } - - $0.it("joins a mixed-type collection") { - let result = try template.render(Context(dictionary: ["value": ["One", 2, true, 10.5, "Five"]])) - try expect(result) == "One, 2, true, 10.5, Five" - } - - $0.it("can join by non string") { - let template = Template(templateString: """ - {{ value|join:separator }} - """) - let result = try template.render(Context(dictionary: ["value": ["One", "Two"], "separator": true])) - try expect(result) == "OnetrueTwo" - } - - $0.it("can join without arguments") { - let template = Template(templateString: """ - {{ value|join }} - """) - let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) - try expect(result) == "OneTwo" - } - } - - describe("split filter") { - let template = Template(templateString: """ - {{ value|split:", " }} - """) - - $0.it("split a string into array") { - let result = try template.render(Context(dictionary: ["value": "One, Two"])) - try expect(result) == """ - ["One", "Two"] - """ - } - - $0.it("can split without arguments") { - let template = Template(templateString: """ - {{ value|split }} - """) - let result = try template.render(Context(dictionary: ["value": "One, Two"])) - try expect(result) == """ - ["One,", "Two"] - """ - } - } - describe("filter suggestion") { - var template: Template! - var filterExtension: Extension! + describe("filter suggestion") { + var template: Template! + var filterExtension: Extension! - func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError { - guard let range = template.templateString.range(of: token) else { - fatalError("Can't find '\(token)' in '\(template)'") + func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError { + guard let range = template.templateString.range(of: token) else { + fatalError("Can't find '\(token)' in '\(template)'") + } + let lexer = Lexer(templateString: template.templateString) + let location = lexer.rangeLocation(range) + let sourceMap = SourceMap(filename: template.name, location: location) + let token = Token.block(value: token, at: sourceMap) + return TemplateSyntaxError(reason: description, token: token, stackTrace: []) } - let lexer = Lexer(templateString: template.templateString) - let location = lexer.rangeLocation(range) - let sourceMap = SourceMap(filename: template.name, location: location) - let token = Token.block(value: token, at: sourceMap) - return TemplateSyntaxError(reason: description, token: token, stackTrace: []) + + func expectError(reason: String, token: String, + file: String = #file, line: Int = #line, function: String = #function) throws { + let expectedError = expectedSyntaxError(token: token, template: template, description: reason) + let environment = Environment(extensions: [filterExtension]) + + let error = try expect(environment.render(template: template, context: [:]), + file: file, line: line, function: function).toThrow() as TemplateSyntaxError + let reporter = SimpleErrorReporter() + try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError) + } + + $0.it("made for unknown filter") { + template = Template(templateString: "{{ value|unknownFilter }}") + + filterExtension = Extension() + filterExtension.registerFilter("knownFilter") { value, _ in value } + + try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'.", token: "value|unknownFilter") + } + + $0.it("made for multiple similar filters") { + template = Template(templateString: "{{ value|lowerFirst }}") + + filterExtension = Extension() + filterExtension.registerFilter("lowerFirstWord") { value, _ in value } + filterExtension.registerFilter("lowerFirstLetter") { value, _ in value } + + try expectError(reason: "Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'.", token: "value|lowerFirst") + } + + $0.it("not made when can't find similar filter") { + template = Template(templateString: "{{ value|unknownFilter }}") + try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'lowerFirstWord'.", token: "value|unknownFilter") + } + } - func expectError(reason: String, token: String, - file: String = #file, line: Int = #line, function: String = #function) throws { - let expectedError = expectedSyntaxError(token: token, template: template, description: reason) - let environment = Environment(extensions: [filterExtension]) - let error = try expect(environment.render(template: template, context: [:]), - file: file, line: line, function: function).toThrow() as TemplateSyntaxError - let reporter = SimpleErrorReporter() - try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError) - } - - $0.it("made for unknown filter") { - template = Template(templateString: "{{ value|unknownFilter }}") - - filterExtension = Extension() - filterExtension.registerFilter("knownFilter") { value, _ in value } - - try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'.", token: "value|unknownFilter") - } - - $0.it("made for multiple similar filters") { - template = Template(templateString: "{{ value|lowerFirst }}") - - filterExtension = Extension() - filterExtension.registerFilter("lowerFirstWord") { value, _ in value } - filterExtension.registerFilter("lowerFirstLetter") { value, _ in value } - - try expectError(reason: "Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'.", token: "value|lowerFirst") - } - - $0.it("not made when can't find similar filter") { - template = Template(templateString: "{{ value|unknownFilter }}") - try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'lowerFirstWord'.", token: "value|unknownFilter") - } - - } - - - describe("indent filter") { - $0.it("indents content") { - let template = Template(templateString: """ - {{ value|indent:2 }} - """) - let result = try template.render(Context(dictionary: ["value": """ - One - Two - """])) - try expect(result) == """ - One - Two - """ - } - - $0.it("can indent with arbitrary character") { - let template = Template(templateString: """ - {{ value|indent:2,"\t" }} - """) - let result = try template.render(Context(dictionary: ["value": """ - One - Two - """])) - try expect(result) == """ - One - \t\tTwo - """ - } - - $0.it("can indent first line") { - let template = Template(templateString: """ - {{ value|indent:2," ",true }} - """) - let result = try template.render(Context(dictionary: ["value": """ - One - Two - """])) - try expect(result) == """ + describe("indent filter") { + $0.it("indents content") { + let template = Template(templateString: """ + {{ value|indent:2 }} + """) + let result = try template.render(Context(dictionary: ["value": """ One Two - """ - } - - $0.it("does not indent empty lines") { - let template = Template(templateString: """ - {{ value|indent }} - """) - let result = try template.render(Context(dictionary: ["value": """ - One - - - Two - - - """])) - try expect(result) == """ - One - - + """])) + try expect(result) == """ + One Two + """ + } + + $0.it("can indent with arbitrary character") { + let template = Template(templateString: """ + {{ value|indent:2,"\t" }} + """) + let result = try template.render(Context(dictionary: ["value": """ + One + Two + """])) + try expect(result) == """ + One + \t\tTwo + """ + } + + $0.it("can indent first line") { + let template = Template(templateString: """ + {{ value|indent:2," ",true }} + """) + let result = try template.render(Context(dictionary: ["value": """ + One + Two + """])) + try expect(result) == """ + One + Two + """ + } + + $0.it("does not indent empty lines") { + let template = Template(templateString: """ + {{ value|indent }} + """) + let result = try template.render(Context(dictionary: ["value": """ + One - """ + Two + + + """])) + try expect(result) == """ + One + + + Two + + + """ + } } } } diff --git a/Tests/StencilTests/FilterTagSpec.swift b/Tests/StencilTests/FilterTagSpec.swift index 4899531..5423747 100644 --- a/Tests/StencilTests/FilterTagSpec.swift +++ b/Tests/StencilTests/FilterTagSpec.swift @@ -1,49 +1,51 @@ +import XCTest import Spectre import Stencil +class FilterTagTests: XCTestCase { + func testFilterTag() { + describe("Filter Tag") { + $0.it("allows you to use a filter") { + let template = Template(templateString: "{% filter uppercase %}Test{% endfilter %}") + let result = try template.render() + try expect(result) == "TEST" + } -func testFilterTag() { - describe("Filter Tag") { - $0.it("allows you to use a filter") { - let template = Template(templateString: "{% filter uppercase %}Test{% endfilter %}") - let result = try template.render() - try expect(result) == "TEST" - } + $0.it("allows you to chain filters") { + let template = Template(templateString: "{% filter lowercase|capitalize %}TEST{% endfilter %}") + let result = try template.render() + try expect(result) == "Test" + } - $0.it("allows you to chain filters") { - let template = Template(templateString: "{% filter lowercase|capitalize %}TEST{% endfilter %}") - let result = try template.render() - try expect(result) == "Test" - } + $0.it("errors without a filter") { + let template = Template(templateString: "Some {% filter %}Test{% endfilter %}") + try expect(try template.render()).toThrow() + } - $0.it("errors without a filter") { - let template = Template(templateString: "Some {% filter %}Test{% endfilter %}") - try expect(try template.render()).toThrow() - } - - $0.it("can render filters with arguments") { - let ext = Extension() - ext.registerFilter("split", filter: { - return ($0 as! String).components(separatedBy: $1[0] as! String) - }) - let env = Environment(extensions: [ext]) - let result = try env.renderTemplate(string: """ + $0.it("can render filters with arguments") { + let ext = Extension() + ext.registerFilter("split", filter: { + return ($0 as! String).components(separatedBy: $1[0] as! String) + }) + let env = Environment(extensions: [ext]) + let result = try env.renderTemplate(string: """ {% filter split:","|join:";" %}{{ items|join:"," }}{% endfilter %} """, context: ["items": [1, 2]]) - try expect(result) == "1;2" - } + try expect(result) == "1;2" + } - $0.it("can render filters with quote as an argument") { + $0.it("can render filters with quote as an argument") { let ext = Extension() ext.registerFilter("replace", filter: { - print($1[0] as! String) - return ($0 as! String).replacingOccurrences(of: $1[0] as! String, with: $1[1] as! String) + print($1[0] as! String) + return ($0 as! String).replacingOccurrences(of: $1[0] as! String, with: $1[1] as! String) }) let env = Environment(extensions: [ext]) let result = try env.renderTemplate(string: """ {% filter replace:'"',"" %}{{ items|join:"," }}{% endfilter %} """, context: ["items": ["\"1\"", "\"2\""]]) try expect(result) == "1,2" + } } } } diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index e362e91..8cd7846 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -1,351 +1,353 @@ +import XCTest import Spectre @testable import Stencil import Foundation - -func testForNode() { - describe("ForNode") { - let context = Context(dictionary: [ - "items": [1, 2, 3], - "emptyItems": [Int](), - "dict": [ - "one": "I", - "two": "II", - ], - "tuples": [(1, 2, 3), (4, 5, 6)] - ]) - - $0.it("renders the given nodes for each item") { - let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(context)) == "123" - } - - $0.it("renders the given empty nodes when no items found item") { - let nodes: [NodeType] = [VariableNode(variable: "item")] - let emptyNodes: [NodeType] = [TextNode(text: "empty")] - let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes) - try expect(try node.render(context)) == "empty" - } - - $0.it("renders a context variable of type Array") { - let any_context = Context(dictionary: [ - "items": ([1, 2, 3] as [Any]) +class ForNodeTests: XCTestCase { + func testForNode() { + describe("ForNode") { + let context = Context(dictionary: [ + "items": [1, 2, 3], + "emptyItems": [Int](), + "dict": [ + "one": "I", + "two": "II", + ], + "tuples": [(1, 2, 3), (4, 5, 6)] ]) - let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(any_context)) == "123" - } + $0.it("renders the given nodes for each item") { + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(context)) == "123" + } - $0.it("renders a context variable of type CountableClosedRange") { - let context = Context(dictionary: ["range": 1...3]) - let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + $0.it("renders the given empty nodes when no items found item") { + let nodes: [NodeType] = [VariableNode(variable: "item")] + let emptyNodes: [NodeType] = [TextNode(text: "empty")] + let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes) + try expect(try node.render(context)) == "empty" + } - try expect(try node.render(context)) == "123" - } + $0.it("renders a context variable of type Array") { + let any_context = Context(dictionary: [ + "items": ([1, 2, 3] as [Any]) + ]) - $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: []) + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(any_context)) == "123" + } - try expect(try node.render(context)) == "123" - } + $0.it("renders a context variable of type CountableClosedRange") { + let context = Context(dictionary: ["range": 1...3]) + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - #if os(OSX) - $0.it("renders a context variable of type NSArray") { - let nsarray_context = Context(dictionary: [ - "items": NSArray(array: [1, 2, 3]) - ]) + try expect(try node.render(context)) == "123" + } - let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(nsarray_context)) == "123" - } - #endif + $0.it("renders a context variable of type CountableRange") { + let context = Context(dictionary: ["range": 1..<4]) + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("range"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - $0.it("renders the given nodes while providing if the item is first in the context") { - let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.first")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(context)) == "1true2false3false" - } + try expect(try node.render(context)) == "123" + } - $0.it("renders the given nodes while providing if the item is last in the context") { - let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.last")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(context)) == "1false2false3true" - } + #if os(OSX) + $0.it("renders a context variable of type NSArray") { + let nsarray_context = Context(dictionary: [ + "items": NSArray(array: [1, 2, 3]) + ]) - $0.it("renders the given nodes while providing item counter") { - let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(context)) == "112233" - } + let nodes: [NodeType] = [VariableNode(variable: "item")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(nsarray_context)) == "123" + } + #endif - $0.it("renders the given nodes while providing item counter") { - let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter0")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(context)) == "102132" - } + $0.it("renders the given nodes while providing if the item is first in the context") { + let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.first")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(context)) == "1true2false3false" + } - $0.it("renders the given nodes while providing loop length") { - let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.length")] - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) - try expect(try node.render(context)) == "132333" - } + $0.it("renders the given nodes while providing if the item is last in the context") { + let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.last")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(context)) == "1false2false3true" + } - $0.it("renders the given nodes while filtering items using where expression") { - let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] - let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown)) - let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`) - try expect(try node.render(context)) == "2132" - } + $0.it("renders the given nodes while providing item counter") { + let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(context)) == "112233" + } - $0.it("renders the given empty nodes when all items filtered out with where expression") { - let nodes: [NodeType] = [VariableNode(variable: "item")] - let emptyNodes: [NodeType] = [TextNode(text: "empty")] - let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown)) - let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`) - try expect(try node.render(context)) == "empty" - } + $0.it("renders the given nodes while providing item counter") { + let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter0")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(context)) == "102132" + } - $0.it("can render a filter with spaces") { - let templateString = """ + $0.it("renders the given nodes while providing loop length") { + let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.length")] + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) + try expect(try node.render(context)) == "132333" + } + + $0.it("renders the given nodes while filtering items using where expression") { + let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] + let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown)) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`) + try expect(try node.render(context)) == "2132" + } + + $0.it("renders the given empty nodes when all items filtered out with where expression") { + let nodes: [NodeType] = [VariableNode(variable: "item")] + let emptyNodes: [NodeType] = [TextNode(text: "empty")] + let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown)) + let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`) + try expect(try node.render(context)) == "empty" + } + + $0.it("can render a filter with spaces") { + let templateString = """ {% for article in ars | default: a, b , articles %}\ - {{ article.title }} by {{ article.author }}. {% endfor %} """ - let context = Context(dictionary: [ - "articles": [ - Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"), - Article(title: "Memory Management with ARC", author: "Kyle Fuller"), - ] - ]) + let context = Context(dictionary: [ + "articles": [ + Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"), + Article(title: "Memory Management with ARC", author: "Kyle Fuller"), + ] + ]) - let template = Template(templateString: templateString) - let result = try template.render(context) + let template = Template(templateString: templateString) + let result = try template.render(context) - try expect(result) == """ + try expect(result) == """ - Migrating from OCUnit to XCTest by Kyle Fuller. - Memory Management with ARC by Kyle Fuller. """ - } + } - $0.context("given array of tuples") { - $0.it("can iterate over all tuple values") { - let templateString = """ + $0.context("given array of tuples") { + $0.it("can iterate over all tuple values") { + let templateString = """ {% for first,second,third in tuples %}\ {{ first }}, {{ second }}, {{ third }} {% endfor %} """ - let template = Template(templateString: templateString) - let result = try template.render(context) + let template = Template(templateString: templateString) + let result = try template.render(context) - try expect(result) == """ + try expect(result) == """ 1, 2, 3 4, 5, 6 """ - } + } - $0.it("can iterate with less number of variables") { - let templateString = """ + $0.it("can iterate with less number of variables") { + let templateString = """ {% for first,second in tuples %}\ {{ first }}, {{ second }} {% endfor %} """ - let template = Template(templateString: templateString) - let result = try template.render(context) + let template = Template(templateString: templateString) + let result = try template.render(context) - try expect(result) == """ + try expect(result) == """ 1, 2 4, 5 """ - } + } - $0.it("can use _ to skip variables") { - let templateString = """ + $0.it("can use _ to skip variables") { + let templateString = """ {% for first,_,third in tuples %}\ {{ first }}, {{ third }} {% endfor %} """ - let template = Template(templateString: templateString) - let result = try template.render(context) + let template = Template(templateString: templateString) + let result = try template.render(context) - try expect(result) == """ + try expect(result) == """ 1, 3 4, 6 """ - } + } - $0.it("throws when number of variables is more than number of tuple values") { - let templateString = """ + $0.it("throws when number of variables is more than number of tuple values") { + let templateString = """ {% for key,value,smth in dict %} {% endfor %} """ - let template = Template(templateString: templateString) - try expect(template.render(context)).toThrow() + let template = Template(templateString: templateString) + try expect(template.render(context)).toThrow() + } + } - } - - $0.it("can iterate over dictionary") { - let templateString = """ + $0.it("can iterate over dictionary") { + let templateString = """ {% for key, value in dict %}\ {{ key }}: {{ value }},\ {% endfor %} """ - let template = Template(templateString: templateString) - let result = try template.render(context) + let template = Template(templateString: templateString) + let result = try template.render(context) - try expect(result) == """ + try expect(result) == """ one: I,two: II, """ - } - - $0.it("renders supports iterating over dictionary") { - let nodes: [NodeType] = [ - VariableNode(variable: "key"), - TextNode(text: ","), - ] - let emptyNodes: [NodeType] = [TextNode(text: "empty")] - let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil) - let result = try node.render(context) - - try expect(result) == """ - one,two, - """ - } - - $0.it("renders supports iterating over dictionary") { - let nodes: [NodeType] = [ - VariableNode(variable: "key"), - TextNode(text: "="), - VariableNode(variable: "value"), - TextNode(text: ","), - ] - let emptyNodes: [NodeType] = [TextNode(text: "empty")] - let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key", "value"], nodes: nodes, emptyNodes: emptyNodes, where: nil) - let result = try node.render(context) - - try expect(result) == """ - one=I,two=II, - """ - } - - $0.it("handles invalid input") { - let token = Token.block(value: "for i", at: .unknown) - let parser = TokenParser(tokens: [token], environment: Environment()) - let error = TemplateSyntaxError(reason: "'for' statements should use the syntax: `for in [where ]`.", token: token) - try expect(try parser.parse()).toThrow(error) - } - - $0.it("can iterate over struct properties") { - struct MyStruct { - let string: String - let number: Int } - let context = Context(dictionary: [ - "struct": MyStruct(string: "abc", number: 123) - ]) + $0.it("renders supports iterating over dictionary") { + let nodes: [NodeType] = [ + VariableNode(variable: "key"), + TextNode(text: ","), + ] + let emptyNodes: [NodeType] = [TextNode(text: "empty")] + let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil) + let result = try node.render(context) - let nodes: [NodeType] = [ - VariableNode(variable: "property"), - TextNode(text: "="), - VariableNode(variable: "value"), - TextNode(text: "\n"), - ] - let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: []) - let result = try node.render(context) + try expect(result) == """ + one,two, + """ + } - try expect(result) == """ + $0.it("renders supports iterating over dictionary") { + let nodes: [NodeType] = [ + VariableNode(variable: "key"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: ","), + ] + let emptyNodes: [NodeType] = [TextNode(text: "empty")] + let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key", "value"], nodes: nodes, emptyNodes: emptyNodes, where: nil) + let result = try node.render(context) + + try expect(result) == """ + one=I,two=II, + """ + } + + $0.it("handles invalid input") { + let token = Token.block(value: "for i", at: .unknown) + let parser = TokenParser(tokens: [token], environment: Environment()) + let error = TemplateSyntaxError(reason: "'for' statements should use the syntax: `for in [where ]`.", token: token) + try expect(try parser.parse()).toThrow(error) + } + + $0.it("can iterate over struct properties") { + struct MyStruct { + let string: String + let number: Int + } + + let context = Context(dictionary: [ + "struct": MyStruct(string: "abc", number: 123) + ]) + + let nodes: [NodeType] = [ + VariableNode(variable: "property"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: "\n"), + ] + let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: []) + let result = try node.render(context) + + try expect(result) == """ string=abc number=123 """ - } + } - $0.it("can iterate tuple items") { - let context = Context(dictionary: [ - "tuple": (one: 1, two: "dva"), - ]) + $0.it("can iterate tuple items") { + let context = Context(dictionary: [ + "tuple": (one: 1, two: "dva"), + ]) - let nodes: [NodeType] = [ - VariableNode(variable: "label"), - TextNode(text: "="), - VariableNode(variable: "value"), - TextNode(text: "\n"), - ] + let nodes: [NodeType] = [ + VariableNode(variable: "label"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: "\n"), + ] - let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) - let result = try node.render(context) + let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) + let result = try node.render(context) - try expect(result) == """ + try expect(result) == """ one=1 two=dva """ - } - - $0.it("can iterate over class properties") { - class MyClass { - var baseString: String - var baseInt: Int - init(_ string: String, _ int: Int) { - baseString = string - baseInt = int - } } - class MySubclass: MyClass { - var childString: String - init(_ childString: String, _ string: String, _ int: Int) { - self.childString = childString - super.init(string, int) + $0.it("can iterate over class properties") { + class MyClass { + var baseString: String + var baseInt: Int + init(_ string: String, _ int: Int) { + baseString = string + baseInt = int + } } - } - let context = Context(dictionary: [ - "class": MySubclass("child", "base", 1) - ]) + class MySubclass: MyClass { + var childString: String + init(_ childString: String, _ string: String, _ int: Int) { + self.childString = childString + super.init(string, int) + } + } - let nodes: [NodeType] = [ - VariableNode(variable: "label"), - TextNode(text: "="), - VariableNode(variable: "value"), - TextNode(text: "\n"), - ] + let context = Context(dictionary: [ + "class": MySubclass("child", "base", 1) + ]) - let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) - let result = try node.render(context) + let nodes: [NodeType] = [ + VariableNode(variable: "label"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: "\n"), + ] - try expect(result) == """ + let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) + let result = try node.render(context) + + try expect(result) == """ childString=child baseString=base baseInt=1 """ - } + } + + $0.it("can iterate in range of variables") { + let template: Template = "{% for i in 1...j %}{{ i }}{% endfor %}" + try expect(try template.render(Context(dictionary: ["j": 3]))) == "123" + } - $0.it("can iterate in range of variables") { - let template: Template = "{% for i in 1...j %}{{ i }}{% endfor %}" - try expect(try template.render(Context(dictionary: ["j": 3]))) == "123" } } - } fileprivate struct Article { diff --git a/Tests/StencilTests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift index cd662d7..c9d6ae7 100644 --- a/Tests/StencilTests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -1,242 +1,243 @@ +import XCTest import Spectre @testable import Stencil +class IfNodeTests: XCTestCase { + func testIfNode() { + describe("IfNode") { + $0.describe("parsing") { + $0.it("can parse an if block") { + let tokens: [Token] = [ + .block(value: "if value", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "endif", at: .unknown) + ] -func testIfNode() { - describe("IfNode") { - $0.describe("parsing") { - $0.it("can parse an if block") { - let tokens: [Token] = [ - .block(value: "if value", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "endif", at: .unknown) - ] + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? IfNode - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? IfNode + let conditions = node?.conditions + try expect(conditions?.count) == 1 + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode + try expect(trueNode?.text) == "true" + } - let conditions = node?.conditions - try expect(conditions?.count) == 1 - try expect(conditions?[0].nodes.count) == 1 - let trueNode = conditions?[0].nodes.first as? TextNode - try expect(trueNode?.text) == "true" + $0.it("can parse an if with else block") { + let tokens: [Token] = [ + .block(value: "if value", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "else", at: .unknown), + .text(value: "false", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? IfNode + + let conditions = node?.conditions + try expect(conditions?.count) == 2 + + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode + try expect(trueNode?.text) == "true" + + try expect(conditions?[1].nodes.count) == 1 + let falseNode = conditions?[1].nodes.first as? TextNode + try expect(falseNode?.text) == "false" + } + + $0.it("can parse an if with elif block") { + let tokens: [Token] = [ + .block(value: "if value", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "elif something", at: .unknown), + .text(value: "some", at: .unknown), + .block(value: "else", at: .unknown), + .text(value: "false", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? IfNode + + let conditions = node?.conditions + try expect(conditions?.count) == 3 + + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode + try expect(trueNode?.text) == "true" + + try expect(conditions?[1].nodes.count) == 1 + let elifNode = conditions?[1].nodes.first as? TextNode + try expect(elifNode?.text) == "some" + + try expect(conditions?[2].nodes.count) == 1 + let falseNode = conditions?[2].nodes.first as? TextNode + try expect(falseNode?.text) == "false" + } + + $0.it("can parse an if with elif block without else") { + let tokens: [Token] = [ + .block(value: "if value", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "elif something", at: .unknown), + .text(value: "some", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? IfNode + + let conditions = node?.conditions + try expect(conditions?.count) == 2 + + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode + try expect(trueNode?.text) == "true" + + try expect(conditions?[1].nodes.count) == 1 + let elifNode = conditions?[1].nodes.first as? TextNode + try expect(elifNode?.text) == "some" + } + + $0.it("can parse an if with multiple elif block") { + let tokens: [Token] = [ + .block(value: "if value", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "elif something1", at: .unknown), + .text(value: "some1", at: .unknown), + .block(value: "elif something2", at: .unknown), + .text(value: "some2", at: .unknown), + .block(value: "else", at: .unknown), + .text(value: "false", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? IfNode + + let conditions = node?.conditions + try expect(conditions?.count) == 4 + + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode + try expect(trueNode?.text) == "true" + + try expect(conditions?[1].nodes.count) == 1 + let elifNode = conditions?[1].nodes.first as? TextNode + try expect(elifNode?.text) == "some1" + + try expect(conditions?[2].nodes.count) == 1 + let elif2Node = conditions?[2].nodes.first as? TextNode + try expect(elif2Node?.text) == "some2" + + try expect(conditions?[3].nodes.count) == 1 + let falseNode = conditions?[3].nodes.first as? TextNode + try expect(falseNode?.text) == "false" + } + + + $0.it("can parse an if with complex expression") { + let tokens: [Token] = [ + .block(value: "if value == \"test\" and not name", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + try expect(nodes.first is IfNode).beTrue() + } + + $0.it("can parse an ifnot block") { + let tokens: [Token] = [ + .block(value: "ifnot value", at: .unknown), + .text(value: "false", at: .unknown), + .block(value: "else", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? IfNode + let conditions = node?.conditions + try expect(conditions?.count) == 2 + + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode + try expect(trueNode?.text) == "true" + + try expect(conditions?[1].nodes.count) == 1 + let falseNode = conditions?[1].nodes.first as? TextNode + try expect(falseNode?.text) == "false" + } + + $0.it("throws an error when parsing an if block without an endif") { + let tokens: [Token] = [.block(value: "if value", at: .unknown)] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first) + try expect(try parser.parse()).toThrow(error) + } + + $0.it("throws an error when parsing an ifnot without an endif") { + let tokens: [Token] = [.block(value: "ifnot value", at: .unknown)] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first) + try expect(try parser.parse()).toThrow(error) + } } - $0.it("can parse an if with else block") { - let tokens: [Token] = [ - .block(value: "if value", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "else", at: .unknown), - .text(value: "false", at: .unknown), - .block(value: "endif", at: .unknown) - ] + $0.describe("rendering") { + $0.it("renders a true expression") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), + IfCondition(expression: nil, nodes: [TextNode(text: "3")]), + ]) - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? IfNode + try expect(try node.render(Context())) == "1" + } - let conditions = node?.conditions - try expect(conditions?.count) == 2 + $0.it("renders the first true expression") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), + IfCondition(expression: nil, nodes: [TextNode(text: "3")]), + ]) - try expect(conditions?[0].nodes.count) == 1 - let trueNode = conditions?[0].nodes.first as? TextNode - try expect(trueNode?.text) == "true" + try expect(try node.render(Context())) == "2" + } - try expect(conditions?[1].nodes.count) == 1 - let falseNode = conditions?[1].nodes.first as? TextNode - try expect(falseNode?.text) == "false" + $0.it("renders the empty expression when other conditions are falsy") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]), + IfCondition(expression: nil, nodes: [TextNode(text: "3")]), + ]) + + try expect(try node.render(Context())) == "3" + } + + $0.it("renders empty when no truthy conditions") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]), + ]) + + try expect(try node.render(Context())) == "" + } } - $0.it("can parse an if with elif block") { - let tokens: [Token] = [ - .block(value: "if value", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "elif something", at: .unknown), - .text(value: "some", at: .unknown), - .block(value: "else", at: .unknown), - .text(value: "false", at: .unknown), - .block(value: "endif", at: .unknown) - ] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? IfNode - - let conditions = node?.conditions - try expect(conditions?.count) == 3 - - try expect(conditions?[0].nodes.count) == 1 - let trueNode = conditions?[0].nodes.first as? TextNode - try expect(trueNode?.text) == "true" - - try expect(conditions?[1].nodes.count) == 1 - let elifNode = conditions?[1].nodes.first as? TextNode - try expect(elifNode?.text) == "some" - - try expect(conditions?[2].nodes.count) == 1 - let falseNode = conditions?[2].nodes.first as? TextNode - try expect(falseNode?.text) == "false" - } - - $0.it("can parse an if with elif block without else") { - let tokens: [Token] = [ - .block(value: "if value", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "elif something", at: .unknown), - .text(value: "some", at: .unknown), - .block(value: "endif", at: .unknown) - ] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? IfNode - - let conditions = node?.conditions - try expect(conditions?.count) == 2 - - try expect(conditions?[0].nodes.count) == 1 - let trueNode = conditions?[0].nodes.first as? TextNode - try expect(trueNode?.text) == "true" - - try expect(conditions?[1].nodes.count) == 1 - let elifNode = conditions?[1].nodes.first as? TextNode - try expect(elifNode?.text) == "some" - } - - $0.it("can parse an if with multiple elif block") { - let tokens: [Token] = [ - .block(value: "if value", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "elif something1", at: .unknown), - .text(value: "some1", at: .unknown), - .block(value: "elif something2", at: .unknown), - .text(value: "some2", at: .unknown), - .block(value: "else", at: .unknown), - .text(value: "false", at: .unknown), - .block(value: "endif", at: .unknown) - ] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? IfNode - - let conditions = node?.conditions - try expect(conditions?.count) == 4 - - try expect(conditions?[0].nodes.count) == 1 - let trueNode = conditions?[0].nodes.first as? TextNode - try expect(trueNode?.text) == "true" - - try expect(conditions?[1].nodes.count) == 1 - let elifNode = conditions?[1].nodes.first as? TextNode - try expect(elifNode?.text) == "some1" - - try expect(conditions?[2].nodes.count) == 1 - let elif2Node = conditions?[2].nodes.first as? TextNode - try expect(elif2Node?.text) == "some2" - - try expect(conditions?[3].nodes.count) == 1 - let falseNode = conditions?[3].nodes.first as? TextNode - try expect(falseNode?.text) == "false" - } - - - $0.it("can parse an if with complex expression") { - let tokens: [Token] = [ - .block(value: "if value == \"test\" and not name", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "endif", at: .unknown) - ] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - try expect(nodes.first is IfNode).beTrue() - } - - $0.it("can parse an ifnot block") { - let tokens: [Token] = [ - .block(value: "ifnot value", at: .unknown), - .text(value: "false", at: .unknown), - .block(value: "else", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "endif", at: .unknown) - ] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? IfNode - let conditions = node?.conditions - try expect(conditions?.count) == 2 - - try expect(conditions?[0].nodes.count) == 1 - let trueNode = conditions?[0].nodes.first as? TextNode - try expect(trueNode?.text) == "true" - - try expect(conditions?[1].nodes.count) == 1 - let falseNode = conditions?[1].nodes.first as? TextNode - try expect(falseNode?.text) == "false" - } - - $0.it("throws an error when parsing an if block without an endif") { - let tokens: [Token] = [.block(value: "if value", at: .unknown)] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first) - try expect(try parser.parse()).toThrow(error) - } - - $0.it("throws an error when parsing an ifnot without an endif") { - let tokens: [Token] = [.block(value: "ifnot value", at: .unknown)] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first) - try expect(try parser.parse()).toThrow(error) - } - } - - $0.describe("rendering") { - $0.it("renders a true expression") { - let node = IfNode(conditions: [ - IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "1")]), - IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), - IfCondition(expression: nil, nodes: [TextNode(text: "3")]), - ]) - - try expect(try node.render(Context())) == "1" - } - - $0.it("renders the first true expression") { - let node = IfNode(conditions: [ - IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), - IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), - IfCondition(expression: nil, nodes: [TextNode(text: "3")]), - ]) - - try expect(try node.render(Context())) == "2" - } - - $0.it("renders the empty expression when other conditions are falsy") { - let node = IfNode(conditions: [ - IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), - IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]), - IfCondition(expression: nil, nodes: [TextNode(text: "3")]), - ]) - - try expect(try node.render(Context())) == "3" - } - - $0.it("renders empty when no truthy conditions") { - let node = IfNode(conditions: [ - IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), - IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]), - ]) - - try expect(try node.render(Context())) == "" - } - } - - $0.it("supports variable filters in the if expression") { + $0.it("supports variable filters in the if expression") { let tokens: [Token] = [ .block(value: "if value|uppercase == \"TEST\"", at: .unknown), .text(value: "true", at: .unknown), @@ -248,40 +249,41 @@ func testIfNode() { let result = try renderNodes(nodes, Context(dictionary: ["value": "test"])) try expect(result) == "true" - } - - $0.it("evaluates nil properties as false") { - let tokens: [Token] = [ - .block(value: "if instance.value", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "endif", at: .unknown) - ] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - - struct SomeType { - let value: String? = nil } - let result = try renderNodes(nodes, Context(dictionary: ["instance": SomeType()])) - try expect(result) == "" + + $0.it("evaluates nil properties as false") { + let tokens: [Token] = [ + .block(value: "if instance.value", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + + struct SomeType { + let value: String? = nil + } + let result = try renderNodes(nodes, Context(dictionary: ["instance": SomeType()])) + try expect(result) == "" + } + + $0.it("supports closed range variables") { + let tokens: [Token] = [ + .block(value: "if value in 1...3", at: .unknown), + .text(value: "true", at: .unknown), + .block(value: "else", at: .unknown), + .text(value: "false", at: .unknown), + .block(value: "endif", at: .unknown) + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + + try expect(renderNodes(nodes, Context(dictionary: ["value": 3]))) == "true" + try expect(renderNodes(nodes, Context(dictionary: ["value": 4]))) == "false" + } + } - - $0.it("supports closed range variables") { - let tokens: [Token] = [ - .block(value: "if value in 1...3", at: .unknown), - .text(value: "true", at: .unknown), - .block(value: "else", at: .unknown), - .text(value: "false", at: .unknown), - .block(value: "endif", at: .unknown) - ] - - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - - try expect(renderNodes(nodes, Context(dictionary: ["value": 3]))) == "true" - try expect(renderNodes(nodes, Context(dictionary: ["value": 4]))) == "false" - } - } } diff --git a/Tests/StencilTests/IncludeSpec.swift b/Tests/StencilTests/IncludeSpec.swift index cf51546..1b587ae 100644 --- a/Tests/StencilTests/IncludeSpec.swift +++ b/Tests/StencilTests/IncludeSpec.swift @@ -1,69 +1,71 @@ +import XCTest import Spectre @testable import Stencil import PathKit +class IncludeTests: XCTestCase { + func testInclude() { + describe("Include") { + let path = Path(#file) + ".." + "fixtures" + let loader = FileSystemLoader(paths: [path]) + let environment = Environment(loader: loader) -func testInclude() { - describe("Include") { - let path = Path(#file) + ".." + "fixtures" - let loader = FileSystemLoader(paths: [path]) - let environment = Environment(loader: loader) + $0.describe("parsing") { + $0.it("throws an error when no template is given") { + let tokens: [Token] = [ .block(value: "include", at: .unknown) ] + let parser = TokenParser(tokens: tokens, environment: Environment()) - $0.describe("parsing") { - $0.it("throws an error when no template is given") { - let tokens: [Token] = [ .block(value: "include", at: .unknown) ] - let parser = TokenParser(tokens: tokens, environment: Environment()) + let error = TemplateSyntaxError(reason: "'include' tag requires one argument, the template file to be included. A second optional argument can be used to specify the context that will be passed to the included file", token: tokens.first) + try expect(try parser.parse()).toThrow(error) + } - let error = TemplateSyntaxError(reason: "'include' tag requires one argument, the template file to be included. A second optional argument can be used to specify the context that will be passed to the included file", token: tokens.first) - try expect(try parser.parse()).toThrow(error) - } + $0.it("can parse a valid include block") { + let tokens: [Token] = [ .block(value: "include \"test.html\"", at: .unknown) ] + let parser = TokenParser(tokens: tokens, environment: Environment()) - $0.it("can parse a valid include block") { - let tokens: [Token] = [ .block(value: "include \"test.html\"", at: .unknown) ] - let parser = TokenParser(tokens: tokens, environment: Environment()) - - let nodes = try parser.parse() - let node = nodes.first as? IncludeNode - try expect(nodes.count) == 1 - try expect(node?.templateName) == Variable("\"test.html\"") - } - } - - $0.describe("rendering") { - $0.it("throws an error when rendering without a loader") { - let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown)) - - do { - _ = try node.render(Context()) - } catch { - try expect("\(error)") == "Template named `test.html` does not exist. No loaders found" + let nodes = try parser.parse() + let node = nodes.first as? IncludeNode + try expect(nodes.count) == 1 + try expect(node?.templateName) == Variable("\"test.html\"") } } - $0.it("throws an error when it cannot find the included template") { - let node = IncludeNode(templateName: Variable("\"unknown.html\""), token: .block(value: "", at: .unknown)) + $0.describe("rendering") { + $0.it("throws an error when rendering without a loader") { + let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown)) - do { - _ = try node.render(Context(environment: environment)) - } catch { - try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue() + do { + _ = try node.render(Context()) + } catch { + try expect("\(error)") == "Template named `test.html` does not exist. No loaders found" + } } - } - $0.it("successfully renders a found included template") { - let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown)) - let context = Context(dictionary: ["target": "World"], environment: environment) - let value = try node.render(context) - try expect(value) == "Hello World!" - } + $0.it("throws an error when it cannot find the included template") { + let node = IncludeNode(templateName: Variable("\"unknown.html\""), token: .block(value: "", at: .unknown)) - $0.it("successfully passes context") { - let template = Template(templateString: """ + do { + _ = try node.render(Context(environment: environment)) + } catch { + try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue() + } + } + + $0.it("successfully renders a found included template") { + let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown)) + let context = Context(dictionary: ["target": "World"], environment: environment) + let value = try node.render(context) + try expect(value) == "Hello World!" + } + + $0.it("successfully passes context") { + let template = Template(templateString: """ {% include "test.html" child %} """) - let context = Context(dictionary: ["child": ["target": "World"]], environment: environment) - let value = try template.render(context) - try expect(value) == "Hello World!" + let context = Context(dictionary: ["child": ["target": "World"]], environment: environment) + let value = try template.render(context) + try expect(value) == "Hello World!" + } } } } diff --git a/Tests/StencilTests/InheritenceSpec.swift b/Tests/StencilTests/InheritenceSpec.swift index 68ef597..d859fb4 100644 --- a/Tests/StencilTests/InheritenceSpec.swift +++ b/Tests/StencilTests/InheritenceSpec.swift @@ -1,36 +1,38 @@ +import XCTest import Spectre import Stencil import PathKit +class InheritenceTests: XCTestCase { + func testInheritence() { + describe("Inheritence") { + let path = Path(#file) + ".." + "fixtures" + let loader = FileSystemLoader(paths: [path]) + let environment = Environment(loader: loader) -func testInheritence() { - describe("Inheritence") { - let path = Path(#file) + ".." + "fixtures" - let loader = FileSystemLoader(paths: [path]) - let environment = Environment(loader: loader) - - $0.it("can inherit from another template") { - let template = try environment.loadTemplate(name: "child.html") - try expect(try template.render()) == """ + $0.it("can inherit from another template") { + let template = try environment.loadTemplate(name: "child.html") + try expect(try template.render()) == """ Super_Header Child_Header Child_Body """ - } + } - $0.it("can inherit from another template inheriting from another template") { - let template = try environment.loadTemplate(name: "child-child.html") - try expect(try template.render()) == """ + $0.it("can inherit from another template inheriting from another template") { + let template = try environment.loadTemplate(name: "child-child.html") + try expect(try template.render()) == """ Super_Header Child_Header Child_Child_Header Child_Body """ - } + } - $0.it("can inherit from a template that calls a super block") { - let template = try environment.loadTemplate(name: "child-super.html") - try expect(try template.render()) == """ + $0.it("can inherit from a template that calls a super block") { + let template = try environment.loadTemplate(name: "child-super.html") + try expect(try template.render()) == """ Header Child_Body """ + } } } } diff --git a/Tests/StencilTests/LexerSpec.swift b/Tests/StencilTests/LexerSpec.swift index fd3493d..6f49a4c 100644 --- a/Tests/StencilTests/LexerSpec.swift +++ b/Tests/StencilTests/LexerSpec.swift @@ -1,75 +1,76 @@ +import XCTest import Spectre @testable import Stencil +class LexerTests: XCTestCase { + func testLexer() { + describe("Lexer") { + $0.it("can tokenize text") { + let lexer = Lexer(templateString: "Hello World") + let tokens = lexer.tokenize() -func testLexer() { - describe("Lexer") { - $0.it("can tokenize text") { - let lexer = Lexer(templateString: "Hello World") - let tokens = lexer.tokenize() + try expect(tokens.count) == 1 + try expect(tokens.first) == .text(value: "Hello World", at: SourceMap(location: ("Hello World", 1, 0))) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .text(value: "Hello World", at: SourceMap(location: ("Hello World", 1, 0))) - } + $0.it("can tokenize a comment") { + let lexer = Lexer(templateString: "{# Comment #}") + let tokens = lexer.tokenize() - $0.it("can tokenize a comment") { - let lexer = Lexer(templateString: "{# Comment #}") - let tokens = lexer.tokenize() + try expect(tokens.count) == 1 + try expect(tokens.first) == .comment(value: "Comment", at: SourceMap(location: ("{# Comment #}", 1, 3))) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .comment(value: "Comment", at: SourceMap(location: ("{# Comment #}", 1, 3))) - } + $0.it("can tokenize a variable") { + let lexer = Lexer(templateString: "{{ Variable }}") + let tokens = lexer.tokenize() - $0.it("can tokenize a variable") { - let lexer = Lexer(templateString: "{{ Variable }}") - let tokens = lexer.tokenize() + try expect(tokens.count) == 1 + try expect(tokens.first) == .variable(value: "Variable", at: SourceMap(location: ("{{ Variable }}", 1, 3))) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .variable(value: "Variable", at: SourceMap(location: ("{{ Variable }}", 1, 3))) - } + $0.it("can tokenize unclosed tag by ignoring it") { + let templateString = "{{ thing" + let lexer = Lexer(templateString: templateString) + let tokens = lexer.tokenize() - $0.it("can tokenize unclosed tag by ignoring it") { - let templateString = "{{ thing" - let lexer = Lexer(templateString: templateString) - let tokens = lexer.tokenize() + try expect(tokens.count) == 1 + try expect(tokens.first) == .text(value: "", at: SourceMap(location: ("{{ thing", 1, 0))) + } - try expect(tokens.count) == 1 - try expect(tokens.first) == .text(value: "", at: SourceMap(location: ("{{ thing", 1, 0))) - } + $0.it("can tokenize a mixture of content") { + let templateString = "My name is {{ myname }}." + let lexer = Lexer(templateString: templateString) + let tokens = lexer.tokenize() - $0.it("can tokenize a mixture of content") { - let templateString = "My name is {{ myname }}." - let lexer = Lexer(templateString: templateString) - let tokens = lexer.tokenize() + try expect(tokens.count) == 3 + try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is ")!))) + try expect(tokens[1]) == Token.variable(value: "myname", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "myname")!))) + try expect(tokens[2]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!))) + } - try expect(tokens.count) == 3 - try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is ")!))) - try expect(tokens[1]) == Token.variable(value: "myname", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "myname")!))) - try expect(tokens[2]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!))) - } + $0.it("can tokenize two variables without being greedy") { + let templateString = "{{ thing }}{{ name }}" + let lexer = Lexer(templateString: templateString) + let tokens = lexer.tokenize() - $0.it("can tokenize two variables without being greedy") { - let templateString = "{{ thing }}{{ name }}" - let lexer = Lexer(templateString: templateString) - let tokens = lexer.tokenize() + try expect(tokens.count) == 2 + try expect(tokens[0]) == Token.variable(value: "thing", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "thing")!))) + try expect(tokens[1]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name")!))) + } - try expect(tokens.count) == 2 - try expect(tokens[0]) == Token.variable(value: "thing", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "thing")!))) - try expect(tokens[1]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name")!))) - } + $0.it("can tokenize an unclosed block") { + let lexer = Lexer(templateString: "{%}") + let _ = lexer.tokenize() + } - $0.it("can tokenize an unclosed block") { - let lexer = Lexer(templateString: "{%}") - let _ = lexer.tokenize() - } + $0.it("can tokenize an empty variable") { + let lexer = Lexer(templateString: "{{}}") + let _ = lexer.tokenize() + } - $0.it("can tokenize an empty variable") { - let lexer = Lexer(templateString: "{{}}") - let _ = lexer.tokenize() - } - - $0.it("can tokenize with new lines") { - let templateString = """ + $0.it("can tokenize with new lines") { + let templateString = """ My name is {% if name and @@ -80,16 +81,17 @@ func testLexer() { endif %}. """ - let lexer = Lexer(templateString: templateString) + let lexer = Lexer(templateString: templateString) - let tokens = lexer.tokenize() + let tokens = lexer.tokenize() - try expect(tokens.count) == 5 - try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is")!))) - try expect(tokens[1]) == Token.block(value: "if name and name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "{%")!))) - try expect(tokens[2]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name", options: [.backwards])!))) - try expect(tokens[3]) == Token.block(value: "endif", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "endif")!))) - try expect(tokens[4]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!))) + try expect(tokens.count) == 5 + try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "My name is")!))) + try expect(tokens[1]) == Token.block(value: "if name and name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "{%")!))) + try expect(tokens[2]) == Token.variable(value: "name", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "name", options: [.backwards])!))) + try expect(tokens[3]) == Token.block(value: "endif", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: "endif")!))) + try expect(tokens[4]) == Token.text(value: ".", at: SourceMap(location: lexer.rangeLocation(templateString.range(of: ".")!))) + } } } } diff --git a/Tests/StencilTests/LoaderSpec.swift b/Tests/StencilTests/LoaderSpec.swift index 6e207d6..91d541f 100644 --- a/Tests/StencilTests/LoaderSpec.swift +++ b/Tests/StencilTests/LoaderSpec.swift @@ -1,55 +1,57 @@ +import XCTest import Spectre import Stencil import PathKit +class TemplateLoaderTests: XCTestCase { + func testTemplateLoader() { + describe("FileSystemLoader") { + let path = Path(#file) + ".." + "fixtures" + let loader = FileSystemLoader(paths: [path]) + let environment = Environment(loader: loader) -func testTemplateLoader() { - describe("FileSystemLoader") { - let path = Path(#file) + ".." + "fixtures" - let loader = FileSystemLoader(paths: [path]) - let environment = Environment(loader: loader) + $0.it("errors when a template cannot be found") { + try expect(try environment.loadTemplate(name: "unknown.html")).toThrow() + } - $0.it("errors when a template cannot be found") { - try expect(try environment.loadTemplate(name: "unknown.html")).toThrow() + $0.it("errors when an array of templates cannot be found") { + try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow() + } + + $0.it("can load a template from a file") { + _ = try environment.loadTemplate(name: "test.html") + } + + $0.it("errors when loading absolute file outside of the selected path") { + try expect(try environment.loadTemplate(name: "/etc/hosts")).toThrow() + } + + $0.it("errors when loading relative file outside of the selected path") { + try expect(try environment.loadTemplate(name: "../LoaderSpec.swift")).toThrow() + } } - $0.it("errors when an array of templates cannot be found") { - try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow() - } - - $0.it("can load a template from a file") { - _ = try environment.loadTemplate(name: "test.html") - } - - $0.it("errors when loading absolute file outside of the selected path") { - try expect(try environment.loadTemplate(name: "/etc/hosts")).toThrow() - } - - $0.it("errors when loading relative file outside of the selected path") { - try expect(try environment.loadTemplate(name: "../LoaderSpec.swift")).toThrow() - } - } - - describe("DictionaryLoader") { - let loader = DictionaryLoader(templates: [ + describe("DictionaryLoader") { + let loader = DictionaryLoader(templates: [ "index.html": "Hello World" - ]) - let environment = Environment(loader: loader) + ]) + let environment = Environment(loader: loader) - $0.it("errors when a template cannot be found") { - try expect(try environment.loadTemplate(name: "unknown.html")).toThrow() - } + $0.it("errors when a template cannot be found") { + try expect(try environment.loadTemplate(name: "unknown.html")).toThrow() + } - $0.it("errors when an array of templates cannot be found") { - try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow() - } + $0.it("errors when an array of templates cannot be found") { + try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow() + } - $0.it("can load a template from a known templates") { - _ = try environment.loadTemplate(name: "index.html") - } + $0.it("can load a template from a known templates") { + _ = try environment.loadTemplate(name: "index.html") + } - $0.it("can load a known template from a collection of templates") { - _ = try environment.loadTemplate(names: ["unknown.html", "index.html"]) + $0.it("can load a known template from a collection of templates") { + _ = try environment.loadTemplate(names: ["unknown.html", "index.html"]) + } } } } diff --git a/Tests/StencilTests/NodeSpec.swift b/Tests/StencilTests/NodeSpec.swift index 1adfa26..1987282 100644 --- a/Tests/StencilTests/NodeSpec.swift +++ b/Tests/StencilTests/NodeSpec.swift @@ -1,7 +1,7 @@ +import XCTest import Spectre @testable import Stencil - class ErrorNode : NodeType { let token: Token? init(token: Token? = nil) { @@ -13,52 +13,53 @@ class ErrorNode : NodeType { } } +class NodeTests: XCTestCase { + func testNode() { + describe("Node") { + let context = Context(dictionary: [ + "name": "Kyle", + "age": 27, + "items": [1, 2, 3], + ]) -func testNode() { - describe("Node") { - let context = Context(dictionary: [ - "name": "Kyle", - "age": 27, - "items": [1, 2, 3], - ]) - - $0.describe("TextNode") { - $0.it("renders the given text") { - let node = TextNode(text: "Hello World") - try expect(try node.render(context)) == "Hello World" - } - } - - $0.describe("VariableNode") { - $0.it("resolves and renders the variable") { - let node = VariableNode(variable: Variable("name")) - try expect(try node.render(context)) == "Kyle" + $0.describe("TextNode") { + $0.it("renders the given text") { + let node = TextNode(text: "Hello World") + try expect(try node.render(context)) == "Hello World" + } } - $0.it("resolves and renders a non string variable") { - let node = VariableNode(variable: Variable("age")) - try expect(try node.render(context)) == "27" - } - } + $0.describe("VariableNode") { + $0.it("resolves and renders the variable") { + let node = VariableNode(variable: Variable("name")) + try expect(try node.render(context)) == "Kyle" + } - $0.describe("rendering nodes") { - $0.it("renders the nodes") { - let nodes: [NodeType] = [ - TextNode(text:"Hello "), - VariableNode(variable: "name"), - ] - - try expect(try renderNodes(nodes, context)) == "Hello Kyle" + $0.it("resolves and renders a non string variable") { + let node = VariableNode(variable: Variable("age")) + try expect(try node.render(context)) == "27" + } } - $0.it("correctly throws a nodes failure") { - let nodes: [NodeType] = [ - TextNode(text:"Hello "), - VariableNode(variable: "name"), - ErrorNode(), - ] + $0.describe("rendering nodes") { + $0.it("renders the nodes") { + let nodes: [NodeType] = [ + TextNode(text:"Hello "), + VariableNode(variable: "name"), + ] - try expect(try renderNodes(nodes, context)).toThrow(TemplateSyntaxError("Custom Error")) + try expect(try renderNodes(nodes, context)) == "Hello Kyle" + } + + $0.it("correctly throws a nodes failure") { + let nodes: [NodeType] = [ + TextNode(text:"Hello "), + VariableNode(variable: "name"), + ErrorNode(), + ] + + try expect(try renderNodes(nodes, context)).toThrow(TemplateSyntaxError("Custom Error")) + } } } } diff --git a/Tests/StencilTests/NowNodeSpec.swift b/Tests/StencilTests/NowNodeSpec.swift index 33a0d3f..e9a2a62 100644 --- a/Tests/StencilTests/NowNodeSpec.swift +++ b/Tests/StencilTests/NowNodeSpec.swift @@ -1,43 +1,46 @@ +import XCTest import Foundation import Spectre @testable import Stencil -func testNowNode() { -#if !os(Linux) - describe("NowNode") { - $0.describe("parsing") { - $0.it("parses default format without any now arguments") { - let tokens: [Token] = [ .block(value: "now", at: .unknown) ] - let parser = TokenParser(tokens: tokens, environment: Environment()) +class NowNodeTests: XCTestCase { + func testNowNode() { + #if !os(Linux) + describe("NowNode") { + $0.describe("parsing") { + $0.it("parses default format without any now arguments") { + let tokens: [Token] = [ .block(value: "now", at: .unknown) ] + let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? NowNode - try expect(nodes.count) == 1 - try expect(node?.format.variable) == "\"yyyy-MM-dd 'at' HH:mm\"" + let nodes = try parser.parse() + let node = nodes.first as? NowNode + try expect(nodes.count) == 1 + try expect(node?.format.variable) == "\"yyyy-MM-dd 'at' HH:mm\"" + } + + $0.it("parses now with a format") { + let tokens: [Token] = [ .block(value: "now \"HH:mm\"", at: .unknown) ] + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? NowNode + try expect(nodes.count) == 1 + try expect(node?.format.variable) == "\"HH:mm\"" + } } - $0.it("parses now with a format") { - let tokens: [Token] = [ .block(value: "now \"HH:mm\"", at: .unknown) ] - let parser = TokenParser(tokens: tokens, environment: Environment()) - let nodes = try parser.parse() - let node = nodes.first as? NowNode - try expect(nodes.count) == 1 - try expect(node?.format.variable) == "\"HH:mm\"" - } - } - - $0.describe("rendering") { - $0.it("renders the date") { - let node = NowNode(format: Variable("\"yyyy-MM-dd\"")) - - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - let date = formatter.string(from: NSDate() as Date) - - try expect(try node.render(Context())) == date + $0.describe("rendering") { + $0.it("renders the date") { + let node = NowNode(format: Variable("\"yyyy-MM-dd\"")) + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + let date = formatter.string(from: NSDate() as Date) + + try expect(try node.render(Context())) == date + } } } + #endif } -#endif } diff --git a/Tests/StencilTests/ParserSpec.swift b/Tests/StencilTests/ParserSpec.swift index 25c485f..e8650d3 100644 --- a/Tests/StencilTests/ParserSpec.swift +++ b/Tests/StencilTests/ParserSpec.swift @@ -1,61 +1,63 @@ +import XCTest import Spectre @testable import Stencil +class TokenParserTests: XCTestCase { + func testTokenParser() { + describe("TokenParser") { + $0.it("can parse a text token") { + let parser = TokenParser(tokens: [ + .text(value: "Hello World", at: .unknown) + ], environment: Environment()) -func testTokenParser() { - describe("TokenParser") { - $0.it("can parse a text token") { - let parser = TokenParser(tokens: [ - .text(value: "Hello World", at: .unknown) - ], environment: Environment()) + let nodes = try parser.parse() + let node = nodes.first as? TextNode - let nodes = try parser.parse() - let node = nodes.first as? TextNode - - try expect(nodes.count) == 1 - try expect(node?.text) == "Hello World" - } - - $0.it("can parse a variable token") { - let parser = TokenParser(tokens: [ - .variable(value: "'name'", at: .unknown) - ], environment: Environment()) - - let nodes = try parser.parse() - let node = nodes.first as? VariableNode - try expect(nodes.count) == 1 - let result = try node?.render(Context()) - try expect(result) == "name" - } - - $0.it("can parse a comment token") { - let parser = TokenParser(tokens: [ - .comment(value: "Secret stuff!", at: .unknown) - ], environment: Environment()) - - let nodes = try parser.parse() - try expect(nodes.count) == 0 - } - - $0.it("can parse a tag token") { - let simpleExtension = Extension() - simpleExtension.registerSimpleTag("known") { _ in - return "" + try expect(nodes.count) == 1 + try expect(node?.text) == "Hello World" } - let parser = TokenParser(tokens: [ - .block(value: "known", at: .unknown), - ], environment: Environment(extensions: [simpleExtension])) + $0.it("can parse a variable token") { + let parser = TokenParser(tokens: [ + .variable(value: "'name'", at: .unknown) + ], environment: Environment()) - let nodes = try parser.parse() - try expect(nodes.count) == 1 - } + let nodes = try parser.parse() + let node = nodes.first as? VariableNode + try expect(nodes.count) == 1 + let result = try node?.render(Context()) + try expect(result) == "name" + } - $0.it("errors when parsing an unknown tag") { - let tokens: [Token] = [.block(value: "unknown", at: .unknown)] - let parser = TokenParser(tokens: tokens, environment: Environment()) + $0.it("can parse a comment token") { + let parser = TokenParser(tokens: [ + .comment(value: "Secret stuff!", at: .unknown) + ], environment: Environment()) - try expect(try parser.parse()).toThrow(TemplateSyntaxError(reason: "Unknown template tag 'unknown'", token: tokens.first)) + let nodes = try parser.parse() + try expect(nodes.count) == 0 + } + + $0.it("can parse a tag token") { + let simpleExtension = Extension() + simpleExtension.registerSimpleTag("known") { _ in + return "" + } + + let parser = TokenParser(tokens: [ + .block(value: "known", at: .unknown), + ], environment: Environment(extensions: [simpleExtension])) + + let nodes = try parser.parse() + try expect(nodes.count) == 1 + } + + $0.it("errors when parsing an unknown tag") { + let tokens: [Token] = [.block(value: "unknown", at: .unknown)] + let parser = TokenParser(tokens: tokens, environment: Environment()) + + try expect(try parser.parse()).toThrow(TemplateSyntaxError(reason: "Unknown template tag 'unknown'", token: tokens.first)) + } } } } diff --git a/Tests/StencilTests/StencilSpec.swift b/Tests/StencilTests/StencilSpec.swift index 137f5be..5ec6094 100644 --- a/Tests/StencilTests/StencilSpec.swift +++ b/Tests/StencilTests/StencilSpec.swift @@ -1,7 +1,7 @@ +import XCTest import Spectre import Stencil - fileprivate struct CustomNode : NodeType { let token: Token? func render(_ context:Context) throws -> String { @@ -9,64 +9,64 @@ fileprivate struct CustomNode : NodeType { } } - fileprivate struct Article { let title: String let author: String } +class StencilTests: XCTestCase { + func testStencil() { + describe("Stencil") { + let exampleExtension = Extension() -func testStencil() { - describe("Stencil") { - let exampleExtension = Extension() + exampleExtension.registerSimpleTag("simpletag") { context in + return "Hello World" + } - exampleExtension.registerSimpleTag("simpletag") { context in - return "Hello World" - } + exampleExtension.registerTag("customtag") { parser, token in + return CustomNode(token: token) + } - exampleExtension.registerTag("customtag") { parser, token in - return CustomNode(token: token) - } + let environment = Environment(extensions: [exampleExtension]) - let environment = Environment(extensions: [exampleExtension]) + $0.it("can render the README example") { - $0.it("can render the README example") { + let templateString = """ + There are {{ articles.count }} articles. - let templateString = """ - There are {{ articles.count }} articles. + {% for article in articles %}\ + - {{ article.title }} by {{ article.author }}. + {% endfor %} + """ - {% for article in articles %}\ - - {{ article.title }} by {{ article.author }}. - {% endfor %} - """ - - let context = [ - "articles": [ - Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"), - Article(title: "Memory Management with ARC", author: "Kyle Fuller"), + let context = [ + "articles": [ + Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"), + Article(title: "Memory Management with ARC", author: "Kyle Fuller"), + ] ] - ] - let template = Template(templateString: templateString) - let result = try template.render(context) + let template = Template(templateString: templateString) + let result = try template.render(context) - try expect(result) == """ - There are 2 articles. + try expect(result) == """ + There are 2 articles. - - Migrating from OCUnit to XCTest by Kyle Fuller. - - Memory Management with ARC by Kyle Fuller. + - Migrating from OCUnit to XCTest by Kyle Fuller. + - Memory Management with ARC by Kyle Fuller. - """ - } + """ + } - $0.it("can render a custom template tag") { - let result = try environment.renderTemplate(string: "{% customtag %}") - try expect(result) == "Hello World" - } + $0.it("can render a custom template tag") { + let result = try environment.renderTemplate(string: "{% customtag %}") + try expect(result) == "Hello World" + } - $0.it("can render a simple custom tag") { - let result = try environment.renderTemplate(string: "{% simpletag %}") - try expect(result) == "Hello World" + $0.it("can render a simple custom tag") { + let result = try environment.renderTemplate(string: "{% simpletag %}") + try expect(result) == "Hello World" + } } } } diff --git a/Tests/StencilTests/TemplateSpec.swift b/Tests/StencilTests/TemplateSpec.swift index fee0c5e..3d3001b 100644 --- a/Tests/StencilTests/TemplateSpec.swift +++ b/Tests/StencilTests/TemplateSpec.swift @@ -1,20 +1,22 @@ +import XCTest import Spectre @testable import Stencil - -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") { +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") { 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 dd73298..0722b6d 100644 --- a/Tests/StencilTests/TokenSpec.swift +++ b/Tests/StencilTests/TokenSpec.swift @@ -1,34 +1,36 @@ +import XCTest import Spectre @testable import Stencil +class TokenTests: XCTestCase { + func testToken() { + describe("Token") { + $0.it("can split the contents into components") { + let token = Token.text(value: "hello world", at: .unknown) + let components = token.components() -func testToken() { - describe("Token") { - $0.it("can split the contents into components") { - let token = Token.text(value: "hello world", at: .unknown) - let components = token.components() + try expect(components.count) == 2 + try expect(components[0]) == "hello" + try expect(components[1]) == "world" + } - try expect(components.count) == 2 - try expect(components[0]) == "hello" - try expect(components[1]) == "world" - } + $0.it("can split the contents into components with single quoted strings") { + let token = Token.text(value: "hello 'kyle fuller'", at: .unknown) + let components = token.components() - $0.it("can split the contents into components with single quoted strings") { - let token = Token.text(value: "hello 'kyle fuller'", at: .unknown) - let components = token.components() + try expect(components.count) == 2 + try expect(components[0]) == "hello" + try expect(components[1]) == "'kyle fuller'" + } - try expect(components.count) == 2 - try expect(components[0]) == "hello" - try expect(components[1]) == "'kyle fuller'" - } + $0.it("can split the contents into components with double quoted strings") { + let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown) + let components = token.components() - $0.it("can split the contents into components with double quoted strings") { - let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown) - let components = token.components() - - try expect(components.count) == 2 - try expect(components[0]) == "hello" - try expect(components[1]) == "\"kyle fuller\"" + try expect(components.count) == 2 + try expect(components[0]) == "hello" + try expect(components[1]) == "\"kyle fuller\"" + } } } } diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index 2d052bb..ef2632d 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -1,3 +1,4 @@ +import XCTest import Foundation import Spectre @testable import Stencil @@ -29,381 +30,383 @@ fileprivate class Blog: WebSite { let featuring: Article? = Article(author: Person(name: "Jhon")) } -func testVariable() { - describe("Variable") { - let context = Context(dictionary: [ - "name": "Kyle", - "contacts": ["Katie", "Carlton"], - "profiles": [ - "github": "kylef", - ], - "counter": [ - "count": "kylef", +class VariableTests: XCTestCase { + func testVariable() { + describe("Variable") { + let context = Context(dictionary: [ + "name": "Kyle", + "contacts": ["Katie", "Carlton"], + "profiles": [ + "github": "kylef", ], - "article": Article(author: Person(name: "Kyle")), - "tuple": (one: 1, two: 2) - ]) + "counter": [ + "count": "kylef", + ], + "article": Article(author: Person(name: "Kyle")), + "tuple": (one: 1, two: 2) + ]) -#if os(OSX) - context["object"] = Object() -#endif - context["blog"] = Blog() + #if os(OSX) + context["object"] = Object() + #endif + context["blog"] = Blog() - $0.it("can resolve a string literal with double quotes") { - let variable = Variable("\"name\"") - let result = try variable.resolve(context) as? String - try expect(result) == "name" - } - - $0.it("can resolve a string literal with single quotes") { - let variable = Variable("'name'") - let result = try variable.resolve(context) as? String - try expect(result) == "name" - } - - $0.it("can resolve an integer literal") { - let variable = Variable("5") - let result = try variable.resolve(context) as? Int - try expect(result) == 5 - } - - $0.it("can resolve an float literal") { - let variable = Variable("3.14") - let result = try variable.resolve(context) as? Number - try expect(result) == 3.14 - } - - $0.it("can resolve boolean literal") { - try expect(Variable("true").resolve(context) as? Bool) == true - try expect(Variable("false").resolve(context) as? Bool) == false - try expect(Variable("0").resolve(context) as? Int) == 0 - try expect(Variable("1").resolve(context) as? Int) == 1 - } - - $0.it("can resolve a string variable") { - let variable = Variable("name") - let result = try variable.resolve(context) as? String - try expect(result) == "Kyle" - } - - $0.context("given string") { - $0.it("can resolve an item via it's index") { - let variable = Variable("name.0") - let result = try variable.resolve(context) as? Character - try expect(result) == "K" - - let variable1 = Variable("name.1") - let result1 = try variable1.resolve(context) as? Character - try expect(result1) == "y" + $0.it("can resolve a string literal with double quotes") { + let variable = Variable("\"name\"") + let result = try variable.resolve(context) as? String + try expect(result) == "name" } - $0.it("can resolve an item via unknown index") { - let variable = Variable("name.5") - let result = try variable.resolve(context) as? Character - try expect(result).to.beNil() - - let variable1 = Variable("name.-5") - let result1 = try variable1.resolve(context) as? Character - try expect(result1).to.beNil() + $0.it("can resolve a string literal with single quotes") { + let variable = Variable("'name'") + let result = try variable.resolve(context) as? String + try expect(result) == "name" } - $0.it("can resolve the first item") { - let variable = Variable("name.first") - let result = try variable.resolve(context) as? Character - try expect(result) == "K" - } - - $0.it("can resolve the last item") { - let variable = Variable("name.last") - let result = try variable.resolve(context) as? Character - try expect(result) == "e" - } - - $0.it("can get the characters count") { - let variable = Variable("name.count") + $0.it("can resolve an integer literal") { + let variable = Variable("5") let result = try variable.resolve(context) as? Int - try expect(result) == 4 + try expect(result) == 5 } - } - $0.context("given dictionary") { - $0.it("can resolve an item") { - let variable = Variable("profiles.github") + $0.it("can resolve an float literal") { + let variable = Variable("3.14") + let result = try variable.resolve(context) as? Number + try expect(result) == 3.14 + } + + $0.it("can resolve boolean literal") { + try expect(Variable("true").resolve(context) as? Bool) == true + try expect(Variable("false").resolve(context) as? Bool) == false + try expect(Variable("0").resolve(context) as? Int) == 0 + try expect(Variable("1").resolve(context) as? Int) == 1 + } + + $0.it("can resolve a string variable") { + let variable = Variable("name") let result = try variable.resolve(context) as? String - try expect(result) == "kylef" + try expect(result) == "Kyle" } - $0.it("can get the count") { - let variable = Variable("profiles.count") - let result = try variable.resolve(context) as? Int - try expect(result) == 1 - } - } + $0.context("given string") { + $0.it("can resolve an item via it's index") { + let variable = Variable("name.0") + let result = try variable.resolve(context) as? Character + try expect(result) == "K" - $0.context("given array") { - $0.it("can resolve an item via it's index") { - let variable = Variable("contacts.0") - let result = try variable.resolve(context) as? String - try expect(result) == "Katie" + let variable1 = Variable("name.1") + let result1 = try variable1.resolve(context) as? Character + try expect(result1) == "y" + } - let variable1 = Variable("contacts.1") - let result1 = try variable1.resolve(context) as? String - try expect(result1) == "Carlton" - } + $0.it("can resolve an item via unknown index") { + let variable = Variable("name.5") + let result = try variable.resolve(context) as? Character + try expect(result).to.beNil() - $0.it("can resolve an item via unknown index") { - let variable = Variable("contacts.5") - let result = try variable.resolve(context) as? String - try expect(result).to.beNil() + let variable1 = Variable("name.-5") + let result1 = try variable1.resolve(context) as? Character + try expect(result1).to.beNil() + } - let variable1 = Variable("contacts.-5") - let result1 = try variable1.resolve(context) as? String - try expect(result1).to.beNil() - } + $0.it("can resolve the first item") { + let variable = Variable("name.first") + let result = try variable.resolve(context) as? Character + try expect(result) == "K" + } - $0.it("can resolve the first item") { - let variable = Variable("contacts.first") - let result = try variable.resolve(context) as? String - try expect(result) == "Katie" - } + $0.it("can resolve the last item") { + let variable = Variable("name.last") + let result = try variable.resolve(context) as? Character + try expect(result) == "e" + } - $0.it("can resolve the last item") { - let variable = Variable("contacts.last") - let result = try variable.resolve(context) as? String - try expect(result) == "Carlton" - } - - $0.it("can get the count") { - let variable = Variable("contacts.count") - let result = try variable.resolve(context) as? Int - try expect(result) == 2 - } - } - - $0.it("can resolve a property with reflection") { - let variable = Variable("article.author.name") - let result = try variable.resolve(context) as? String - try expect(result) == "Kyle" - } - -#if os(OSX) - $0.it("can resolve a value via KVO") { - let variable = Variable("object.title") - let result = try variable.resolve(context) as? String - try expect(result) == "Hello World" - } - - $0.it("can resolve a superclass value via KVO") { - let variable = Variable("object.name") - let result = try variable.resolve(context) as? String - try expect(result) == "Foo" - } - - $0.it("does not crash on KVO") { - let variable = Variable("object.fullname") - let result = try variable.resolve(context) as? String - try expect(result).to.beNil() - } -#endif - - $0.it("can resolve a value via reflection") { - let variable = Variable("blog.articles.0.author.name") - let result = try variable.resolve(context) as? String - try expect(result) == "Kyle" - } - - $0.it("can resolve a superclass value via reflection") { - let variable = Variable("blog.url") - let result = try variable.resolve(context) as? String - try expect(result) == "blog.com" - } - - $0.it("can resolve optional variable property using reflection") { - let variable = Variable("blog.featuring.author.name") - let result = try variable.resolve(context) as? String - try expect(result) == "Jhon" - } - - $0.it("does not render Optional") { - var array: [Any?] = [1, nil] - array.append(array) - let context = Context(dictionary: ["values": array]) - - try expect(VariableNode(variable: "values").render(context)) == "[1, nil, [1, nil]]" - try expect(VariableNode(variable: "values.1").render(context)) == "" - } - - $0.it("can subscript tuple by index") { - let variable = Variable("tuple.0") - let result = try variable.resolve(context) as? Int - try expect(result) == 1 - } - - $0.it("can subscript tuple by label") { - let variable = Variable("tuple.two") - let result = try variable.resolve(context) as? Int - try expect(result) == 2 - } - - $0.describe("Subscripting") { - $0.it("can resolve a property subscript via reflection") { - try context.push(dictionary: ["property": "name"]) { - let variable = Variable("article.author[property]") - let result = try variable.resolve(context) as? String - try expect(result) == "Kyle" + $0.it("can get the characters count") { + let variable = Variable("name.count") + let result = try variable.resolve(context) as? Int + try expect(result) == 4 } } - $0.it("can subscript an array with a valid index") { - try context.push(dictionary: ["property": 0]) { - let variable = Variable("contacts[property]") + $0.context("given dictionary") { + $0.it("can resolve an item") { + let variable = Variable("profiles.github") + let result = try variable.resolve(context) as? String + try expect(result) == "kylef" + } + + $0.it("can get the count") { + let variable = Variable("profiles.count") + let result = try variable.resolve(context) as? Int + try expect(result) == 1 + } + } + + $0.context("given array") { + $0.it("can resolve an item via it's index") { + let variable = Variable("contacts.0") + let result = try variable.resolve(context) as? String + try expect(result) == "Katie" + + let variable1 = Variable("contacts.1") + let result1 = try variable1.resolve(context) as? String + try expect(result1) == "Carlton" + } + + $0.it("can resolve an item via unknown index") { + let variable = Variable("contacts.5") + let result = try variable.resolve(context) as? String + try expect(result).to.beNil() + + let variable1 = Variable("contacts.-5") + let result1 = try variable1.resolve(context) as? String + try expect(result1).to.beNil() + } + + $0.it("can resolve the first item") { + let variable = Variable("contacts.first") let result = try variable.resolve(context) as? String try expect(result) == "Katie" } - } - $0.it("can subscript an array with an unknown index") { - try context.push(dictionary: ["property": 5]) { - let variable = Variable("contacts[property]") + $0.it("can resolve the last item") { + let variable = Variable("contacts.last") let result = try variable.resolve(context) as? String - try expect(result).to.beNil() + try expect(result) == "Carlton" + } + + $0.it("can get the count") { + let variable = Variable("contacts.count") + let result = try variable.resolve(context) as? Int + try expect(result) == 2 } } -#if os(OSX) - $0.it("can resolve a subscript via KVO") { - try context.push(dictionary: ["property": "name"]) { - let variable = Variable("object[property]") - let result = try variable.resolve(context) as? String - try expect(result) == "Foo" - } - } -#endif - - $0.it("can resolve an optional subscript via reflection") { - try context.push(dictionary: ["property": "featuring"]) { - let variable = Variable("blog[property].author.name") - let result = try variable.resolve(context) as? String - try expect(result) == "Jhon" - } + $0.it("can resolve a property with reflection") { + let variable = Variable("article.author.name") + let result = try variable.resolve(context) as? String + try expect(result) == "Kyle" } - $0.it("can resolve multiple subscripts") { - try context.push(dictionary: [ - "prop1": "articles", - "prop2": 0, - "prop3": "name" - ]) { - let variable = Variable("blog[prop1][prop2].author[prop3]") - let result = try variable.resolve(context) as? String - try expect(result) == "Kyle" - } + #if os(OSX) + $0.it("can resolve a value via KVO") { + let variable = Variable("object.title") + let result = try variable.resolve(context) as? String + try expect(result) == "Hello World" } - $0.it("can resolve nested subscripts") { - try context.push(dictionary: [ - "prop1": "prop2", - "ref": ["prop2": "name"] - ]) { - let variable = Variable("article.author[ref[prop1]]") - let result = try variable.resolve(context) as? String - try expect(result) == "Kyle" - } + $0.it("can resolve a superclass value via KVO") { + let variable = Variable("object.name") + let result = try variable.resolve(context) as? String + try expect(result) == "Foo" } - $0.it("throws for invalid keypath syntax") { - try context.push(dictionary: ["prop": "name"]) { - let samples = [ - ".", - "..", - ".test", - "test..test", - "[prop]", - "article.author[prop", - "article.author[[prop]", - "article.author[prop]]", - "article.author[]", - "article.author[[]]", - "article.author[prop][]", - "article.author[prop]comments", - "article.author[.]" - ] + $0.it("does not crash on KVO") { + let variable = Variable("object.fullname") + let result = try variable.resolve(context) as? String + try expect(result).to.beNil() + } + #endif - for lookup in samples { - let variable = Variable(lookup) - try expect(variable.resolve(context)).toThrow() + $0.it("can resolve a value via reflection") { + let variable = Variable("blog.articles.0.author.name") + let result = try variable.resolve(context) as? String + try expect(result) == "Kyle" + } + + $0.it("can resolve a superclass value via reflection") { + let variable = Variable("blog.url") + let result = try variable.resolve(context) as? String + try expect(result) == "blog.com" + } + + $0.it("can resolve optional variable property using reflection") { + let variable = Variable("blog.featuring.author.name") + let result = try variable.resolve(context) as? String + try expect(result) == "Jhon" + } + + $0.it("does not render Optional") { + var array: [Any?] = [1, nil] + array.append(array) + let context = Context(dictionary: ["values": array]) + + try expect(VariableNode(variable: "values").render(context)) == "[1, nil, [1, nil]]" + try expect(VariableNode(variable: "values.1").render(context)) == "" + } + + $0.it("can subscript tuple by index") { + let variable = Variable("tuple.0") + let result = try variable.resolve(context) as? Int + try expect(result) == 1 + } + + $0.it("can subscript tuple by label") { + let variable = Variable("tuple.two") + let result = try variable.resolve(context) as? Int + try expect(result) == 2 + } + + $0.describe("Subscripting") { + $0.it("can resolve a property subscript via reflection") { + try context.push(dictionary: ["property": "name"]) { + let variable = Variable("article.author[property]") + let result = try variable.resolve(context) as? String + try expect(result) == "Kyle" + } + } + + $0.it("can subscript an array with a valid index") { + try context.push(dictionary: ["property": 0]) { + let variable = Variable("contacts[property]") + let result = try variable.resolve(context) as? String + try expect(result) == "Katie" + } + } + + $0.it("can subscript an array with an unknown index") { + try context.push(dictionary: ["property": 5]) { + let variable = Variable("contacts[property]") + let result = try variable.resolve(context) as? String + try expect(result).to.beNil() + } + } + + #if os(OSX) + $0.it("can resolve a subscript via KVO") { + try context.push(dictionary: ["property": "name"]) { + let variable = Variable("object[property]") + let result = try variable.resolve(context) as? String + try expect(result) == "Foo" + } + } + #endif + + $0.it("can resolve an optional subscript via reflection") { + try context.push(dictionary: ["property": "featuring"]) { + let variable = Variable("blog[property].author.name") + let result = try variable.resolve(context) as? String + try expect(result) == "Jhon" + } + } + + $0.it("can resolve multiple subscripts") { + try context.push(dictionary: [ + "prop1": "articles", + "prop2": 0, + "prop3": "name" + ]) { + let variable = Variable("blog[prop1][prop2].author[prop3]") + let result = try variable.resolve(context) as? String + try expect(result) == "Kyle" + } + } + + $0.it("can resolve nested subscripts") { + try context.push(dictionary: [ + "prop1": "prop2", + "ref": ["prop2": "name"] + ]) { + let variable = Variable("article.author[ref[prop1]]") + let result = try variable.resolve(context) as? String + try expect(result) == "Kyle" + } + } + + $0.it("throws for invalid keypath syntax") { + try context.push(dictionary: ["prop": "name"]) { + let samples = [ + ".", + "..", + ".test", + "test..test", + "[prop]", + "article.author[prop", + "article.author[[prop]", + "article.author[prop]]", + "article.author[]", + "article.author[[]]", + "article.author[prop][]", + "article.author[prop]comments", + "article.author[.]" + ] + + for lookup in samples { + let variable = Variable(lookup) + try expect(variable.resolve(context)).toThrow() + } } } } } - } - describe("RangeVariable") { + describe("RangeVariable") { - let context: Context = { - let ext = Extension() - ext.registerFilter("incr", filter: { (arg: Any?) in toNumber(value: arg!)! + 1 }) - let environment = Environment(extensions: [ext]) - return Context(dictionary: [:], environment: environment) - }() + let context: Context = { + let ext = Extension() + ext.registerFilter("incr", filter: { (arg: Any?) in toNumber(value: arg!)! + 1 }) + let environment = Environment(extensions: [ext]) + return Context(dictionary: [:], environment: environment) + }() + + func makeVariable(_ token: String) throws -> RangeVariable? { + let token = Token.variable(value: token, at: .unknown) + let parser = TokenParser(tokens: [token], environment: context.environment) + return try RangeVariable(token.contents, parser: parser, containedIn: token) + } + + $0.it("can resolve closed range as array") { + let result = try makeVariable("1...3")?.resolve(context) as? [Int] + try expect(result) == [1, 2, 3] + } + + $0.it("can resolve decreasing closed range as reversed array") { + let result = try makeVariable("3...1")?.resolve(context) as? [Int] + try expect(result) == [3, 2, 1] + } + + $0.it("can use filter on range variables") { + let result = try makeVariable("1|incr...3|incr")?.resolve(context) as? [Int] + try expect(result) == [2, 3, 4] + } + + $0.it("throws when left value is not int") { + let template: Template = "{% for i in k...j %}{{ i }}{% endfor %}" + try expect(try template.render(Context(dictionary: ["j": 3, "k": "1"]))).toThrow() + } + + $0.it("throws when right value is not int") { + let variable = try makeVariable("k...j") + try expect(try variable?.resolve(Context(dictionary: ["j": "3", "k": 1]))).toThrow() + } + + $0.it("throws is left range value is missing") { + try expect(makeVariable("...1")).toThrow() + } + + $0.it("throws is right range value is missing") { + try expect(makeVariable("1...")).toThrow() + } - func makeVariable(_ token: String) throws -> RangeVariable? { - let token = Token.variable(value: token, at: .unknown) - let parser = TokenParser(tokens: [token], environment: context.environment) - return try RangeVariable(token.contents, parser: parser, containedIn: token) } - $0.it("can resolve closed range as array") { - let result = try makeVariable("1...3")?.resolve(context) as? [Int] - try expect(result) == [1, 2, 3] - } + describe("inline if expression") { - $0.it("can resolve decreasing closed range as reversed array") { - let result = try makeVariable("3...1")?.resolve(context) as? [Int] - try expect(result) == [3, 2, 1] - } + $0.it("can conditionally render variable") { + let template: Template = "{{ variable if variable|uppercase == \"A\" }}" + try expect(template.render(Context(dictionary: ["variable": "a"]))) == "a" + try expect(template.render(Context(dictionary: ["variable": "b"]))) == "" + } - $0.it("can use filter on range variables") { - let result = try makeVariable("1|incr...3|incr")?.resolve(context) as? [Int] - try expect(result) == [2, 3, 4] - } + $0.it("can render with else expression") { + let template: Template = "{{ variable if variable|uppercase == \"A\" else fallback|uppercase }}" + try expect(template.render(Context(dictionary: ["variable": "b", "fallback": "c"]))) == "C" + } - $0.it("throws when left value is not int") { - let template: Template = "{% for i in k...j %}{{ i }}{% endfor %}" - try expect(try template.render(Context(dictionary: ["j": 3, "k": "1"]))).toThrow() - } - - $0.it("throws when right value is not int") { - let variable = try makeVariable("k...j") - try expect(try variable?.resolve(Context(dictionary: ["j": "3", "k": 1]))).toThrow() - } - - $0.it("throws is left range value is missing") { - try expect(makeVariable("...1")).toThrow() - } - - $0.it("throws is right range value is missing") { - try expect(makeVariable("1...")).toThrow() - } - - } - - describe("inline if expression") { - - $0.it("can conditionally render variable") { - let template: Template = "{{ variable if variable|uppercase == \"A\" }}" - try expect(template.render(Context(dictionary: ["variable": "a"]))) == "a" - try expect(template.render(Context(dictionary: ["variable": "b"]))) == "" - } - - $0.it("can render with else expression") { - let template: Template = "{{ variable if variable|uppercase == \"A\" else fallback|uppercase }}" - try expect(template.render(Context(dictionary: ["variable": "b", "fallback": "c"]))) == "C" - } - - $0.it("throws when used invalid condition") { - let template: Template = "{{ variable if variable \"A\" }}" - try expect(template.render(Context(dictionary: ["variable": "a"]))).toThrow() + $0.it("throws when used invalid condition") { + let template: Template = "{{ variable if variable \"A\" }}" + try expect(template.render(Context(dictionary: ["variable": "a"]))).toThrow() + } } } } diff --git a/Tests/StencilTests/XCTest.swift b/Tests/StencilTests/XCTest.swift deleted file mode 100644 index b2d4de9..0000000 --- a/Tests/StencilTests/XCTest.swift +++ /dev/null @@ -1,30 +0,0 @@ -import XCTest - - -public func stencilTests() { - testContext() - testFilter() - testLexer() - testToken() - testTokenParser() - testTemplateLoader() - testTemplate() - testVariable() - testNode() - testForNode() - testExpressions() - testIfNode() - testNowNode() - testInclude() - testInheritence() - testFilterTag() - testEnvironment() - testStencil() -} - - -class StencilTests: XCTestCase { - func testRunStencilTests() { - stencilTests() - } -} diff --git a/Tests/StencilTests/XCTestManifests.swift b/Tests/StencilTests/XCTestManifests.swift new file mode 100644 index 0000000..84f6cce --- /dev/null +++ b/Tests/StencilTests/XCTestManifests.swift @@ -0,0 +1,134 @@ +import XCTest + +extension ContextTests { + static let __allTests = [ + ("testContext", testContext), + ] +} + +extension EnvironmentTests { + static let __allTests = [ + ("testEnvironment", testEnvironment), + ] +} + +extension ExpressionsTests { + static let __allTests = [ + ("testExpressions", testExpressions), + ] +} + +extension FilterTagTests { + static let __allTests = [ + ("testFilterTag", testFilterTag), + ] +} + +extension FilterTests { + static let __allTests = [ + ("testFilter", testFilter), + ] +} + +extension ForNodeTests { + static let __allTests = [ + ("testForNode", testForNode), + ] +} + +extension IfNodeTests { + static let __allTests = [ + ("testIfNode", testIfNode), + ] +} + +extension IncludeTests { + static let __allTests = [ + ("testInclude", testInclude), + ] +} + +extension InheritenceTests { + static let __allTests = [ + ("testInheritence", testInheritence), + ] +} + +extension LexerTests { + static let __allTests = [ + ("testLexer", testLexer), + ] +} + +extension NodeTests { + static let __allTests = [ + ("testNode", testNode), + ] +} + +extension NowNodeTests { + static let __allTests = [ + ("testNowNode", testNowNode), + ] +} + +extension StencilTests { + static let __allTests = [ + ("testStencil", testStencil), + ] +} + +extension TemplateLoaderTests { + static let __allTests = [ + ("testTemplateLoader", testTemplateLoader), + ] +} + +extension TemplateTests { + static let __allTests = [ + ("testTemplate", testTemplate), + ] +} + +extension TokenParserTests { + static let __allTests = [ + ("testTokenParser", testTokenParser), + ] +} + +extension TokenTests { + static let __allTests = [ + ("testToken", testToken), + ] +} + +extension VariableTests { + static let __allTests = [ + ("testVariable", testVariable), + ] +} + +#if !os(macOS) +public func __allTests() -> [XCTestCaseEntry] { + return [ + testCase(ContextTests.__allTests), + testCase(EnvironmentTests.__allTests), + testCase(ExpressionsTests.__allTests), + testCase(FilterTagTests.__allTests), + testCase(FilterTests.__allTests), + testCase(ForNodeTests.__allTests), + testCase(IfNodeTests.__allTests), + testCase(IncludeTests.__allTests), + testCase(InheritenceTests.__allTests), + testCase(LexerTests.__allTests), + testCase(NodeTests.__allTests), + testCase(NowNodeTests.__allTests), + testCase(StencilTests.__allTests), + testCase(TemplateLoaderTests.__allTests), + testCase(TemplateTests.__allTests), + testCase(TokenParserTests.__allTests), + testCase(TokenTests.__allTests), + testCase(VariableTests.__allTests), + ] +} +#endif