Merge pull request #249 from stencilproject/feature/swiftlint

SwiftLint integration
This commit is contained in:
David Jennes
2018-11-11 17:43:29 +01:00
committed by GitHub
45 changed files with 3001 additions and 2823 deletions

54
.swiftlint.yml Normal file
View File

@@ -0,0 +1,54 @@
opt_in_rules:
- anyobject_protocol
- array_init
- attributes
- closure_end_indentation
- closure_spacing
- contains_over_first_not_nil
- convenience_type
- discouraged_optional_boolean
- discouraged_optional_collection
- empty_count
- empty_string
- fallthrough
- fatal_error_message
- first_where
- force_unwrapping
- implicit_return
- implicitly_unwrapped_optional
- joined_default_parameter
- literal_expression_end_indentation
- lower_acl_than_parent
- modifier_order
- multiline_arguments
- multiline_function_chains
- multiline_parameters
- number_separator
- operator_usage_whitespace
- overridden_super_call
- override_in_extension
- private_action
- private_outlet
- prohibited_super_call
- redundant_nil_coalescing
- sorted_first_last
- sorted_imports
- trailing_closure
- unavailable_function
- unneeded_parentheses_in_closure_argument
- vertical_parameter_alignment_on_call
- yoda_condition
# Rules customization
line_length:
warning: 120
error: 200
nesting:
type_level:
warning: 2
# Exclude generated files
excluded:
- .build
- Tests/StencilTests/XCTestManifests.swift

View File

@@ -20,5 +20,8 @@ sudo: required
dist: trusty
install:
- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then wget --output-document /tmp/SwiftLint.pkg https://github.com/realm/SwiftLint/releases/download/0.27.0/SwiftLint.pkg &&
sudo installer -pkg /tmp/SwiftLint.pkg -target /; fi
script:
- swift test
- swift test
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then swiftlint; fi

View File

@@ -28,6 +28,9 @@ _None_
- `Token` type converted to struct to allow computing token components only once.
[Ilya Puchka](https://github.com/ilyapuchka)
[#256](https://github.com/stencilproject/Stencil/pull/256)
- Added SwiftLint to the project.
[David Jennes](https://github.com/djbe)
[#249](https://github.com/stencilproject/Stencil/pull/249)
## 0.13.1

View File

@@ -4,8 +4,8 @@ public class Context {
public let environment: Environment
init(dictionary: [String: Any]? = nil, environment: Environment? = nil) {
if let dictionary = dictionary {
init(dictionary: [String: Any] = [:], environment: Environment? = nil) {
if !dictionary.isEmpty {
dictionaries = [dictionary]
} else {
dictionaries = []
@@ -28,17 +28,16 @@ public class Context {
/// Set a variable in the current context, deleting the variable if it's nil
set(value) {
if let dictionary = dictionaries.popLast() {
var mutable_dictionary = dictionary
mutable_dictionary[key] = value
dictionaries.append(mutable_dictionary)
if var dictionary = dictionaries.popLast() {
dictionary[key] = value
dictionaries.append(dictionary)
}
}
}
/// Push a new level into the Context
fileprivate func push(_ dictionary: [String: Any]? = nil) {
dictionaries.append(dictionary ?? [:])
fileprivate func push(_ dictionary: [String: Any] = [:]) {
dictionaries.append(dictionary)
}
/// Pop the last level off of the Context
@@ -47,7 +46,7 @@ public class Context {
}
/// Push a new level onto the context for the duration of the execution of the given closure
public func push<Result>(dictionary: [String: Any]? = nil, closure: (() throws -> Result)) rethrows -> Result {
public func push<Result>(dictionary: [String: Any] = [:], closure: (() throws -> Result)) rethrows -> Result {
push(dictionary)
defer { _ = pop() }
return try closure()

View File

@@ -5,12 +5,12 @@ public struct Environment {
public var loader: Loader?
public init(loader: Loader? = nil,
extensions: [Extension]? = nil,
extensions: [Extension] = [],
templateClass: Template.Type = Template.self) {
self.templateClass = templateClass
self.loader = loader
self.extensions = (extensions ?? []) + [DefaultExtension()]
self.extensions = extensions + [DefaultExtension()]
}
public func loadTemplate(name: String) throws -> Template {
@@ -29,17 +29,17 @@ public struct Environment {
}
}
public func renderTemplate(name: String, context: [String: Any]? = nil) throws -> String {
public func renderTemplate(name: String, context: [String: Any] = [:]) throws -> String {
let template = try loadTemplate(name: name)
return try render(template: template, context: context)
}
public func renderTemplate(string: String, context: [String: Any]? = nil) throws -> String {
public func renderTemplate(string: String, context: [String: Any] = [:]) throws -> String {
let template = templateClass.init(templateString: string, environment: self)
return try render(template: template, context: context)
}
func render(template: Template, context: [String: Any]?) throws -> String {
func render(template: Template, context: [String: Any]) throws -> String {
// update template environment as it can be created from string literal with default environment
template.environment = self
return try template.render(context)

View File

@@ -18,14 +18,14 @@ public class TemplateDoesNotExist: Error, CustomStringConvertible {
}
}
public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
public struct TemplateSyntaxError: Error, Equatable, CustomStringConvertible {
public let reason: String
public var description: String { return reason }
public internal(set) var token: Token?
public internal(set) var stackTrace: [Token]
public var templateName: String? { return token?.sourceMap.filename }
var allTokens: [Token] {
return stackTrace + (token.map({ [$0] }) ?? [])
return stackTrace + (token.map { [$0] } ?? [])
}
public init(reason: String, token: Token? = nil, stackTrace: [Token] = []) {
@@ -50,7 +50,7 @@ extension Error {
}
}
public protocol ErrorReporter: class {
public protocol ErrorReporter: AnyObject {
func renderError(_ error: Error) -> String
}

View File

@@ -2,17 +2,14 @@ public protocol Expression: CustomStringConvertible {
func evaluate(context: Context) throws -> Bool
}
protocol InfixOperator: Expression {
init(lhs: Expression, rhs: Expression)
}
protocol PrefixOperator: Expression {
init(expression: Expression)
}
final class StaticExpression: Expression, CustomStringConvertible {
let value: Bool
@@ -29,7 +26,6 @@ final class StaticExpression: Expression, CustomStringConvertible {
}
}
final class VariableExpression: Expression, CustomStringConvertible {
let variable: Resolvable
@@ -48,7 +44,7 @@ final class VariableExpression: Expression, CustomStringConvertible {
if let result = result as? [Any] {
truthy = !result.isEmpty
} else if let result = result as? [String:Any] {
} else if let result = result as? [String: Any] {
truthy = !result.isEmpty
} else if let result = result as? Bool {
truthy = result
@@ -68,7 +64,6 @@ final class VariableExpression: Expression, CustomStringConvertible {
}
}
final class NotExpression: Expression, PrefixOperator, CustomStringConvertible {
let expression: Expression
@@ -144,7 +139,6 @@ final class OrExpression: Expression, InfixOperator, CustomStringConvertible {
}
}
final class AndExpression: Expression, InfixOperator, CustomStringConvertible {
let lhs: Expression
let rhs: Expression
@@ -168,7 +162,6 @@ final class AndExpression: Expression, InfixOperator, CustomStringConvertible {
}
}
class EqualityExpression: Expression, InfixOperator, CustomStringConvertible {
let lhs: Expression
let rhs: Expression
@@ -204,7 +197,6 @@ class EqualityExpression: Expression, InfixOperator, CustomStringConvertible {
}
}
class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
let lhs: Expression
let rhs: Expression
@@ -215,7 +207,7 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
}
var description: String {
return "(\(lhs) \(op) \(rhs))"
return "(\(lhs) \(symbol) \(rhs))"
}
func evaluate(context: Context) throws -> Bool {
@@ -233,7 +225,7 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
return false
}
var op: String {
var symbol: String {
return ""
}
@@ -242,9 +234,8 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
}
}
class MoreThanExpression: NumericExpression {
override var op: String {
override var symbol: String {
return ">"
}
@@ -253,9 +244,8 @@ class MoreThanExpression: NumericExpression {
}
}
class MoreThanEqualExpression: NumericExpression {
override var op: String {
override var symbol: String {
return ">="
}
@@ -264,9 +254,8 @@ class MoreThanEqualExpression: NumericExpression {
}
}
class LessThanExpression: NumericExpression {
override var op: String {
override var symbol: String {
return "<"
}
@@ -275,9 +264,8 @@ class LessThanExpression: NumericExpression {
}
}
class LessThanEqualExpression: NumericExpression {
override var op: String {
override var symbol: String {
return "<="
}
@@ -286,7 +274,6 @@ class LessThanEqualExpression: NumericExpression {
}
}
class InequalityExpression: EqualityExpression {
override var description: String {
return "(\(lhs) != \(rhs))"
@@ -297,7 +284,7 @@ class InequalityExpression: EqualityExpression {
}
}
// swiftlint:disable:next cyclomatic_complexity
func toNumber(value: Any) -> Number? {
if let value = value as? Float {
return Number(value)

View File

@@ -14,12 +14,13 @@ open class Extension {
/// Registers a simple template tag with a name and a handler
public func registerSimpleTag(_ name: String, handler: @escaping (Context) throws -> String) {
registerTag(name, parser: { parser, token in
return SimpleNode(token: token, handler: handler)
})
registerTag(name) { _, token in
SimpleNode(token: token, handler: handler)
}
}
/// Registers boolean filter with it's negative counterpart
// swiftlint:disable:next discouraged_optional_boolean
public func registerFilter(name: String, negativeFilterName: String, filter: @escaping (Any?) throws -> Bool?) {
filters[name] = .simple(filter)
filters[negativeFilterName] = .simple {
@@ -44,7 +45,6 @@ open class Extension {
}
}
class DefaultExtension: Extension {
override init() {
super.init()
@@ -77,7 +77,6 @@ class DefaultExtension: Extension {
}
}
protocol FilterType {
func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any?
}

View File

@@ -1,4 +1,4 @@
class FilterNode : NodeType {
class FilterNode: NodeType {
let resolvable: Resolvable
let nodes: [NodeType]
let token: Token?
@@ -30,8 +30,7 @@ class FilterNode : NodeType {
let value = try renderNodes(nodes, context)
return try context.push(dictionary: ["filter_value": value]) {
return try VariableNode(variable: resolvable, token: token).render(context)
try VariableNode(variable: resolvable, token: token).render(context)
}
}
}

View File

@@ -72,7 +72,7 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
}
var indentWidth = 4
if arguments.count > 0 {
if !arguments.isEmpty {
guard let value = arguments[0] as? Int else {
throw TemplateSyntaxError("""
'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))
@@ -99,18 +99,17 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
indentFirst = value
}
let indentation = [String](repeating: indentationChar, count: indentWidth).joined(separator: "")
let indentation = [String](repeating: indentationChar, count: indentWidth).joined()
return indent(stringify(value), indentation: indentation, indentFirst: indentFirst)
}
func indent(_ content: String, indentation: String, indentFirst: Bool) -> String {
guard !indentation.isEmpty else { return content }
var lines = content.components(separatedBy: .newlines)
let firstLine = (indentFirst ? indentation : "") + lines.removeFirst()
let result = lines.reduce([firstLine]) { (result, line) in
return result + [(line.isEmpty ? "" : "\(indentation)\(line)")]
let result = lines.reduce([firstLine]) { result, line in
result + [(line.isEmpty ? "" : "\(indentation)\(line)")]
}
return result.joined(separator: "\n")
}

View File

@@ -1,14 +1,14 @@
import Foundation
class ForNode : NodeType {
class ForNode: NodeType {
let resolvable: Resolvable
let loopVariables: [String]
let nodes:[NodeType]
let nodes: [NodeType]
let emptyNodes: [NodeType]
let `where`: Expression?
let token: Token?
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
let components = token.components
func hasToken(_ token: String, at index: Int) -> Bool {
@@ -46,10 +46,24 @@ class ForNode : NodeType {
_ = parser.nextToken()
}
return ForNode(resolvable: resolvable, loopVariables: loopVariables, nodes: forNodes, emptyNodes: emptyNodes, where: `where`, token: token)
return ForNode(
resolvable: resolvable,
loopVariables: loopVariables,
nodes: forNodes,
emptyNodes: emptyNodes,
where: `where`,
token: token
)
}
init(resolvable: Resolvable, loopVariables: [String], nodes: [NodeType], emptyNodes: [NodeType], where: Expression? = nil, token: Token? = nil) {
init(
resolvable: Resolvable,
loopVariables: [String],
nodes: [NodeType],
emptyNodes: [NodeType],
where: Expression? = nil,
token: Token? = nil
) {
self.resolvable = resolvable
self.loopVariables = loopVariables
self.nodes = nodes
@@ -58,10 +72,48 @@ class ForNode : NodeType {
self.token = token
}
func push<Result>(value: Any, context: Context, closure: () throws -> (Result)) throws -> Result {
func render(_ context: Context) throws -> String {
var values = try resolve(context)
if let `where` = self.where {
values = try values.filter { item -> Bool in
try push(value: item, context: context) {
try `where`.evaluate(context: context)
}
}
}
if !values.isEmpty {
let count = values.count
return try zip(0..., values)
.map { index, item in
let forContext: [String: Any] = [
"first": index == 0,
"last": index == (count - 1),
"counter": index + 1,
"counter0": index,
"length": count
]
return try context.push(dictionary: ["forloop": forContext]) {
try push(value: item, context: context) {
try renderNodes(nodes, context)
}
}
}
.joined()
}
return try context.push {
try renderNodes(emptyNodes, context)
}
}
private func push<Result>(value: Any, context: Context, closure: () throws -> (Result)) throws -> Result {
if loopVariables.isEmpty {
return try context.push() {
return try closure()
return try context.push {
try closure()
}
}
@@ -71,27 +123,26 @@ class ForNode : NodeType {
throw TemplateSyntaxError("Tuple '\(value)' has less values than loop variables")
}
var variablesContext = [String: Any]()
valueMirror.children.prefix(loopVariables.count).enumerated().forEach({ (offset, element) in
valueMirror.children.prefix(loopVariables.count).enumerated().forEach { offset, element in
if loopVariables[offset] != "_" {
variablesContext[loopVariables[offset]] = element.value
}
})
}
return try context.push(dictionary: variablesContext) {
return try closure()
try closure()
}
}
return try context.push(dictionary: [loopVariables.first!: value]) {
return try closure()
return try context.push(dictionary: [loopVariables.first ?? "": value]) {
try closure()
}
}
func render(_ context: Context) throws -> String {
private func resolve(_ context: Context) throws -> [Any] {
let resolved = try resolvable.resolve(context)
var values: [Any]
if let dictionary = resolved as? [String: Any], !dictionary.isEmpty {
values = dictionary.sorted { $0.key < $1.key }
} else if let array = resolved as? [Any] {
@@ -120,36 +171,6 @@ class ForNode : NodeType {
values = []
}
if let `where` = self.where {
values = try values.filter({ item -> Bool in
return try push(value: item, context: context) {
try `where`.evaluate(context: context)
}
})
}
if !values.isEmpty {
let count = values.count
return try values.enumerated().map { index, item in
let forContext: [String: Any] = [
"first": index == 0,
"last": index == (count - 1),
"counter": index + 1,
"counter0": index,
"length": count
]
return try context.push(dictionary: ["forloop": forContext]) {
return try push(value: item, context: context) {
try renderNodes(nodes, context)
}
}
}.joined(separator: "")
}
return try context.push {
try renderNodes(emptyNodes, context)
}
return values
}
}

View File

@@ -12,7 +12,6 @@ enum Operator {
}
}
let operators: [Operator] = [
.infix("in", 5, InExpression.self),
.infix("or", 6, OrExpression.self),
@@ -23,21 +22,17 @@ let operators: [Operator] = [
.infix(">", 10, MoreThanExpression.self),
.infix(">=", 10, MoreThanEqualExpression.self),
.infix("<", 10, LessThanExpression.self),
.infix("<=", 10, LessThanEqualExpression.self),
.infix("<=", 10, LessThanEqualExpression.self)
]
func findOperator(name: String) -> Operator? {
for op in operators {
if op.name == name {
return op
}
for `operator` in operators where `operator`.name == name {
return `operator`
}
return nil
}
indirect enum IfToken {
case infix(name: String, bindingPower: Int, operatorType: InfixOperator.Type)
case prefix(name: String, bindingPower: Int, operatorType: PrefixOperator.Type)
@@ -51,9 +46,9 @@ indirect enum IfToken {
return bindingPower
case .prefix(_, let bindingPower, _):
return bindingPower
case .variable(_):
case .variable:
return 0
case .subExpression(_):
case .subExpression:
return 0
case .end:
return 0
@@ -64,9 +59,9 @@ indirect enum IfToken {
switch self {
case .infix(let name, _, _):
throw TemplateSyntaxError("'if' expression error: infix operator '\(name)' doesn't have a left hand side")
case .prefix(_, let bindingPower, let op):
case .prefix(_, let bindingPower, let operatorType):
let expression = try parser.expression(bindingPower: bindingPower)
return op.init(expression: expression)
return operatorType.init(expression: expression)
case .variable(let variable):
return VariableExpression(variable: variable)
case .subExpression(let expression):
@@ -78,14 +73,14 @@ indirect enum IfToken {
func leftDenotation(left: Expression, parser: IfExpressionParser) throws -> Expression {
switch self {
case .infix(_, let bindingPower, let op):
case .infix(_, let bindingPower, let operatorType):
let right = try parser.expression(bindingPower: bindingPower)
return op.init(lhs: left, rhs: right)
return operatorType.init(lhs: left, rhs: right)
case .prefix(let name, _, _):
throw TemplateSyntaxError("'if' expression error: prefix operator '\(name)' was called with a left hand side")
case .variable(let variable):
throw TemplateSyntaxError("'if' expression error: variable '\(variable)' was called with a left hand side")
case .subExpression(_):
case .subExpression:
throw TemplateSyntaxError("'if' expression error: sub expression was called with a left hand side")
case .end:
throw TemplateSyntaxError("'if' expression error: end")
@@ -102,7 +97,6 @@ indirect enum IfToken {
}
}
final class IfExpressionParser {
let tokens: [IfToken]
var position: Int = 0
@@ -118,7 +112,7 @@ final class IfExpressionParser {
private init(components: ArraySlice<String>, environment: Environment, token: Token) throws {
var parsedComponents = Set<Int>()
var bracketsBalance = 0
self.tokens = try zip(components.indices, components).compactMap { (index, component) in
self.tokens = try zip(components.indices, components).compactMap { index, component in
guard !parsedComponents.contains(index) else { return nil }
if component == "(" {
@@ -139,8 +133,8 @@ final class IfExpressionParser {
return nil
} else {
parsedComponents.insert(index)
if let op = findOperator(name: component) {
switch op {
if let `operator` = findOperator(name: component) {
switch `operator` {
case .infix(let name, let bindingPower, let operatorType):
return .infix(name: name, bindingPower: bindingPower, operatorType: operatorType)
case .prefix(let name, let bindingPower, let operatorType):
@@ -152,17 +146,20 @@ final class IfExpressionParser {
}
}
private static func subExpression(from components: ArraySlice<String>, environment: Environment, token: Token) throws -> (Expression, Int) {
private static func subExpression(
from components: ArraySlice<String>,
environment: Environment,
token: Token
) throws -> (Expression, Int) {
var bracketsBalance = 1
let subComponents = components
.prefix(while: {
let subComponents = components.prefix {
if $0 == "(" {
bracketsBalance += 1
} else if $0 == ")" {
bracketsBalance -= 1
}
return bracketsBalance != 0
})
}
if bracketsBalance > 0 {
throw TemplateSyntaxError("'if' expression error: missing closing bracket")
}
@@ -211,7 +208,6 @@ final class IfExpressionParser {
}
}
/// Represents an if condition and the associated nodes when the condition
/// evaluates
final class IfCondition {
@@ -225,13 +221,12 @@ final class IfCondition {
func render(_ context: Context) throws -> String {
return try context.push {
return try renderNodes(nodes, context)
try renderNodes(nodes, context)
}
}
}
class IfNode : NodeType {
class IfNode: NodeType {
let conditions: [IfCondition]
let token: Token?
@@ -291,7 +286,7 @@ class IfNode : NodeType {
return IfNode(conditions: [
IfCondition(expression: expression, nodes: trueNodes),
IfCondition(expression: nil, nodes: falseNodes),
IfCondition(expression: nil, nodes: falseNodes)
], token: token)
}

View File

@@ -1,7 +1,6 @@
import PathKit
class IncludeNode : NodeType {
class IncludeNode: NodeType {
let templateName: Variable
let includeContext: String?
let token: Token?
@@ -34,9 +33,9 @@ class IncludeNode : NodeType {
let template = try context.environment.loadTemplate(name: templateName)
do {
let subContext = includeContext.flatMap { context[$0] as? [String: Any] }
let subContext = includeContext.flatMap { context[$0] as? [String: Any] } ?? [:]
return try context.push(dictionary: subContext) {
return try template.render(context)
try template.render(context)
}
} catch {
if let error = error as? TemplateSyntaxError {
@@ -47,4 +46,3 @@ class IncludeNode : NodeType {
}
}
}

View File

@@ -33,7 +33,6 @@ class BlockContext {
}
}
extension Collection {
func any(_ closure: (Iterator.Element) -> Bool) -> Iterator.Element? {
for element in self {
@@ -46,10 +45,9 @@ extension Collection {
}
}
class ExtendsNode : NodeType {
class ExtendsNode: NodeType {
let templateName: Variable
let blocks: [String:BlockNode]
let blocks: [String: BlockNode]
let token: Token?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
@@ -66,7 +64,7 @@ class ExtendsNode : NodeType {
let blockNodes = parsedNodes.compactMap { $0 as? BlockNode }
let nodes = blockNodes.reduce([String: BlockNode]()) { (accumulator, node) -> [String: BlockNode] in
let nodes = blockNodes.reduce([String: BlockNode]()) { accumulator, node -> [String: BlockNode] in
var dict = accumulator
dict[node.name] = node
return dict
@@ -102,7 +100,7 @@ class ExtendsNode : NodeType {
// pushes base template and renders it's content
// block_context contains all blocks from child templates
return try context.push(dictionary: [BlockContext.contextKey: blockContext]) {
return try baseTemplate.render(context)
try baseTemplate.render(context)
}
} catch {
// if error template is already set (see catch in BlockNode)
@@ -117,8 +115,7 @@ class ExtendsNode : NodeType {
}
}
class BlockNode : NodeType {
class BlockNode: NodeType {
let name: String
let nodes: [NodeType]
let token: Token?
@@ -133,7 +130,7 @@ class BlockNode : NodeType {
let blockName = bits[1]
let nodes = try parser.parse(until(["endblock"]))
_ = parser.nextToken()
return BlockNode(name:blockName, nodes:nodes, token: token)
return BlockNode(name: blockName, nodes: nodes, token: token)
}
init(name: String, nodes: [NodeType], token: Token) {
@@ -148,7 +145,7 @@ class BlockNode : NodeType {
// render extension node
do {
return try context.push(dictionary: childContext) {
return try child.render(context)
try child.render(context)
}
} catch {
throw error.withToken(child.token)
@@ -163,8 +160,11 @@ class BlockNode : NodeType {
var childContext: [String: Any] = [BlockContext.contextKey: blockContext]
if let blockSuperNode = child.nodes.first(where: {
if let token = $0.token, case .variable = token.kind, token.contents == "block.super" { return true }
else { return false}
if let token = $0.token, case .variable = token.kind, token.contents == "block.super" {
return true
} else {
return false
}
}) {
do {
// render base node so that its content can be used as part of child node that extends it

View File

@@ -24,8 +24,8 @@ final class KeyPath {
subscriptLevel = 0
}
for c in variable {
switch c {
for character in variable {
switch character {
case "." where subscriptLevel == 0:
try foundSeparator()
case "[":
@@ -33,7 +33,7 @@ final class KeyPath {
case "]":
try closeBracket()
default:
try addCharacter(c)
try addCharacter(character)
}
}
try finish()
@@ -90,12 +90,12 @@ final class KeyPath {
subscriptLevel -= 1
}
private func addCharacter(_ c: Character) throws {
private func addCharacter(_ character: Character) throws {
guard partialComponents.isEmpty || subscriptLevel > 0 else {
throw TemplateSyntaxError("Unexpected character '\(c)' in variable '\(variable)'")
throw TemplateSyntaxError("Unexpected character '\(character)' in variable '\(variable)'")
}
current.append(c)
current.append(character)
}
private func finish() throws {

View File

@@ -24,8 +24,9 @@ struct Lexer {
self.templateString = templateString
self.lines = templateString.components(separatedBy: .newlines).enumerated().compactMap {
guard !$0.element.isEmpty else { return nil }
return (content: $0.element, number: UInt($0.offset + 1), templateString.range(of: $0.element)!)
guard !$0.element.isEmpty,
let range = templateString.range(of: $0.element) else { return nil }
return (content: $0.element, number: UInt($0.offset + 1), range)
}
}
@@ -43,8 +44,8 @@ struct Lexer {
guard string.count > 4 else { return "" }
let trimmed = String(string.dropFirst(2).dropLast(2))
.components(separatedBy: "\n")
.filter({ !$0.isEmpty })
.map({ $0.trim(character: " ") })
.filter { !$0.isEmpty }
.map { $0.trim(character: " ") }
.joined(separator: " ")
return trimmed
}

View File

@@ -1,13 +1,11 @@
import Foundation
import PathKit
public protocol Loader {
func loadTemplate(name: String, environment: Environment) throws -> Template
func loadTemplate(names: [String], environment: Environment) throws -> Template
}
extension Loader {
public func loadTemplate(names: [String], environment: Environment) throws -> Template {
for name in names {
@@ -24,7 +22,6 @@ extension Loader {
}
}
// A class for loading a template from disk
public class FileSystemLoader: Loader, CustomStringConvertible {
public let paths: [Path]
@@ -35,7 +32,7 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
public init(bundle: [Bundle]) {
self.paths = bundle.map {
return Path($0.bundlePath)
Path($0.bundlePath)
}
}
@@ -74,7 +71,6 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
}
}
public class DictionaryLoader: Loader {
public let templates: [String: String]
@@ -101,7 +97,6 @@ public class DictionaryLoader: Loader {
}
}
extension Path {
func safeJoin(path: Path) throws -> Path {
let newPath = self + path
@@ -114,7 +109,6 @@ extension Path {
}
}
class SuspiciousFileOperation: Error {
let basePath: Path
let path: Path

View File

@@ -2,26 +2,27 @@ import Foundation
public protocol NodeType {
/// Render the node in the given context
func render(_ context:Context) throws -> String
func render(_ context: Context) throws -> String
/// Reference to this node's token
var token: Token? { get }
}
/// Render the collection of nodes in the given context
public func renderNodes(_ nodes:[NodeType], _ context:Context) throws -> String {
return try nodes.map {
public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String {
return try nodes
.map {
do {
return try $0.render(context)
} catch {
throw error.withToken($0.token)
}
}.joined(separator: "")
}
.joined()
}
public class SimpleNode : NodeType {
public let handler:(Context) throws -> String
public class SimpleNode: NodeType {
public let handler: (Context) throws -> String
public let token: Token?
public init(token: Token, handler: @escaping (Context) throws -> String) {
@@ -34,34 +35,31 @@ public class SimpleNode : NodeType {
}
}
public class TextNode : NodeType {
public let text:String
public class TextNode: NodeType {
public let text: String
public let token: Token?
public init(text:String) {
public init(text: String) {
self.text = text
self.token = nil
}
public func render(_ context:Context) throws -> String {
public func render(_ context: Context) throws -> String {
return self.text
}
}
public protocol Resolvable {
func resolve(_ context: Context) throws -> Any?
}
public class VariableNode : NodeType {
public class VariableNode: NodeType {
public let variable: Resolvable
public var token: Token?
let condition: Expression?
let elseExpression: Resolvable?
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
var components = token.components
func hasToken(_ token: String, at index: Int) -> Bool {
@@ -121,7 +119,6 @@ public class VariableNode : NodeType {
}
}
func stringify(_ result: Any?) -> String {
if let result = result as? String {
return result
@@ -144,7 +141,6 @@ func unwrap(_ array: [Any?]) -> [Any] {
} else {
return item
}
}
else { return item as Any }
} else { return item as Any }
}
}

View File

@@ -1,13 +1,12 @@
#if !os(Linux)
import Foundation
class NowNode : NodeType {
let format:Variable
class NowNode: NodeType {
let format: Variable
let token: Token?
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
var format:Variable?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
var format: Variable?
let components = token.components
guard components.count <= 2 else {
@@ -17,10 +16,10 @@ class NowNode : NodeType {
format = Variable(components[1])
}
return NowNode(format:format, token: token)
return NowNode(format: format, token: token)
}
init(format:Variable?, token: Token? = nil) {
init(format: Variable?, token: Token? = nil) {
self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
self.token = token
}
@@ -28,18 +27,18 @@ class NowNode : NodeType {
func render(_ context: Context) throws -> String {
let date = Date()
let format = try self.format.resolve(context)
var formatter:DateFormatter?
var formatter: DateFormatter
if let format = format as? DateFormatter {
formatter = format
} else if let format = format as? String {
formatter = DateFormatter()
formatter!.dateFormat = format
formatter.dateFormat = format
} else {
return ""
}
return formatter!.string(from: date)
return formatter.string(from: date)
}
}
#endif

View File

@@ -1,18 +1,15 @@
public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
return { parser, token in
if let name = token.components.first {
for tag in tags {
if name == tag {
for tag in tags where name == tag {
return true
}
}
}
return false
}
}
/// A class for parsing an array of tokens and converts them into a collection of Node's
public class TokenParser {
public typealias TagParser = (TokenParser, Token) throws -> NodeType
@@ -30,11 +27,11 @@ public class TokenParser {
return try parse(nil)
}
public func parse(_ parse_until:((_ parser:TokenParser, _ token:Token) -> (Bool))?) throws -> [NodeType] {
public func parse(_ parseUntil: ((_ parser: TokenParser, _ token: Token) -> (Bool))?) throws -> [NodeType] {
var nodes = [NodeType]()
while tokens.count > 0 {
let token = nextToken()!
while !tokens.isEmpty {
guard let token = nextToken() else { break }
switch token.kind {
case .text:
@@ -42,7 +39,7 @@ public class TokenParser {
case .variable:
try nodes.append(VariableNode.parse(self, token: token))
case .block:
if let parse_until = parse_until , parse_until(self, token) {
if let parseUntil = parseUntil, parseUntil(self, token) {
prependToken(token)
return nodes
}
@@ -65,14 +62,14 @@ public class TokenParser {
}
public func nextToken() -> Token? {
if tokens.count > 0 {
if !tokens.isEmpty {
return tokens.remove(at: 0)
}
return nil
}
public func prependToken(_ token:Token) {
public func prependToken(_ token: Token) {
tokens.insert(token, at: 0)
}
@@ -94,7 +91,6 @@ public class TokenParser {
}
extension Environment {
func findTag(name: String) throws -> Extension.TagParser {
for ext in extensions {
if let filter = ext.tags[name] {
@@ -118,23 +114,23 @@ extension Environment {
} else {
throw TemplateSyntaxError("""
Unknown filter '\(name)'. \
Found similar filters: \(suggestedFilters.map({ "'\($0)'" }).joined(separator: ", ")).
Found similar filters: \(suggestedFilters.map { "'\($0)'" }.joined(separator: ", ")).
""")
}
}
private func suggestedFilters(for name: String) -> [String] {
let allFilters = extensions.flatMap({ $0.filters.keys })
let allFilters = extensions.flatMap { $0.filters.keys }
let filtersWithDistance = allFilters
.map({ (filterName: $0, distance: $0.levenshteinDistance(name)) })
.map { (filterName: $0, distance: $0.levenshteinDistance(name)) }
// do not suggest filters which names are shorter than the distance
.filter({ $0.filterName.count > $0.distance })
.filter { $0.filterName.count > $0.distance }
guard let minDistance = filtersWithDistance.min(by: { $0.distance < $1.distance })?.distance else {
return []
}
// suggest all filters with the same distance
return filtersWithDistance.filter({ $0.distance == minDistance }).map({ $0.filterName })
return filtersWithDistance.filter { $0.distance == minDistance }.map { $0.filterName }
}
/// Create filter expression from a string
@@ -153,8 +149,14 @@ extension Environment {
// find offset of filter in the containing token so that only filter is highligted, not the whole token
if let filterTokenRange = containingToken.contents.range(of: filterToken) {
var location = containingToken.sourceMap.location
location.lineOffset += containingToken.contents.distance(from: containingToken.contents.startIndex, to: filterTokenRange.lowerBound)
syntaxError.token = .variable(value: filterToken, at: SourceMap(filename: containingToken.sourceMap.filename, location: location))
location.lineOffset += containingToken.contents.distance(
from: containingToken.contents.startIndex,
to: filterTokenRange.lowerBound
)
syntaxError.token = .variable(
value: filterToken,
at: SourceMap(filename: containingToken.sourceMap.filename, location: location)
)
} else {
syntaxError.token = containingToken
}
@@ -183,9 +185,8 @@ extension Environment {
// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
extension String {
subscript(_ i: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: i)]
subscript(_ index: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: index)]
}
func levenshteinDistance(_ target: String) -> Int {
@@ -198,19 +199,19 @@ extension String {
last = [Int](0...target.count)
current = [Int](repeating: 0, count: target.count + 1)
for i in 0..<self.count {
for selfIndex in 0..<self.count {
// calculate v1 (current row distances) from the previous row v0
// first element of v1 is A[i+1][0]
// edit distance is delete (i+1) chars from s to match empty t
current[0] = i + 1
current[0] = selfIndex + 1
// use formula to fill in the rest of the row
for j in 0..<target.count {
current[j+1] = Swift.min(
last[j+1] + 1,
current[j] + 1,
last[j] + (self[i] == target[j] ? 0 : 1)
for targetIndex in 0..<target.count {
current[targetIndex + 1] = Swift.min(
last[targetIndex + 1] + 1,
current[targetIndex] + 1,
last[targetIndex] + (self[selfIndex] == target[targetIndex] ? 0 : 1)
)
}
@@ -220,5 +221,4 @@ extension String {
return current[target.count]
}
}

View File

@@ -8,7 +8,7 @@ let NSFileNoSuchFileError = 4
/// A class representing a template
open class Template: ExpressibleByStringLiteral {
let templateString: String
internal(set) var environment: Environment
var environment: Environment
let tokens: [Token]
/// The name of the loaded Template if the Template was loaded from a Loader
@@ -26,18 +26,18 @@ open class Template: ExpressibleByStringLiteral {
/// Create a template with the given name inside the given bundle
@available(*, deprecated, message: "Use Environment/FileSystemLoader instead")
public convenience init(named:String, inBundle bundle:Bundle? = nil) throws {
public convenience init(named: String, inBundle bundle: Bundle? = nil) throws {
let useBundle = bundle ?? Bundle.main
guard let url = useBundle.url(forResource: named, withExtension: nil) else {
throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, userInfo: nil)
}
try self.init(URL:url)
try self.init(URL: url)
}
/// Create a template with a file found at the given URL
@available(*, deprecated, message: "Use Environment/FileSystemLoader instead")
public convenience init(URL:Foundation.URL) throws {
public convenience init(URL: Foundation.URL) throws {
try self.init(path: Path(URL.path))
}
@@ -72,8 +72,9 @@ open class Template: ExpressibleByStringLiteral {
return try renderNodes(nodes, context)
}
// swiftlint:disable discouraged_optional_collection
/// Render the given template
open func render(_ dictionary: [String: Any]? = nil) throws -> String {
return try render(Context(dictionary: dictionary, environment: environment))
return try render(Context(dictionary: dictionary ?? [:], environment: environment))
}
}

View File

@@ -1,6 +1,5 @@
import Foundation
extension String {
/// Split a string by a separator leaving quoted phrases together
func smartSplit(separator: Character = " ") -> [String] {
@@ -10,37 +9,18 @@ extension String {
var singleQuoteCount = 0
var doubleQuoteCount = 0
let specialCharacters = ",|:"
func appendWord(_ word: String) {
if components.count > 0 {
if let precedingChar = components.last?.last, specialCharacters.contains(precedingChar) {
components[components.count-1] += word
} else if specialCharacters.contains(word) {
components[components.count-1] += word
} else if word != "(" && word.first == "(" || word != ")" && word.first == ")" {
components.append(String(word.prefix(1)))
appendWord(String(word.dropFirst()))
} else if word != "(" && word.last == "(" || word != ")" && word.last == ")" {
appendWord(String(word.dropLast()))
components.append(String(word.suffix(1)))
} else {
components.append(word)
}
} else {
components.append(word)
}
}
for character in self {
if character == "'" { singleQuoteCount += 1 }
else if character == "\"" { doubleQuoteCount += 1 }
if character == "'" {
singleQuoteCount += 1
} else if character == "\"" {
doubleQuoteCount += 1
}
if character == separate {
if separate != separator {
word.append(separate)
} else if (singleQuoteCount % 2 == 0 || doubleQuoteCount % 2 == 0) && !word.isEmpty {
appendWord(word)
appendWord(word, to: &components)
word = ""
}
@@ -54,11 +34,33 @@ extension String {
}
if !word.isEmpty {
appendWord(word)
appendWord(word, to: &components)
}
return components
}
private func appendWord(_ word: String, to components: inout [String]) {
let specialCharacters = ",|:"
if !components.isEmpty {
if let precedingChar = components.last?.last, specialCharacters.contains(precedingChar) {
components[components.count - 1] += word
} else if specialCharacters.contains(word) {
components[components.count - 1] += word
} else if word != "(" && word.first == "(" || word != ")" && word.first == ")" {
components.append(String(word.prefix(1)))
appendWord(String(word.dropFirst()), to: &components)
} else if word != "(" && word.last == "(" || word != ")" && word.last == ")" {
appendWord(String(word.dropLast()), to: &components)
components.append(String(word.suffix(1)))
} else {
components.append(word)
}
} else {
components.append(word)
}
}
}
public struct SourceMap: Equatable {
@@ -72,7 +74,7 @@ public struct SourceMap: Equatable {
static let unknown = SourceMap()
public static func ==(lhs: SourceMap, rhs: SourceMap) -> Bool {
public static func == (lhs: SourceMap, rhs: SourceMap) -> Bool {
return lhs.filename == rhs.filename && lhs.location == rhs.location
}
}
@@ -125,5 +127,4 @@ public class Token: Equatable {
public static func == (lhs: Token, rhs: Token) -> Bool {
return lhs.contents == rhs.contents && lhs.kind == rhs.kind && lhs.sourceMap == rhs.sourceMap
}
}

View File

@@ -1,15 +1,13 @@
import Foundation
typealias Number = Float
class FilterExpression : Resolvable {
class FilterExpression: Resolvable {
let filters: [(FilterType, [Variable])]
let variable: Variable
init(token: String, environment: Environment) throws {
let bits = token.smartSplit(separator: "|").map({ String($0).trim(character: " ") })
let bits = token.smartSplit(separator: "|").map { String($0).trim(character: " ") }
if bits.isEmpty {
throw TemplateSyntaxError("Variable tags must include at least 1 argument")
}
@@ -32,15 +30,15 @@ class FilterExpression : Resolvable {
func resolve(_ context: Context) throws -> Any? {
let result = try variable.resolve(context)
return try filters.reduce(result) { x, y in
let arguments = try y.1.map { try $0.resolve(context) }
return try y.0.invoke(value: x, arguments: arguments, context: context)
return try filters.reduce(result) { value, filter in
let arguments = try filter.1.map { try $0.resolve(context) }
return try filter.0.invoke(value: value, arguments: arguments, context: context)
}
}
}
/// A structure used to represent a template variable, and to resolve it in a given context.
public struct Variable : Equatable, Resolvable {
public struct Variable: Equatable, Resolvable {
public let variable: String
/// Create a variable with a string representing the variable
@@ -48,16 +46,8 @@ public struct Variable : Equatable, Resolvable {
self.variable = variable
}
// Split the lookup string and resolve references if possible
fileprivate func lookup(_ context: Context) throws -> [String] {
let keyPath = KeyPath(variable, in: context)
return try keyPath.parse()
}
/// Resolve the variable in the given context
public func resolve(_ context: Context) throws -> Any? {
var current: Any? = context
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
// String literal
return String(variable[variable.index(after: variable.startIndex) ..< variable.index(before: variable.endIndex)])
@@ -75,37 +65,13 @@ public struct Variable : Equatable, Resolvable {
return bool
}
var current: Any? = context
for bit in try lookup(context) {
current = normalize(current)
current = resolve(bit: bit, context: current)
if let context = current as? Context {
current = context[bit]
} else if let dictionary = current as? [String: Any] {
if bit == "count" {
current = dictionary.count
} else {
current = dictionary[bit]
}
} else if let array = current as? [Any] {
current = resolveCollection(array, bit: bit)
} else if let string = current as? String {
current = resolveCollection(string, bit: bit)
} else if let object = current as? NSObject { // NSKeyValueCoding
#if os(Linux)
return nil
#else
if object.responds(to: Selector(bit)) {
current = object.value(forKey: bit)
}
#endif
} else if let value = current {
current = Mirror(reflecting: value).getValue(for: bit)
if current == nil {
return nil
}
} else {
return nil
}
}
if let resolvable = current as? Resolvable {
@@ -116,9 +82,51 @@ public struct Variable : Equatable, Resolvable {
return normalize(current)
}
}
private func resolveCollection<T: Collection>(_ collection: T, bit: String) -> Any? {
// Split the lookup string and resolve references if possible
private func lookup(_ context: Context) throws -> [String] {
let keyPath = KeyPath(variable, in: context)
return try keyPath.parse()
}
// Try to resolve a partial keypath for the given context
private func resolve(bit: String, context: Any?) -> Any? {
let context = normalize(context)
if let context = context as? Context {
return context[bit]
} else if let dictionary = context as? [String: Any] {
return resolve(bit: bit, dictionary: dictionary)
} else if let array = context as? [Any] {
return resolve(bit: bit, collection: array)
} else if let string = context as? String {
return resolve(bit: bit, collection: string)
} else if let object = context as? NSObject { // NSKeyValueCoding
#if os(Linux)
return nil
#else
if object.responds(to: Selector(bit)) {
return object.value(forKey: bit)
}
#endif
} else if let value = context {
return Mirror(reflecting: value).getValue(for: bit)
}
return nil
}
// Try to resolve a partial keypath for the given dictionary
private func resolve(bit: String, dictionary: [String: Any]) -> Any? {
if bit == "count" {
return dictionary.count
} else {
return dictionary[bit]
}
}
// Try to resolve a partial keypath for the given collection
private func resolve<T: Collection>(bit: String, collection: T) -> Any? {
if let index = Int(bit) {
if index >= 0 && index < collection.count {
return collection[collection.index(collection.startIndex, offsetBy: index)]
@@ -134,6 +142,7 @@ private func resolveCollection<T: Collection>(_ collection: T, bit: String) -> A
} else {
return nil
}
}
}
/// A structure used to represet range of two integer values expressed as `from...to`.
@@ -142,6 +151,7 @@ private func resolveCollection<T: Collection>(_ collection: T, bit: String) -> A
/// If `from` is more than `to` array will contain values of reversed range.
public struct RangeVariable: Resolvable {
public let from: Resolvable
// swiftlint:disable:next identifier_name
public let to: Resolvable
public init?(_ token: String, environment: Environment) throws {
@@ -165,24 +175,23 @@ public struct RangeVariable: Resolvable {
}
public func resolve(_ context: Context) throws -> Any? {
let fromResolved = try from.resolve(context)
let toResolved = try to.resolve(context)
let lowerResolved = try from.resolve(context)
let upperResolved = try to.resolve(context)
guard let from = fromResolved.flatMap(toNumber(value:)).flatMap(Int.init) else {
throw TemplateSyntaxError("'from' value is not an Integer (\(fromResolved ?? "nil"))")
guard let lower = lowerResolved.flatMap(toNumber(value:)).flatMap(Int.init) else {
throw TemplateSyntaxError("'from' value is not an Integer (\(lowerResolved ?? "nil"))")
}
guard let to = toResolved.flatMap(toNumber(value:)).flatMap(Int.init) else {
throw TemplateSyntaxError("'to' value is not an Integer (\(toResolved ?? "nil") )")
guard let upper = upperResolved.flatMap(toNumber(value:)).flatMap(Int.init) else {
throw TemplateSyntaxError("'to' value is not an Integer (\(upperResolved ?? "nil") )")
}
let range = min(from, to)...max(from, to)
return from > to ? Array(range.reversed()) : Array(range)
let range = min(lower, upper)...max(lower, upper)
return lower > upper ? Array(range.reversed()) : Array(range)
}
}
func normalize(_ current: Any?) -> Any? {
if let current = current as? Normalizable {
return current.normalize()
@@ -195,19 +204,19 @@ protocol Normalizable {
func normalize() -> Any?
}
extension Array : Normalizable {
extension Array: Normalizable {
func normalize() -> Any? {
return map { $0 as Any }
}
}
extension NSArray : Normalizable {
extension NSArray: Normalizable {
func normalize() -> Any? {
return map { $0 as Any }
}
}
extension Dictionary : Normalizable {
extension Dictionary: Normalizable {
func normalize() -> Any? {
var dictionary: [String: Any] = [:]
@@ -235,7 +244,7 @@ func parseFilterComponents(token: String) -> (String, [Variable]) {
extension Mirror {
func getValue(for key: String) -> Any? {
let result = descendant(key) ?? Int(key).flatMap({ descendant($0) })
let result = descendant(key) ?? Int(key).flatMap { descendant($0) }
if result == nil {
// go through inheritance chain to reach superclass properties
return superclassMirror?.getValue(for: key)
@@ -267,5 +276,3 @@ extension Optional: AnyOptional {
}
}
}

View File

@@ -10,16 +10,16 @@ import Foundation
#if !swift(>=4.1)
public extension Collection {
func index(_ i: Self.Index, offsetBy n: Int) -> Self.Index {
let indexDistance = Self.IndexDistance(n)
return index(i, offsetBy: indexDistance)
func index(_ index: Self.Index, offsetBy offset: Int) -> Self.Index {
let indexDistance = Self.IndexDistance(offset)
return self.index(index, offsetBy: indexDistance)
}
}
#endif
#if !swift(>=4.1)
public extension TemplateSyntaxError {
public static func ==(lhs: TemplateSyntaxError, rhs: TemplateSyntaxError) -> Bool {
public static func == (lhs: TemplateSyntaxError, rhs: TemplateSyntaxError) -> Bool {
return lhs.reason == rhs.reason &&
lhs.description == rhs.description &&
lhs.token == rhs.token &&
@@ -31,7 +31,7 @@ public extension TemplateSyntaxError {
#if !swift(>=4.1)
public extension Variable {
public static func ==(lhs: Variable, rhs: Variable) -> Bool {
public static func == (lhs: Variable, rhs: Variable) -> Bool {
return lhs.variable == rhs.variable
}
}

View File

@@ -0,0 +1,3 @@
disabled_rules: # rule identifiers to exclude from running
- type_body_length
- file_length

View File

@@ -1,14 +1,11 @@
import XCTest
import Spectre
@testable import Stencil
import XCTest
class ContextTests: XCTestCase {
func testContext() {
describe("Context") {
var context: Context!
final class ContextTests: XCTestCase {
func testContextSubscripting() {
describe("Context Subscripting") {
var context = Context()
$0.before {
context = Context(dictionary: ["name": "Kyle"])
}
@@ -41,6 +38,15 @@ class ContextTests: XCTestCase {
try expect(context["name"] as? String) == "Katie"
}
}
}
}
func testContextRestoration() {
describe("Context Restoration") {
var context = Context()
$0.before {
context = Context(dictionary: ["name": "Kyle"])
}
$0.it("allows you to pop to restore previous state") {
context.push {

View File

@@ -1,224 +1,223 @@
import XCTest
import Spectre
import PathKit
import Spectre
@testable import Stencil
import XCTest
class EnvironmentTests: XCTestCase {
func testEnvironment() {
describe("Environment") {
var environment: Environment!
var template: Template!
final class EnvironmentTests: XCTestCase {
var environment = Environment(loader: ExampleLoader())
var template: Template = ""
override func setUp() {
super.setUp()
let errorExtension = Extension()
errorExtension.registerFilter("throw") { (_: Any?) in
throw TemplateSyntaxError("filter error")
}
errorExtension.registerSimpleTag("simpletag") { _ in
throw TemplateSyntaxError("simpletag error")
}
errorExtension.registerTag("customtag") { _, token in
ErrorNode(token: token)
}
$0.before {
environment = Environment(loader: ExampleLoader())
template = nil
environment.extensions += [errorExtension]
template = ""
}
$0.it("can load a template from a name") {
let template = try environment.loadTemplate(name: "example.html")
func testLoading() {
it("can load a template from a name") {
let template = try self.environment.loadTemplate(name: "example.html")
try expect(template.name) == "example.html"
}
$0.it("can load a template from a names") {
let template = try environment.loadTemplate(names: ["first.html", "example.html"])
it("can load a template from a names") {
let template = try self.environment.loadTemplate(names: ["first.html", "example.html"])
try expect(template.name) == "example.html"
}
}
$0.it("can render a template from a string") {
let result = try environment.renderTemplate(string: "Hello World")
func testRendering() {
it("can render a template from a string") {
let result = try self.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")
it("can render a template from a file") {
let result = try self.environment.renderTemplate(name: "example.html")
try expect(result) == "Hello World!"
}
$0.it("allows you to provide a custom template class") {
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 {
func testSyntaxError() {
it("reports syntax error on invalid for tag syntax") {
self.template = "Hello {% for name in %}{{ name }}, {% endfor %}!"
try self.expectError(
reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.",
token: "for name in"
)
}
it("reports syntax error on missing endfor") {
self.template = "{% for name in names %}{{ name }}"
try self.expectError(reason: "`endfor` was not found.", token: "for name in names")
}
it("reports syntax error on unknown tag") {
self.template = "{% for name in names %}{{ name }}{% end %}"
try self.expectError(reason: "Unknown template tag 'end'", token: "end")
}
}
func testUnknownFilter() {
it("reports syntax error in for tag") {
self.template = "{% for name in names|unknown %}{{ name }}{% endfor %}"
try self.expectError(
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "names|unknown"
)
}
it("reports syntax error in for-where tag") {
self.template = "{% for name in names where name|unknown %}{{ name }}{% endfor %}"
try self.expectError(
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "name|unknown"
)
}
it("reports syntax error in if tag") {
self.template = "{% if name|unknown %}{{ name }}{% endif %}"
try self.expectError(
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "name|unknown"
)
}
it("reports syntax error in elif tag") {
self.template = "{% if name %}{{ name }}{% elif name|unknown %}{% endif %}"
try self.expectError(
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "name|unknown"
)
}
it("reports syntax error in ifnot tag") {
self.template = "{% ifnot name|unknown %}{{ name }}{% endif %}"
try self.expectError(
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "name|unknown"
)
}
it("reports syntax error in filter tag") {
self.template = "{% filter unknown %}Text{% endfilter %}"
try self.expectError(
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "filter unknown"
)
}
it("reports syntax error in variable tag") {
self.template = "{{ name|unknown }}"
try self.expectError(
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "name|unknown"
)
}
}
func testRenderingError() {
it("reports rendering error in variable filter") {
self.template = Template(templateString: "{{ name|throw }}", environment: self.environment)
try self.expectError(reason: "filter error", token: "name|throw")
}
it("reports rendering error in filter tag") {
self.template = Template(templateString: "{% filter throw %}Test{% endfilter %}", environment: self.environment)
try self.expectError(reason: "filter error", token: "filter throw")
}
it("reports rendering error in simple tag") {
self.template = Template(templateString: "{% simpletag %}", environment: self.environment)
try self.expectError(reason: "simpletag error", token: "simpletag")
}
it("reports passing argument to simple filter") {
self.template = "{{ name|uppercase:5 }}"
try self.expectError(reason: "cannot invoke filter with an argument", token: "name|uppercase:5")
}
it("reports rendering error in custom tag") {
self.template = Template(templateString: "{% customtag %}", environment: self.environment)
try self.expectError(reason: "Custom Error", token: "customtag")
}
it("reports rendering error in for body") {
self.template = Template(templateString: """
{% for name in names %}{% customtag %}{% endfor %}
""", environment: self.environment)
try self.expectError(reason: "Custom Error", token: "customtag")
}
it("reports rendering error in block") {
self.template = Template(
templateString: "{% block some %}{% customtag %}{% endblock %}",
environment: self.environment
)
try self.expectError(reason: "Custom Error", token: "customtag")
}
}
private func expectError(
reason: String,
token: String,
file: String = #file,
line: Int = #line,
function: String = #function
) throws {
let expectedError = expectedSyntaxError(token: token, template: template, description: reason)
let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let error = try expect(
self.environment.render(template: self.template, context: ["names": ["Bob", "Alice"], "name": "Bob"]),
file: file,
line: line,
function: function
).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
try expect(
reporter.renderError(error),
file: file,
line: line,
function: function
) == reporter.renderError(expectedError)
}
}
$0.context("given syntax error") {
final class EnvironmentIncludeTemplateTests: XCTestCase {
var environment = Environment(loader: ExampleLoader())
var template: Template = ""
var includedTemplate: Template = ""
$0.it("reports syntax error on invalid for tag syntax") {
template = "Hello {% for name in %}{{ name }}, {% endfor %}!"
try expectError(reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.", token: "for name in")
}
override func setUp() {
super.setUp()
$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
template = ""
includedTemplate = ""
}
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") {
func testSyntaxError() throws {
template = Template(templateString: """
{% include "invalid-include.html" %}
""", environment: environment)
@@ -231,11 +230,11 @@ class EnvironmentTests: XCTestCase {
includedToken: "target|unknown")
}
$0.it("reports runtime error in included template") {
func testRuntimeError() throws {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
filterExtension.registerFilter("unknown") { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
}
environment.extensions += [filterExtension]
template = Template(templateString: """
@@ -248,34 +247,53 @@ class EnvironmentTests: XCTestCase {
includedToken: "target|unknown")
}
}
private func expectError(
reason: String,
token: String,
includedToken: String,
file: String = #file,
line: Int = #line,
function: String = #function
) throws {
var expectedError = expectedSyntaxError(token: token, template: template, description: reason)
expectedError.stackTrace = [expectedSyntaxError(
token: includedToken,
template: includedTemplate,
description: reason
).token].compactMap { $0 }
let error = try expect(
self.environment.render(template: self.template, context: ["target": "World"]),
file: file,
line: line,
function: function
).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(
reporter.renderError(error),
file: file,
line: line,
function: function
) == reporter.renderError(expectedError)
}
}
final class EnvironmentBaseAndChildTemplateTests: XCTestCase {
var environment = Environment(loader: ExampleLoader())
var childTemplate: Template = ""
var baseTemplate: Template = ""
override func setUp() {
super.setUp()
$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
childTemplate = ""
baseTemplate = ""
}
func expectError(reason: String, childToken: String, baseToken: String?,
file: String = #file, line: Int = #line, function: String = #function) throws {
var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason)
if let baseToken = baseToken {
expectedError.stackTrace = [expectedSyntaxError(token: baseToken, template: baseTemplate, description: reason).token!]
}
let error = try expect(environment.render(template: childTemplate, context: ["target": "World"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.it("reports syntax error in base template") {
func testSyntaxErrorInBaseTemplate() throws {
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
@@ -284,11 +302,11 @@ class EnvironmentTests: XCTestCase {
baseToken: "target|unknown")
}
$0.it("reports runtime error in base template") {
func testRuntimeErrorInBaseTemplate() throws {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
filterExtension.registerFilter("unknown") { (_: Any?) in
throw TemplateSyntaxError("filter error")
})
}
environment.extensions += [filterExtension]
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
@@ -299,44 +317,78 @@ class EnvironmentTests: XCTestCase {
baseToken: "target|unknown")
}
$0.it("reports syntax error in child template") {
childTemplate = Template(templateString: """
func testSyntaxErrorInChildTemplate() throws {
childTemplate = Template(
templateString: """
{% extends "base.html" %}
{% block body %}Child {{ target|unknown }}{% endblock %}
""", environment: environment, name: nil)
""",
environment: environment,
name: nil
)
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
childToken: "target|unknown",
baseToken: nil)
}
$0.it("reports runtime error in child template") {
func testRuntimeErrorInChildTemplate() throws {
let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in
filterExtension.registerFilter("unknown") { (_: 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)
""",
environment: environment,
name: nil
)
try expectError(reason: "filter error",
childToken: "target|unknown",
baseToken: nil)
}
private func expectError(
reason: String,
childToken: String,
baseToken: String?,
file: String = #file,
line: Int = #line,
function: String = #function
) throws {
var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason)
if let baseToken = baseToken {
expectedError.stackTrace = [expectedSyntaxError(
token: baseToken,
template: baseTemplate,
description: reason
).token].compactMap { $0 }
}
}
let error = try expect(
self.environment.render(template: self.childTemplate, context: ["target": "World"]),
file: file,
line: line,
function: function
).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(
reporter.renderError(error),
file: file,
line: line,
function: function
) == reporter.renderError(expectedError)
}
}
extension Expectation {
@discardableResult
func toThrow<T: Error>() throws -> T {
var thrownError: Error? = nil
var thrownError: Error?
do {
_ = try expression()
@@ -356,7 +408,20 @@ extension Expectation {
}
}
fileprivate class ExampleLoader: Loader {
extension XCTestCase {
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
guard let range = template.templateString.range(of: token) else {
fatalError("Can't find '\(token)' in '\(template)'")
}
let lexer = Lexer(templateString: template.templateString)
let location = lexer.rangeLocation(range)
let sourceMap = SourceMap(filename: template.name, location: location)
let token = Token.block(value: token, at: sourceMap)
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
}
}
private class ExampleLoader: Loader {
func loadTemplate(name: String, environment: Environment) throws -> Template {
if name == "example.html" {
return Template(templateString: "Hello World!", environment: environment, name: name)
@@ -366,8 +431,8 @@ fileprivate class ExampleLoader: Loader {
}
}
class CustomTemplate: Template {
private class CustomTemplate: Template {
// swiftlint:disable discouraged_optional_collection
override func render(_ dictionary: [String: Any]? = nil) throws -> String {
return "here"
}

View File

@@ -1,345 +1,355 @@
import XCTest
import Spectre
@testable import Stencil
import XCTest
class ExpressionsTests: XCTestCase {
func testExpressions() {
describe("Expression") {
final class ExpressionsTests: XCTestCase {
let parser = TokenParser(tokens: [], environment: Environment())
func parseExpression(components: [String]) throws -> Expression {
let parser = try IfExpressionParser.parser(components: components, environment: Environment(), token: .text(value: "", at: .unknown))
private func makeExpression(_ components: [String]) -> Expression {
do {
let parser = try IfExpressionParser.parser(
components: components,
environment: Environment(),
token: .text(value: "", at: .unknown)
)
return try parser.parse()
} catch {
fatalError(error.localizedDescription)
}
}
$0.describe("VariableExpression") {
func testTrueExpressions() {
let expression = VariableExpression(variable: Variable("value"))
$0.it("evaluates to true when value is not nil") {
it("evaluates to true when value is not nil") {
let context = Context(dictionary: ["value": "known"])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when value is unset") {
let context = Context()
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to true when array variable is not empty") {
let items: [[String: Any]] = [["key":"key1","value":42],["key":"key2","value":1337]]
it("evaluates to true when array variable is not empty") {
let items: [[String: Any]] = [["key": "key1", "value": 42], ["key": "key2", "value": 1_337]]
let context = Context(dictionary: ["value": [items]])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when array value is empty") {
it("evaluates to false when dictionary value is empty") {
let emptyItems = [String: Any]()
let context = Context(dictionary: ["value": emptyItems])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
it("evaluates to true when integer value is above 0") {
let context = Context(dictionary: ["value": 1])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
it("evaluates to true with string") {
let context = Context(dictionary: ["value": "test"])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
it("evaluates to true when float value is above 0") {
let context = Context(dictionary: ["value": Float(0.5)])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
it("evaluates to true when double value is above 0") {
let context = Context(dictionary: ["value": Double(0.5)])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
}
func testFalseExpressions() {
let expression = VariableExpression(variable: Variable("value"))
it("evaluates to false when value is unset") {
let context = Context()
try expect(try expression.evaluate(context: context)).to.beFalse()
}
it("evaluates to false when array value is empty") {
let emptyItems = [[String: Any]]()
let context = Context(dictionary: ["value": emptyItems])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when dictionary value is empty") {
let emptyItems = [String:Any]()
it("evaluates to false when dictionary value is empty") {
let emptyItems = [String: Any]()
let context = Context(dictionary: ["value": emptyItems])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to false when Array<Any> value is empty") {
it("evaluates to false when Array<Any> value is empty") {
let context = Context(dictionary: ["value": ([] as [Any])])
try expect(try expression.evaluate(context: context)).to.beFalse()
}
$0.it("evaluates to true when integer value is above 0") {
let context = Context(dictionary: ["value": 1])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to true with string") {
let context = Context(dictionary: ["value": "test"])
try expect(try expression.evaluate(context: context)).to.beTrue()
}
$0.it("evaluates to false when empty string") {
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") {
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])
let negativeContext = Context(dictionary: ["value": -1])
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") {
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") {
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") {
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") {
func testNotExpression() {
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") {
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"])
func testExpressionParsing() {
it("can parse a variable expression") {
let expression = self.makeExpression(["value"])
try expect(expression.evaluate(context: Context())).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
}
$0.it("can parse a not expression") {
let expression = try parseExpression(components: ["not", "value"])
it("can parse a not expression") {
let expression = self.makeExpression(["not", "value"])
try expect(expression.evaluate(context: Context())).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
}
}
$0.describe("and expression") {
let expression = try! parseExpression(components: ["lhs", "and", "rhs"])
func testAndExpression() {
let expression = makeExpression(["lhs", "and", "rhs"])
$0.it("evaluates to false with lhs false") {
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") {
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") {
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") {
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"])
func testOrExpression() {
let expression = makeExpression(["lhs", "or", "rhs"])
$0.it("evaluates to true with lhs true") {
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") {
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") {
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") {
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"])
func testEqualityExpression() {
let expression = makeExpression(["lhs", "==", "rhs"])
$0.it("evaluates to true with equal lhs/rhs") {
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") {
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") {
it("evaluates to true with nils") {
try expect(expression.evaluate(context: Context(dictionary: [:]))).to.beTrue()
}
$0.it("evaluates to true with numbers") {
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") {
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") {
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") {
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") {
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"])
func testInequalityExpression() {
let expression = makeExpression(["lhs", "!=", "rhs"])
$0.it("evaluates to true with inequal lhs/rhs") {
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") {
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"])
func testMoreThanExpression() {
let expression = makeExpression(["lhs", ">", "rhs"])
$0.it("evaluates to true with lhs > rhs") {
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") {
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"])
func testMoreThanEqualExpression() {
let expression = makeExpression(["lhs", ">=", "rhs"])
$0.it("evaluates to true with lhs == rhs") {
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") {
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"])
func testLessThanExpression() {
let expression = makeExpression(["lhs", "<", "rhs"])
$0.it("evaluates to true with lhs < rhs") {
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") {
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"])
func testLessThanEqualExpression() {
let expression = makeExpression(["lhs", "<=", "rhs"])
$0.it("evaluates to true with lhs == rhs") {
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") {
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"])
func testMultipleExpressions() {
let expression = makeExpression(["one", "or", "two", "and", "not", "three"])
$0.it("evaluates to true with one") {
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") {
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") {
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") {
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") {
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") {
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"])
func testTrueInExpression() throws {
let expression = makeExpression(["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()
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()
}
}
func testFalseInExpression() throws {
let expression = makeExpression(["lhs", "in", "rhs"])
$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"))
}
}
}
}
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()
}
}

View File

@@ -1,13 +1,12 @@
import XCTest
import Spectre
@testable import Stencil
import XCTest
class FilterTests: XCTestCase {
func testFilter() {
describe("template filters") {
final class FilterTests: XCTestCase {
func testRegistration() {
let context: [String: Any] = ["name": "Kyle"]
$0.it("allows you to register a custom filter") {
it("allows you to register a custom filter") {
let template = Template(templateString: "{{ name|repeat }}")
let repeatExtension = Extension()
@@ -19,11 +18,14 @@ class FilterTests: XCTestCase {
return nil
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
let result = try template.render(Context(
dictionary: context,
environment: Environment(extensions: [repeatExtension])
))
try expect(result) == "Kyle Kyle"
}
$0.it("allows you to register boolean filters") {
it("allows you to register boolean filters") {
let repeatExtension = Extension()
repeatExtension.registerFilter(name: "isPositive", negativeFilterName: "isNotPositive") { (value: Any?) in
if let value = value as? Int {
@@ -41,106 +43,117 @@ class FilterTests: XCTestCase {
try expect(negativeResult) == "true"
}
$0.it("allows you to register a custom filter which accepts single argument") {
it("allows you to register a custom which throws") {
let template = Template(templateString: "{{ name|repeat }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { (_: Any?) in
throw TemplateSyntaxError("No Repeat")
}
let context = Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))
try expect(try template.render(context))
.toThrow(TemplateSyntaxError(reason: "No Repeat", token: template.tokens.first))
}
it("throws when you pass arguments to simple filter") {
let template = Template(templateString: "{{ name|uppercase:5 }}")
try expect(try template.render(Context(dictionary: ["name": "kyle"]))).toThrow()
}
}
func testRegistrationOverrideDefault() throws {
let template = Template(templateString: "{{ name|join }}")
let context: [String: Any] = ["name": "Kyle"]
let repeatExtension = Extension()
repeatExtension.registerFilter("join") { (_: Any?) in
"joined"
}
let result = try template.render(Context(
dictionary: context,
environment: Environment(extensions: [repeatExtension])
))
try expect(result) == "joined"
}
func testRegistrationWithArguments() {
let context: [String: Any] = ["name": "Kyle"]
it("allows you to register a custom filter which accepts single argument") {
let template = Template(templateString: """
{{ name|repeat:'value1, "value2"' }}
""")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { value, arguments in
if !arguments.isEmpty {
return "\(value!) \(value!) with args \(arguments.first!!)"
guard let value = value,
let argument = arguments.first else { return nil }
return "\(value) \(value) with args \(argument ?? "")"
}
return nil
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
let result = try template.render(Context(
dictionary: context,
environment: Environment(extensions: [repeatExtension])
))
try expect(result) == """
Kyle Kyle with args value1, "value2"
"""
}
$0.it("allows you to register a custom filter which accepts several arguments") {
it("allows you to register a custom filter which accepts several arguments") {
let template = Template(templateString: """
{{ name|repeat:'value"1"',"value'2'",'(key, value)' }}
""")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { value, arguments in
if !arguments.isEmpty {
return "\(value!) \(value!) with args 0: \(arguments[0]!), 1: \(arguments[1]!), 2: \(arguments[2]!)"
guard let value = value else { return nil }
let args = arguments.compactMap { $0 }
return "\(value) \(value) with args 0: \(args[0]), 1: \(args[1]), 2: \(args[2])"
}
return nil
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
let result = try template.render(Context(
dictionary: context,
environment: Environment(extensions: [repeatExtension])
))
try expect(result) == """
Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value)
"""
}
$0.it("allows you to register a custom which throws") {
let template = Template(templateString: "{{ name|repeat }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { (value: Any?) in
throw TemplateSyntaxError("No Repeat")
}
let context = Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))
try expect(try template.render(context)).toThrow(TemplateSyntaxError(reason: "No Repeat", token: template.tokens.first))
}
$0.it("allows you to override a default filter") {
let template = Template(templateString: "{{ name|join }}")
let repeatExtension = Extension()
repeatExtension.registerFilter("join") { (value: Any?) in
return "joined"
}
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == "joined"
}
$0.it("allows whitespace in expression") {
it("allows whitespace in expression") {
let template = Template(templateString: """
{{ value | join : ", " }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "One, Two"
}
$0.it("throws when you pass arguments to simple filter") {
let template = Template(templateString: "{{ name|uppercase:5 }}")
try expect(try template.render(Context(dictionary: ["name": "kyle"]))).toThrow()
}
}
describe("string filters") {
$0.context("given string") {
$0.it("transforms a string to be capitalized") {
func testStringFilters() {
it("transforms a string to be capitalized") {
let template = Template(templateString: "{{ name|capitalize }}")
let result = try template.render(Context(dictionary: ["name": "kyle"]))
try expect(result) == "Kyle"
}
$0.it("transforms a string to be uppercase") {
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") {
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") {
func testStringFiltersWithArrays() {
it("transforms a string to be capitalized") {
let template = Template(templateString: "{{ names|capitalize }}")
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
try expect(result) == """
@@ -148,7 +161,7 @@ class FilterTests: XCTestCase {
"""
}
$0.it("transforms a string to be uppercase") {
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) == """
@@ -156,7 +169,7 @@ class FilterTests: XCTestCase {
"""
}
$0.it("transforms a string to be lowercase") {
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) == """
@@ -164,24 +177,23 @@ class FilterTests: XCTestCase {
"""
}
}
}
describe("default filter") {
func testDefaultFilter() {
let template = Template(templateString: """
Hello {{ name|default:"World" }}
""")
$0.it("shows the variable value") {
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") {
it("shows the default value") {
let result = try template.render(Context(dictionary: [:]))
try expect(result) == "Hello World"
}
$0.it("supports multiple defaults") {
it("supports multiple defaults") {
let template = Template(templateString: """
Hello {{ name|default:a,b,c,"World" }}
""")
@@ -189,19 +201,19 @@ class FilterTests: XCTestCase {
try expect(result) == "Hello World"
}
$0.it("can use int as default") {
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") {
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") {
it("checks for underlying nil value correctly") {
let template = Template(templateString: """
Hello {{ user.name|default:"anonymous" }}
""")
@@ -212,22 +224,22 @@ class FilterTests: XCTestCase {
}
}
describe("join filter") {
func testJoinFilter() {
let template = Template(templateString: """
{{ value|join:", " }}
""")
$0.it("joins a collection of strings") {
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") {
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") {
it("can join by non string") {
let template = Template(templateString: """
{{ value|join:separator }}
""")
@@ -235,7 +247,7 @@ class FilterTests: XCTestCase {
try expect(result) == "OnetrueTwo"
}
$0.it("can join without arguments") {
it("can join without arguments") {
let template = Template(templateString: """
{{ value|join }}
""")
@@ -244,19 +256,19 @@ class FilterTests: XCTestCase {
}
}
describe("split filter") {
func testSplitFilter() {
let template = Template(templateString: """
{{ value|split:", " }}
""")
$0.it("split a string into array") {
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") {
it("can split without arguments") {
let template = Template(templateString: """
{{ value|split }}
""")
@@ -267,62 +279,49 @@ class FilterTests: XCTestCase {
}
}
describe("filter suggestion") {
var template: Template!
var filterExtension: Extension!
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
guard let range = template.templateString.range(of: token) else {
fatalError("Can't find '\(token)' in '\(template)'")
}
let lexer = Lexer(templateString: template.templateString)
let location = lexer.rangeLocation(range)
let sourceMap = SourceMap(filename: template.name, location: location)
let token = Token.block(value: token, at: sourceMap)
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
}
func expectError(reason: String, token: String,
file: String = #file, line: Int = #line, function: String = #function) throws {
let expectedError = expectedSyntaxError(token: token, template: template, description: reason)
let environment = Environment(extensions: [filterExtension])
let error = try expect(environment.render(template: template, context: [:]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
}
$0.it("made for unknown filter") {
template = Template(templateString: "{{ value|unknownFilter }}")
filterExtension = Extension()
func testFilterSuggestion() {
it("made for unknown filter") {
let template = Template(templateString: "{{ value|unknownFilter }}")
let filterExtension = Extension()
filterExtension.registerFilter("knownFilter") { value, _ in value }
try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'.", token: "value|unknownFilter")
try self.expectError(
reason: "Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'.",
token: "value|unknownFilter",
template: template,
extension: filterExtension
)
}
$0.it("made for multiple similar filters") {
template = Template(templateString: "{{ value|lowerFirst }}")
filterExtension = Extension()
it("made for multiple similar filters") {
let template = Template(templateString: "{{ value|lowerFirst }}")
let filterExtension = Extension()
filterExtension.registerFilter("lowerFirstWord") { value, _ in value }
filterExtension.registerFilter("lowerFirstLetter") { value, _ in value }
try expectError(reason: "Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'.", token: "value|lowerFirst")
try self.expectError(
reason: "Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'.",
token: "value|lowerFirst",
template: template,
extension: filterExtension
)
}
$0.it("not made when can't find similar filter") {
template = Template(templateString: "{{ value|unknownFilter }}")
try expectError(reason: "Unknown filter 'unknownFilter'. Found similar filters: 'lowerFirstWord'.", token: "value|unknownFilter")
it("not made when can't find similar filter") {
let template = Template(templateString: "{{ value|unknownFilter }}")
let filterExtension = Extension()
filterExtension.registerFilter("lowerFirstWord") { value, _ in value }
try self.expectError(
reason: "Unknown filter 'unknownFilter'. Found similar filters: 'lowerFirstWord'.",
token: "value|unknownFilter",
template: template,
extension: filterExtension
)
}
}
}
describe("indent filter") {
$0.it("indents content") {
func testIndentContent() throws {
let template = Template(templateString: """
{{ value|indent:2 }}
""")
@@ -336,7 +335,7 @@ class FilterTests: XCTestCase {
"""
}
$0.it("can indent with arbitrary character") {
func testIndentWithArbitraryCharacter() throws {
let template = Template(templateString: """
{{ value|indent:2,"\t" }}
""")
@@ -350,7 +349,7 @@ class FilterTests: XCTestCase {
"""
}
$0.it("can indent first line") {
func testIndentFirstLine() throws {
let template = Template(templateString: """
{{ value|indent:2," ",true }}
""")
@@ -364,7 +363,7 @@ class FilterTests: XCTestCase {
"""
}
$0.it("does not indent empty lines") {
func testIndentNotEmptyLines() throws {
let template = Template(templateString: """
{{ value|indent }}
""")
@@ -385,29 +384,62 @@ class FilterTests: XCTestCase {
"""
}
}
describe("dynamic filter") {
$0.it("can apply dynamic filter") {
func testDynamicFilters() throws {
it("can apply dynamic filter") {
let template = Template(templateString: "{{ name|filter:somefilter }}")
let result = try template.render(Context(dictionary: ["name": "Jhon", "somefilter": "uppercase"]))
try expect(result) == "JHON"
}
$0.it("can apply dynamic filter on array") {
it("can apply dynamic filter on array") {
let template = Template(templateString: "{{ values|filter:joinfilter }}")
let result = try template.render(Context(dictionary: ["values": [1, 2, 3], "joinfilter": "join:\", \""]))
try expect(result) == "1, 2, 3"
}
$0.it("throws on unknown dynamic filter") {
it("throws on unknown dynamic filter") {
let template = Template(templateString: "{{ values|filter:unknown }}")
let context = Context(dictionary: ["values": [1, 2, 3], "unknown": "absurd"])
try expect(try template.render(context)).toThrow()
}
}
private func expectError(
reason: String,
token: String,
template: Template,
extension: Extension,
file: String = #file,
line: Int = #line,
function: String = #function
) throws {
guard let range = template.templateString.range(of: token) else {
fatalError("Can't find '\(token)' in '\(template)'")
}
let environment = Environment(extensions: [`extension`])
let expectedError: Error = {
let lexer = Lexer(templateString: template.templateString)
let location = lexer.rangeLocation(range)
let sourceMap = SourceMap(filename: template.name, location: location)
let token = Token.block(value: token, at: sourceMap)
return TemplateSyntaxError(reason: reason, token: token, stackTrace: [])
}()
let error = try expect(
environment.render(template: template, context: [:]),
file: file,
line: line,
function: function
).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter()
try expect(
reporter.renderError(error),
file: file,
line: line,
function: function
) == reporter.renderError(expectedError)
}
}

View File

@@ -1,32 +1,33 @@
import XCTest
import Spectre
import Stencil
import XCTest
class FilterTagTests: XCTestCase {
final class FilterTagTests: XCTestCase {
func testFilterTag() {
describe("Filter Tag") {
$0.it("allows you to use a filter") {
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") {
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") {
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") {
it("can render filters with arguments") {
let ext = Extension()
ext.registerFilter("split", filter: {
return ($0 as! String).components(separatedBy: $1[0] as! String)
})
ext.registerFilter("split") {
guard let value = $0 as? String,
let argument = $1.first as? String else { return $0 }
return value.components(separatedBy: argument)
}
let env = Environment(extensions: [ext])
let result = try env.renderTemplate(string: """
{% filter split:","|join:";" %}{{ items|join:"," }}{% endfilter %}
@@ -34,12 +35,15 @@ class FilterTagTests: XCTestCase {
try expect(result) == "1;2"
}
$0.it("can render filters with quote as an argument") {
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)
})
ext.registerFilter("replace") {
guard let value = $0 as? String,
$1.count == 2,
let search = $1.first as? String,
let replacement = $1.last as? String else { return $0 }
return value.replacingOccurrences(of: search, with: replacement)
}
let env = Environment(extensions: [ext])
let result = try env.renderTemplate(string: """
{% filter replace:'"',"" %}{{ items|join:"," }}{% endfilter %}
@@ -47,5 +51,4 @@ class FilterTagTests: XCTestCase {
try expect(result) == "1,2"
}
}
}
}

View File

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

View File

@@ -1,12 +1,14 @@
import XCTest
import Spectre
@testable import Stencil
import XCTest
class IfNodeTests: XCTestCase {
func testIfNode() {
describe("IfNode") {
$0.describe("parsing") {
$0.it("can parse an if block") {
private struct SomeType {
let value: String? = nil
}
final class IfNodeTests: XCTestCase {
func testParseIf() {
it("can parse an if block") {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
@@ -24,7 +26,22 @@ class IfNodeTests: XCTestCase {
try expect(trueNode?.text) == "true"
}
$0.it("can parse an if with else block") {
it("can parse an if with complex expression") {
let tokens: [Token] = [
.block(value: """
if value == \"test\" and (not name or not (name and surname) or( some )and other )
""", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
try expect(nodes.first is IfNode).beTrue()
}
}
func testParseIfWithElse() throws {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
@@ -49,7 +66,7 @@ class IfNodeTests: XCTestCase {
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with elif block") {
func testParseIfWithElif() throws {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
@@ -80,7 +97,7 @@ class IfNodeTests: XCTestCase {
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with elif block without else") {
func testParseIfWithElifWithoutElse() throws {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
@@ -105,7 +122,7 @@ class IfNodeTests: XCTestCase {
try expect(elifNode?.text) == "some"
}
$0.it("can parse an if with multiple elif block") {
func testParseMultipleElif() throws {
let tokens: [Token] = [
.block(value: "if value", at: .unknown),
.text(value: "true", at: .unknown),
@@ -142,20 +159,7 @@ class IfNodeTests: XCTestCase {
try expect(falseNode?.text) == "false"
}
$0.it("can parse an if with complex expression") {
let tokens: [Token] = [
.block(value: "if value == \"test\" and (not name or not (name and surname) or( some )and other )", at: .unknown),
.text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown)
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
try expect(nodes.first is IfNode).beTrue()
}
$0.it("can parse an ifnot block") {
func testParseIfnot() throws {
let tokens: [Token] = [
.block(value: "ifnot value", at: .unknown),
.text(value: "false", at: .unknown),
@@ -179,7 +183,8 @@ class IfNodeTests: XCTestCase {
try expect(falseNode?.text) == "false"
}
$0.it("throws an error when parsing an if block without an endif") {
func testParsingErrors() {
it("throws an error when parsing an if block without an endif") {
let tokens: [Token] = [.block(value: "if value", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
@@ -187,7 +192,7 @@ class IfNodeTests: XCTestCase {
try expect(try parser.parse()).toThrow(error)
}
$0.it("throws an error when parsing an ifnot without an endif") {
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())
@@ -196,48 +201,48 @@ class IfNodeTests: XCTestCase {
}
}
$0.describe("rendering") {
$0.it("renders a true expression") {
func testRendering() {
it("renders a true expression") {
let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")])
])
try expect(try node.render(Context())) == "1"
}
$0.it("renders the first true expression") {
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")]),
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") {
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")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")])
])
try expect(try node.render(Context())) == "3"
}
$0.it("renders empty when no truthy conditions") {
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")]),
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")])
])
try expect(try node.render(Context())) == ""
}
}
$0.it("supports variable filters in the if expression") {
func testSupportVariableFilters() throws {
let tokens: [Token] = [
.block(value: "if value|uppercase == \"TEST\"", at: .unknown),
.text(value: "true", at: .unknown),
@@ -251,7 +256,7 @@ class IfNodeTests: XCTestCase {
try expect(result) == "true"
}
$0.it("evaluates nil properties as false") {
func testEvaluatesNilAsFalse() throws {
let tokens: [Token] = [
.block(value: "if instance.value", at: .unknown),
.text(value: "true", at: .unknown),
@@ -261,14 +266,11 @@ class IfNodeTests: XCTestCase {
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") {
func testSupportsRangeVariables() throws {
let tokens: [Token] = [
.block(value: "if value in 1...3", at: .unknown),
.text(value: "true", at: .unknown),
@@ -283,7 +285,4 @@ class IfNodeTests: XCTestCase {
try expect(renderNodes(nodes, Context(dictionary: ["value": 3]))) == "true"
try expect(renderNodes(nodes, Context(dictionary: ["value": 4]))) == "false"
}
}
}
}

View File

@@ -1,25 +1,27 @@
import XCTest
import PathKit
import Spectre
@testable import Stencil
import PathKit
import XCTest
class IncludeTests: XCTestCase {
func testInclude() {
describe("Include") {
final class IncludeTests: XCTestCase {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
let environment = Environment(loader: loader)
lazy var loader = FileSystemLoader(paths: [path])
lazy var environment = Environment(loader: loader)
$0.describe("parsing") {
$0.it("throws an error when no template is given") {
func testParsing() {
it("throws an error when no template is given") {
let tokens: [Token] = [ .block(value: "include", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
let error = TemplateSyntaxError(reason: "'include' tag requires one argument, the template file to be included. A second optional argument can be used to specify the context that will be passed to the included file", token: tokens.first)
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") {
it("can parse a valid include block") {
let tokens: [Token] = [ .block(value: "include \"test.html\"", at: .unknown) ]
let parser = TokenParser(tokens: tokens, environment: Environment())
@@ -30,8 +32,8 @@ class IncludeTests: XCTestCase {
}
}
$0.describe("rendering") {
$0.it("throws an error when rendering without a loader") {
func testRendering() {
it("throws an error when rendering without a loader") {
let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown))
do {
@@ -41,32 +43,30 @@ class IncludeTests: XCTestCase {
}
}
$0.it("throws an error when it cannot find the included template") {
it("throws an error when it cannot find the included template") {
let node = IncludeNode(templateName: Variable("\"unknown.html\""), token: .block(value: "", at: .unknown))
do {
_ = try node.render(Context(environment: environment))
_ = try node.render(Context(environment: self.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") {
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 context = Context(dictionary: ["target": "World"], environment: self.environment)
let value = try node.render(context)
try expect(value) == "Hello World!"
}
$0.it("successfully passes context") {
it("successfully passes context") {
let template = Template(templateString: """
{% include "test.html" child %}
""")
let context = Context(dictionary: ["child": ["target": "World"]], environment: environment)
let context = Context(dictionary: ["child": ["target": "World"]], environment: self.environment)
let value = try template.render(context)
try expect(value) == "Hello World!"
}
}
}
}
}

View File

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

View File

@@ -1,38 +0,0 @@
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)
$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()) == """
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()) == """
Header
Child_Body
"""
}
}
}
}

View File

@@ -3,15 +3,8 @@ import Spectre
@testable import Stencil
import XCTest
class LexerTests: XCTestCase {
func testLexer() {
describe("Lexer") {
func makeSourceMap(_ token: String, for lexer: Lexer, options: String.CompareOptions = []) -> SourceMap {
guard let range = lexer.templateString.range(of: token, options: options) else { fatalError("Token not found") }
return SourceMap(location: lexer.rangeLocation(range))
}
$0.it("can tokenize text") {
final class LexerTests: XCTestCase {
func testText() throws {
let lexer = Lexer(templateString: "Hello World")
let tokens = lexer.tokenize()
@@ -19,7 +12,7 @@ class LexerTests: XCTestCase {
try expect(tokens.first) == .text(value: "Hello World", at: makeSourceMap("Hello World", for: lexer))
}
$0.it("can tokenize a comment") {
func testComment() throws {
let lexer = Lexer(templateString: "{# Comment #}")
let tokens = lexer.tokenize()
@@ -27,7 +20,7 @@ class LexerTests: XCTestCase {
try expect(tokens.first) == .comment(value: "Comment", at: makeSourceMap("Comment", for: lexer))
}
$0.it("can tokenize a variable") {
func testVariable() throws {
let lexer = Lexer(templateString: "{{ Variable }}")
let tokens = lexer.tokenize()
@@ -35,7 +28,7 @@ class LexerTests: XCTestCase {
try expect(tokens.first) == .variable(value: "Variable", at: makeSourceMap("Variable", for: lexer))
}
$0.it("can tokenize a token without spaces") {
func testTokenWithoutSpaces() throws {
let lexer = Lexer(templateString: "{{Variable}}")
let tokens = lexer.tokenize()
@@ -43,7 +36,7 @@ class LexerTests: XCTestCase {
try expect(tokens.first) == .variable(value: "Variable", at: makeSourceMap("Variable", for: lexer))
}
$0.it("can tokenize unclosed tag by ignoring it") {
func testUnclosedTag() throws {
let templateString = "{{ thing"
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
@@ -52,7 +45,7 @@ class LexerTests: XCTestCase {
try expect(tokens.first) == .text(value: "", at: makeSourceMap("{{ thing", for: lexer))
}
$0.it("can tokenize a mixture of content") {
func testContentMixture() throws {
let templateString = "My name is {{ myname }}."
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
@@ -63,7 +56,7 @@ class LexerTests: XCTestCase {
try expect(tokens[2]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer))
}
$0.it("can tokenize two variables without being greedy") {
func testVariablesWithoutBeingGreedy() throws {
let templateString = "{{ thing }}{{ name }}"
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
@@ -73,22 +66,22 @@ class LexerTests: XCTestCase {
try expect(tokens[1]) == Token.variable(value: "name", at: makeSourceMap("name", for: lexer))
}
$0.it("can tokenize an unclosed block") {
func testUnclosedBlock() throws {
let lexer = Lexer(templateString: "{%}")
_ = lexer.tokenize()
}
$0.it("can tokenize incorrect syntax without crashing") {
func testTokenizeIncorrectSyntaxWithoutCrashing() throws {
let lexer = Lexer(templateString: "func some() {{% if %}")
_ = lexer.tokenize()
}
$0.it("can tokenize an empty variable") {
func testEmptyVariable() throws {
let lexer = Lexer(templateString: "{{}}")
_ = lexer.tokenize()
}
$0.it("can tokenize with new lines") {
func testNewlines() throws {
let templateString = """
My name is {%
if name
@@ -110,7 +103,7 @@ class LexerTests: XCTestCase {
try expect(tokens[4]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer))
}
$0.it("can tokenize escape sequences") {
func testEscapeSequence() throws {
let templateString = "class Some {{ '{' }}{% if true %}{{ stuff }}{% endif %}"
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
@@ -122,8 +115,6 @@ class LexerTests: XCTestCase {
try expect(tokens[3]) == Token.variable(value: "stuff", at: makeSourceMap("stuff", for: lexer))
try expect(tokens[4]) == Token.block(value: "endif", at: makeSourceMap("endif", for: lexer))
}
}
}
func testPerformance() throws {
let path = Path(#file) + ".." + "fixtures" + "huge.html"
@@ -134,4 +125,9 @@ class LexerTests: XCTestCase {
_ = lexer.tokenize()
}
}
private func makeSourceMap(_ token: String, for lexer: Lexer, options: String.CompareOptions = []) -> SourceMap {
guard let range = lexer.templateString.range(of: token, options: options) else { fatalError("Token not found") }
return SourceMap(location: lexer.rangeLocation(range))
}
}

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,10 @@
import XCTest
import Spectre
@testable import Stencil
import XCTest
class TokenParserTests: XCTestCase {
final class TokenParserTests: XCTestCase {
func testTokenParser() {
describe("TokenParser") {
$0.it("can parse a text token") {
it("can parse a text token") {
let parser = TokenParser(tokens: [
.text(value: "Hello World", at: .unknown)
], environment: Environment())
@@ -17,7 +16,7 @@ class TokenParserTests: XCTestCase {
try expect(node?.text) == "Hello World"
}
$0.it("can parse a variable token") {
it("can parse a variable token") {
let parser = TokenParser(tokens: [
.variable(value: "'name'", at: .unknown)
], environment: Environment())
@@ -29,7 +28,7 @@ class TokenParserTests: XCTestCase {
try expect(result) == "name"
}
$0.it("can parse a comment token") {
it("can parse a comment token") {
let parser = TokenParser(tokens: [
.comment(value: "Secret stuff!", at: .unknown)
], environment: Environment())
@@ -38,26 +37,28 @@ class TokenParserTests: XCTestCase {
try expect(nodes.count) == 0
}
$0.it("can parse a tag token") {
it("can parse a tag token") {
let simpleExtension = Extension()
simpleExtension.registerSimpleTag("known") { _ in
return ""
""
}
let parser = TokenParser(tokens: [
.block(value: "known", at: .unknown),
.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") {
it("errors when parsing an unknown tag") {
let tokens: [Token] = [.block(value: "unknown", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
try expect(try parser.parse()).toThrow(TemplateSyntaxError(reason: "Unknown template tag 'unknown'", token: tokens.first))
}
try expect(try parser.parse()).toThrow(TemplateSyntaxError(
reason: "Unknown template tag 'unknown'",
token: tokens.first)
)
}
}
}

View File

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

View File

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

View File

@@ -1,11 +1,10 @@
import XCTest
import Spectre
@testable import Stencil
import XCTest
class TokenTests: XCTestCase {
final class TokenTests: XCTestCase {
func testToken() {
describe("Token") {
$0.it("can split the contents into components") {
it("can split the contents into components") {
let token = Token.text(value: "hello world", at: .unknown)
let components = token.components
@@ -14,7 +13,7 @@ class TokenTests: XCTestCase {
try expect(components[1]) == "world"
}
$0.it("can split the contents into components with single quoted strings") {
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
@@ -23,7 +22,7 @@ class TokenTests: XCTestCase {
try expect(components[1]) == "'kyle fuller'"
}
$0.it("can split the contents into components with double quoted strings") {
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
@@ -32,5 +31,4 @@ class TokenTests: XCTestCase {
try expect(components[1]) == "\"kyle fuller\""
}
}
}
}

View File

@@ -1,232 +1,215 @@
import XCTest
import Foundation
import Spectre
@testable import Stencil
import XCTest
#if os(OSX)
@objc class Superclass: NSObject {
@objc
class Superclass: NSObject {
@objc let name = "Foo"
}
@objc class Object : Superclass {
@objc
class Object: Superclass {
@objc let title = "Hello World"
}
#endif
fileprivate struct Person {
private struct Person {
let name: String
}
fileprivate struct Article {
private struct Article {
let author: Person
}
fileprivate class WebSite {
private class WebSite {
let url: String = "blog.com"
}
fileprivate class Blog: WebSite {
private class Blog: WebSite {
let articles: [Article] = [Article(author: Person(name: "Kyle"))]
let featuring: Article? = Article(author: Person(name: "Jhon"))
}
class VariableTests: XCTestCase {
func testVariable() {
describe("Variable") {
let context = Context(dictionary: [
final class VariableTests: XCTestCase {
let context: Context = {
let ext = Extension()
ext.registerFilter("incr") { arg in
(arg.flatMap { toNumber(value: $0) } ?? 0) + 1
}
let environment = Environment(extensions: [ext])
var context = Context(dictionary: [
"name": "Kyle",
"contacts": ["Katie", "Carlton"],
"profiles": [
"github": "kylef",
"github": "kylef"
],
"counter": [
"count": "kylef",
"count": "kylef"
],
"article": Article(author: Person(name: "Kyle")),
"blog": Blog(),
"tuple": (one: 1, two: 2)
])
#if os(OSX)
], environment: environment)
#if os(OSX)
context["object"] = Object()
#endif
context["blog"] = Blog()
#endif
return context
}()
$0.it("can resolve a string literal with double quotes") {
func testLiterals() {
it("can resolve a string literal with double quotes") {
let variable = Variable("\"name\"")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "name"
}
$0.it("can resolve a string literal with single quotes") {
it("can resolve a string literal with single quotes") {
let variable = Variable("'name'")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "name"
}
$0.it("can resolve an integer literal") {
it("can resolve an integer literal") {
let variable = Variable("5")
let result = try variable.resolve(context) as? Int
let result = try variable.resolve(self.context) as? Int
try expect(result) == 5
}
$0.it("can resolve an float literal") {
it("can resolve an float literal") {
let variable = Variable("3.14")
let result = try variable.resolve(context) as? Number
let result = try variable.resolve(self.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
it("can resolve boolean literal") {
try expect(Variable("true").resolve(self.context) as? Bool) == true
try expect(Variable("false").resolve(self.context) as? Bool) == false
try expect(Variable("0").resolve(self.context) as? Int) == 0
try expect(Variable("1").resolve(self.context) as? Int) == 1
}
}
$0.it("can resolve a string variable") {
func testVariable() {
it("can resolve a string variable") {
let variable = Variable("name")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Kyle"
}
$0.context("given string") {
$0.it("can resolve an item via it's index") {
let variable = Variable("name.0")
let result = try variable.resolve(context) as? Character
try expect(result) == "K"
let variable1 = Variable("name.1")
let result1 = try variable1.resolve(context) as? Character
try expect(result1) == "y"
}
$0.it("can resolve an item via unknown index") {
let variable = Variable("name.5")
let result = try variable.resolve(context) as? Character
try expect(result).to.beNil()
let variable1 = Variable("name.-5")
let result1 = try variable1.resolve(context) as? Character
try expect(result1).to.beNil()
}
$0.it("can resolve the first item") {
let variable = Variable("name.first")
let result = try variable.resolve(context) as? Character
try expect(result) == "K"
}
$0.it("can resolve the last item") {
let variable = Variable("name.last")
let result = try variable.resolve(context) as? Character
try expect(result) == "e"
}
$0.it("can get the characters count") {
let variable = Variable("name.count")
let result = try variable.resolve(context) as? Int
try expect(result) == 4
}
}
$0.context("given dictionary") {
$0.it("can resolve an item") {
func testDictionary() {
it("can resolve an item from a dictionary") {
let variable = Variable("profiles.github")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "kylef"
}
$0.it("can get the count") {
it("can get the count of a dictionary") {
let variable = Variable("profiles.count")
let result = try variable.resolve(context) as? Int
let result = try variable.resolve(self.context) as? Int
try expect(result) == 1
}
}
$0.context("given array") {
$0.it("can resolve an item via it's index") {
func testArray() {
it("can resolve an item from an array via it's index") {
let variable = Variable("contacts.0")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Katie"
let variable1 = Variable("contacts.1")
let result1 = try variable1.resolve(context) as? String
let result1 = try variable1.resolve(self.context) as? String
try expect(result1) == "Carlton"
}
$0.it("can resolve an item via unknown index") {
it("can resolve an item from an array via unknown index") {
let variable = Variable("contacts.5")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result).to.beNil()
let variable1 = Variable("contacts.-5")
let result1 = try variable1.resolve(context) as? String
let result1 = try variable1.resolve(self.context) as? String
try expect(result1).to.beNil()
}
$0.it("can resolve the first item") {
it("can resolve the first item from an array") {
let variable = Variable("contacts.first")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Katie"
}
$0.it("can resolve the last item") {
it("can resolve the last item from an array") {
let variable = Variable("contacts.last")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.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
func testReflection() {
it("can resolve a property with reflection") {
let variable = Variable("article.author.name")
let result = try variable.resolve(self.context) as? String
try expect(result) == "Kyle"
}
it("can resolve a value via reflection") {
let variable = Variable("blog.articles.0.author.name")
let result = try variable.resolve(self.context) as? String
try expect(result) == "Kyle"
}
it("can resolve a superclass value via reflection") {
let variable = Variable("blog.url")
let result = try variable.resolve(self.context) as? String
try expect(result) == "blog.com"
}
it("can resolve optional variable property using reflection") {
let variable = Variable("blog.featuring.author.name")
let result = try variable.resolve(self.context) as? String
try expect(result) == "Jhon"
}
}
func testKVO() {
#if os(OSX)
it("can resolve a value via KVO") {
let variable = Variable("object.title")
let result = try variable.resolve(self.context) as? String
try expect(result) == "Hello World"
}
it("can resolve a superclass value via KVO") {
let variable = Variable("object.name")
let result = try variable.resolve(self.context) as? String
try expect(result) == "Foo"
}
it("does not crash on KVO") {
let variable = Variable("object.fullname")
let result = try variable.resolve(self.context) as? String
try expect(result).to.beNil()
}
#endif
}
func testTuple() {
it("can resolve tuple by index") {
let variable = Variable("tuple.0")
let result = try variable.resolve(self.context) as? Int
try expect(result) == 1
}
it("can resolve tuple by label") {
let variable = Variable("tuple.two")
let result = try variable.resolve(self.context) as? Int
try expect(result) == 2
}
}
$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") {
func testOptional() {
it("does not render Optional") {
var array: [Any?] = [1, nil]
array.append(array)
let context = Context(dictionary: ["values": array])
@@ -234,87 +217,78 @@ class VariableTests: XCTestCase {
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"]) {
func testSubscripting() {
it("can resolve a property subscript via reflection") {
try self.context.push(dictionary: ["property": "name"]) {
let variable = Variable("article.author[property]")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Kyle"
}
}
$0.it("can subscript an array with a valid index") {
try context.push(dictionary: ["property": 0]) {
it("can subscript an array with a valid index") {
try self.context.push(dictionary: ["property": 0]) {
let variable = Variable("contacts[property]")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Katie"
}
}
$0.it("can subscript an array with an unknown index") {
try context.push(dictionary: ["property": 5]) {
it("can subscript an array with an unknown index") {
try self.context.push(dictionary: ["property": 5]) {
let variable = Variable("contacts[property]")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.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"]) {
#if os(OSX)
it("can resolve a subscript via KVO") {
try self.context.push(dictionary: ["property": "name"]) {
let variable = Variable("object[property]")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Foo"
}
}
#endif
#endif
$0.it("can resolve an optional subscript via reflection") {
try context.push(dictionary: ["property": "featuring"]) {
it("can resolve an optional subscript via reflection") {
try self.context.push(dictionary: ["property": "featuring"]) {
let variable = Variable("blog[property].author.name")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Jhon"
}
}
}
$0.it("can resolve multiple subscripts") {
try context.push(dictionary: [
func testMultipleSubscripting() {
it("can resolve multiple subscripts") {
try self.context.push(dictionary: [
"prop1": "articles",
"prop2": 0,
"prop3": "name"
]) {
let variable = Variable("blog[prop1][prop2].author[prop3]")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Kyle"
}
}
$0.it("can resolve nested subscripts") {
try context.push(dictionary: [
it("can resolve nested subscripts") {
try self.context.push(dictionary: [
"prop1": "prop2",
"ref": ["prop2": "name"]
]) {
let variable = Variable("article.author[ref[prop1]]")
let result = try variable.resolve(context) as? String
let result = try variable.resolve(self.context) as? String
try expect(result) == "Kyle"
}
}
$0.it("throws for invalid keypath syntax") {
try context.push(dictionary: ["prop": "name"]) {
it("throws for invalid keypath syntax") {
try self.context.push(dictionary: ["prop": "name"]) {
let samples = [
".",
"..",
@@ -333,79 +307,49 @@ class VariableTests: XCTestCase {
for lookup in samples {
let variable = Variable(lookup)
try expect(variable.resolve(context)).toThrow()
}
try expect(variable.resolve(self.context)).toThrow()
}
}
}
}
describe("RangeVariable") {
let context: Context = {
let ext = Extension()
ext.registerFilter("incr", filter: { (arg: Any?) in toNumber(value: arg!)! + 1 })
let environment = Environment(extensions: [ext])
return Context(dictionary: [:], environment: environment)
}()
func testRangeVariable() {
func makeVariable(_ token: String) throws -> RangeVariable? {
let token = Token.variable(value: token, at: .unknown)
return try RangeVariable(token.contents, environment: context.environment, containedIn: token)
}
$0.it("can resolve closed range as array") {
let result = try makeVariable("1...3")?.resolve(context) as? [Int]
it("can resolve closed range as array") {
let result = try makeVariable("1...3")?.resolve(self.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]
it("can resolve decreasing closed range as reversed array") {
let result = try makeVariable("3...1")?.resolve(self.context) as? [Int]
try expect(result) == [3, 2, 1]
}
$0.it("can use filter on range variables") {
let result = try makeVariable("1|incr...3|incr")?.resolve(context) as? [Int]
it("can use filter on range variables") {
let result = try makeVariable("1|incr...3|incr")?.resolve(self.context) as? [Int]
try expect(result) == [2, 3, 4]
}
$0.it("throws when left value is not int") {
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") {
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") {
it("throws is left range value is missing") {
try expect(makeVariable("...1")).toThrow()
}
$0.it("throws is right range value is missing") {
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()
}
}
}
}

View File

@@ -2,19 +2,54 @@ import XCTest
extension ContextTests {
static let __allTests = [
("testContext", testContext),
("testContextRestoration", testContextRestoration),
("testContextSubscripting", testContextSubscripting),
]
}
extension EnvironmentBaseAndChildTemplateTests {
static let __allTests = [
("testRuntimeErrorInBaseTemplate", testRuntimeErrorInBaseTemplate),
("testRuntimeErrorInChildTemplate", testRuntimeErrorInChildTemplate),
("testSyntaxErrorInBaseTemplate", testSyntaxErrorInBaseTemplate),
("testSyntaxErrorInChildTemplate", testSyntaxErrorInChildTemplate),
]
}
extension EnvironmentIncludeTemplateTests {
static let __allTests = [
("testRuntimeError", testRuntimeError),
("testSyntaxError", testSyntaxError),
]
}
extension EnvironmentTests {
static let __allTests = [
("testEnvironment", testEnvironment),
("testLoading", testLoading),
("testRendering", testRendering),
("testRenderingError", testRenderingError),
("testSyntaxError", testSyntaxError),
("testUnknownFilter", testUnknownFilter),
]
}
extension ExpressionsTests {
static let __allTests = [
("testExpressions", testExpressions),
("testAndExpression", testAndExpression),
("testEqualityExpression", testEqualityExpression),
("testExpressionParsing", testExpressionParsing),
("testFalseExpressions", testFalseExpressions),
("testFalseInExpression", testFalseInExpression),
("testInequalityExpression", testInequalityExpression),
("testLessThanEqualExpression", testLessThanEqualExpression),
("testLessThanExpression", testLessThanExpression),
("testMoreThanEqualExpression", testMoreThanEqualExpression),
("testMoreThanExpression", testMoreThanExpression),
("testMultipleExpressions", testMultipleExpressions),
("testNotExpression", testNotExpression),
("testOrExpression", testOrExpression),
("testTrueExpressions", testTrueExpressions),
("testTrueInExpression", testTrueInExpression),
]
}
@@ -26,50 +61,95 @@ extension FilterTagTests {
extension FilterTests {
static let __allTests = [
("testFilter", testFilter),
("testDefaultFilter", testDefaultFilter),
("testDynamicFilters", testDynamicFilters),
("testFilterSuggestion", testFilterSuggestion),
("testIndentContent", testIndentContent),
("testIndentFirstLine", testIndentFirstLine),
("testIndentNotEmptyLines", testIndentNotEmptyLines),
("testIndentWithArbitraryCharacter", testIndentWithArbitraryCharacter),
("testJoinFilter", testJoinFilter),
("testRegistration", testRegistration),
("testRegistrationOverrideDefault", testRegistrationOverrideDefault),
("testRegistrationWithArguments", testRegistrationWithArguments),
("testSplitFilter", testSplitFilter),
("testStringFilters", testStringFilters),
("testStringFiltersWithArrays", testStringFiltersWithArrays),
]
}
extension ForNodeTests {
static let __allTests = [
("testArrayOfTuples", testArrayOfTuples),
("testForNode", testForNode),
("testHandleInvalidInput", testHandleInvalidInput),
("testIterateDictionary", testIterateDictionary),
("testIterateRange", testIterateRange),
("testIterateUsingMirroring", testIterateUsingMirroring),
("testLoopMetadata", testLoopMetadata),
("testWhereExpression", testWhereExpression),
]
}
extension IfNodeTests {
static let __allTests = [
("testIfNode", testIfNode),
("testEvaluatesNilAsFalse", testEvaluatesNilAsFalse),
("testParseIf", testParseIf),
("testParseIfnot", testParseIfnot),
("testParseIfWithElif", testParseIfWithElif),
("testParseIfWithElifWithoutElse", testParseIfWithElifWithoutElse),
("testParseIfWithElse", testParseIfWithElse),
("testParseMultipleElif", testParseMultipleElif),
("testParsingErrors", testParsingErrors),
("testRendering", testRendering),
("testSupportsRangeVariables", testSupportsRangeVariables),
("testSupportVariableFilters", testSupportVariableFilters),
]
}
extension IncludeTests {
static let __allTests = [
("testInclude", testInclude),
("testParsing", testParsing),
("testRendering", testRendering),
]
}
extension InheritenceTests {
extension InheritanceTests {
static let __allTests = [
("testInheritence", testInheritence),
("testInheritance", testInheritance),
]
}
extension LexerTests {
static let __allTests = [
("testLexer", testLexer),
("testComment", testComment),
("testContentMixture", testContentMixture),
("testEmptyVariable", testEmptyVariable),
("testEscapeSequence", testEscapeSequence),
("testNewlines", testNewlines),
("testPerformance", testPerformance),
("testText", testText),
("testTokenizeIncorrectSyntaxWithoutCrashing", testTokenizeIncorrectSyntaxWithoutCrashing),
("testTokenWithoutSpaces", testTokenWithoutSpaces),
("testUnclosedBlock", testUnclosedBlock),
("testUnclosedTag", testUnclosedTag),
("testVariable", testVariable),
("testVariablesWithoutBeingGreedy", testVariablesWithoutBeingGreedy),
]
}
extension NodeTests {
static let __allTests = [
("testNode", testNode),
("testRendering", testRendering),
("testTextNode", testTextNode),
("testVariableNode", testVariableNode),
]
}
extension NowNodeTests {
static let __allTests = [
("testNowNode", testNowNode),
("testParsing", testParsing),
("testRendering", testRendering),
]
}
@@ -81,7 +161,8 @@ extension StencilTests {
extension TemplateLoaderTests {
static let __allTests = [
("testTemplateLoader", testTemplateLoader),
("testDictionaryLoader", testDictionaryLoader),
("testFileSystemLoader", testFileSystemLoader),
]
}
@@ -105,6 +186,16 @@ extension TokenTests {
extension VariableTests {
static let __allTests = [
("testArray", testArray),
("testDictionary", testDictionary),
("testKVO", testKVO),
("testLiterals", testLiterals),
("testMultipleSubscripting", testMultipleSubscripting),
("testOptional", testOptional),
("testRangeVariable", testRangeVariable),
("testReflection", testReflection),
("testSubscripting", testSubscripting),
("testTuple", testTuple),
("testVariable", testVariable),
]
}
@@ -113,6 +204,8 @@ extension VariableTests {
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(ContextTests.__allTests),
testCase(EnvironmentBaseAndChildTemplateTests.__allTests),
testCase(EnvironmentIncludeTemplateTests.__allTests),
testCase(EnvironmentTests.__allTests),
testCase(ExpressionsTests.__allTests),
testCase(FilterTagTests.__allTests),
@@ -120,7 +213,7 @@ public func __allTests() -> [XCTestCaseEntry] {
testCase(ForNodeTests.__allTests),
testCase(IfNodeTests.__allTests),
testCase(IncludeTests.__allTests),
testCase(InheritenceTests.__allTests),
testCase(InheritanceTests.__allTests),
testCase(LexerTests.__allTests),
testCase(NodeTests.__allTests),
testCase(NowNodeTests.__allTests),