Implement trim whitespace

This commit is contained in:
yonaskolb
2019-11-17 00:41:42 +11:00
committed by David Jennes
parent d4dc631752
commit ef97973e85
10 changed files with 444 additions and 74 deletions

View File

@@ -51,9 +51,9 @@ final class LexerTests: XCTestCase {
let tokens = lexer.tokenize()
try expect(tokens.count) == 3
try expect(tokens[0]) == Token.text(value: "My name is ", at: makeSourceMap("My name is ", for: lexer))
try expect(tokens[1]) == Token.variable(value: "myname", at: makeSourceMap("myname", for: lexer))
try expect(tokens[2]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer))
try expect(tokens[0]) == .text(value: "My name is ", at: makeSourceMap("My name is ", for: lexer))
try expect(tokens[1]) == .variable(value: "myname", at: makeSourceMap("myname", for: lexer))
try expect(tokens[2]) == .text(value: ".", at: makeSourceMap(".", for: lexer))
}
func testVariablesWithoutBeingGreedy() throws {
@@ -62,8 +62,8 @@ final class LexerTests: XCTestCase {
let tokens = lexer.tokenize()
try expect(tokens.count) == 2
try expect(tokens[0]) == Token.variable(value: "thing", at: makeSourceMap("thing", for: lexer))
try expect(tokens[1]) == Token.variable(value: "name", at: makeSourceMap("name", for: lexer))
try expect(tokens[0]) == .variable(value: "thing", at: makeSourceMap("thing", for: lexer))
try expect(tokens[1]) == .variable(value: "name", at: makeSourceMap("name", for: lexer))
}
func testUnclosedBlock() throws {
@@ -98,11 +98,26 @@ final class LexerTests: XCTestCase {
let tokens = lexer.tokenize()
try expect(tokens.count) == 5
try expect(tokens[0]) == Token.text(value: "My name is ", at: makeSourceMap("My name is", for: lexer))
try expect(tokens[1]) == Token.block(value: "if name and name", at: makeSourceMap("{%", for: lexer))
try expect(tokens[2]) == Token.variable(value: "name", at: makeSourceMap("name", for: lexer, options: .backwards))
try expect(tokens[3]) == Token.block(value: "endif", at: makeSourceMap("endif", for: lexer))
try expect(tokens[4]) == Token.text(value: ".", at: makeSourceMap(".", for: lexer))
try expect(tokens[0]) == .text(value: "My name is ", at: makeSourceMap("My name is", for: lexer))
try expect(tokens[1]) == .block(value: "if name and name", at: makeSourceMap("{%", for: lexer))
try expect(tokens[2]) == .variable(value: "name", at: makeSourceMap("name", for: lexer, options: .backwards))
try expect(tokens[3]) == .block(value: "endif", at: makeSourceMap("endif", for: lexer))
try expect(tokens[4]) == .text(value: ".", at: makeSourceMap(".", for: lexer))
}
func testTrimSymbols() throws {
let fBlock = "if hello"
let sBlock = "ta da"
let lexer = Lexer(templateString: "{%+ \(fBlock) -%}{% \(sBlock) -%}")
let tokens = lexer.tokenize()
let behaviours = (
WhitespaceBehaviour(leading: .keep, trailing: .trim),
WhitespaceBehaviour(leading: .unspecified, trailing: .trim)
)
try expect(tokens.count) == 2
try expect(tokens[0]) == .block(value: fBlock, at: makeSourceMap(fBlock, for: lexer), whitespace: behaviours.0)
try expect(tokens[1]) == .block(value: sBlock, at: makeSourceMap(sBlock, for: lexer), whitespace: behaviours.1)
}
func testEscapeSequence() throws {
@@ -111,11 +126,11 @@ final class LexerTests: XCTestCase {
let tokens = lexer.tokenize()
try expect(tokens.count) == 5
try expect(tokens[0]) == Token.text(value: "class Some ", at: makeSourceMap("class Some ", for: lexer))
try expect(tokens[1]) == Token.variable(value: "'{'", at: makeSourceMap("'{'", for: lexer))
try expect(tokens[2]) == Token.block(value: "if true", at: makeSourceMap("if true", for: lexer))
try expect(tokens[3]) == Token.variable(value: "stuff", at: makeSourceMap("stuff", for: lexer))
try expect(tokens[4]) == Token.block(value: "endif", at: makeSourceMap("endif", for: lexer))
try expect(tokens[0]) == .text(value: "class Some ", at: makeSourceMap("class Some ", for: lexer))
try expect(tokens[1]) == .variable(value: "'{'", at: makeSourceMap("'{'", for: lexer))
try expect(tokens[2]) == .block(value: "if true", at: makeSourceMap("if true", for: lexer))
try expect(tokens[3]) == .variable(value: "stuff", at: makeSourceMap("stuff", for: lexer))
try expect(tokens[4]) == .block(value: "endif", at: makeSourceMap("endif", for: lexer))
}
func testPerformance() throws {

View File

@@ -14,6 +14,48 @@ final class NodeTests: XCTestCase {
let node = TextNode(text: "Hello World")
try expect(try node.render(self.context)) == "Hello World"
}
it("Trims leading whitespace") {
let text = " \n Some text "
let trimBehaviour = TrimBehaviour(leading: .whitespace, trailing: .nothing)
let node = TextNode(text: text, trimBehaviour: trimBehaviour)
try expect(try node.render(self.context)) == "\n Some text "
}
it("Trims leading whitespace and one newline") {
let text = "\n\n Some text "
let trimBehaviour = TrimBehaviour(leading: .whitespaceAndOneNewLine, trailing: .nothing)
let node = TextNode(text: text, trimBehaviour: trimBehaviour)
try expect(try node.render(self.context)) == "\n Some text "
}
it("Trims leading whitespace and one newline") {
let text = "\n\n Some text "
let trimBehaviour = TrimBehaviour(leading: .whitespaceAndNewLines, trailing: .nothing)
let node = TextNode(text: text, trimBehaviour: trimBehaviour)
try expect(try node.render(self.context)) == "Some text "
}
it("Trims trailing whitespace") {
let text = " Some text \n"
let trimBehaviour = TrimBehaviour(leading: .nothing, trailing: .whitespace)
let node = TextNode(text: text, trimBehaviour: trimBehaviour)
try expect(try node.render(self.context)) == " Some text\n"
}
it("Trims trailing whitespace and one newline") {
let text = " Some text \n \n "
let trimBehaviour = TrimBehaviour(leading: .nothing, trailing: .whitespaceAndOneNewLine)
let node = TextNode(text: text, trimBehaviour: trimBehaviour)
try expect(try node.render(self.context)) == " Some text \n "
}
it("Trims trailing whitespace and newlines") {
let text = " Some text \n \n "
let trimBehaviour = TrimBehaviour(leading: .nothing, trailing: .whitespaceAndNewLines)
let node = TextNode(text: text, trimBehaviour: trimBehaviour)
try expect(try node.render(self.context)) == " Some text"
}
it("Trims all whitespace") {
let text = " \n \nSome text \n "
let trimBehaviour = TrimBehaviour(leading: .whitespaceAndNewLines, trailing: .whitespaceAndNewLines)
let node = TextNode(text: text, trimBehaviour: trimBehaviour)
try expect(try node.render(self.context)) == "Some text"
}
}
func testVariableNode() {

View File

@@ -3,62 +3,77 @@ import Spectre
import XCTest
final class TokenParserTests: XCTestCase {
func testTokenParser() {
it("can parse a text token") {
let parser = TokenParser(tokens: [
.text(value: "Hello World", at: .unknown)
], environment: Environment())
func testTextToken() throws {
let parser = TokenParser(tokens: [
.text(value: "Hello World", at: .unknown)
], environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? TextNode
let nodes = try parser.parse()
let node = nodes.first as? TextNode
try expect(nodes.count) == 1
try expect(node?.text) == "Hello World"
try expect(nodes.count) == 1
try expect(node?.text) == "Hello World"
}
func testVariableToken() throws {
let parser = TokenParser(tokens: [
.variable(value: "'name'", at: .unknown)
], environment: Environment())
let nodes = try parser.parse()
let node = nodes.first as? VariableNode
try expect(nodes.count) == 1
let result = try node?.render(Context())
try expect(result) == "name"
}
func testCommentToken() throws {
let parser = TokenParser(tokens: [
.comment(value: "Secret stuff!", at: .unknown)
], environment: Environment())
let nodes = try parser.parse()
try expect(nodes.count) == 0
}
func testTagToken() throws {
let simpleExtension = Extension()
simpleExtension.registerSimpleTag("known") { _ in
""
}
it("can parse a variable token") {
let parser = TokenParser(tokens: [
.variable(value: "'name'", at: .unknown)
], environment: Environment())
let parser = TokenParser(tokens: [
.block(value: "known", at: .unknown)
], environment: Environment(extensions: [simpleExtension]))
let nodes = try parser.parse()
let node = nodes.first as? VariableNode
try expect(nodes.count) == 1
let result = try node?.render(Context())
try expect(result) == "name"
}
let nodes = try parser.parse()
try expect(nodes.count) == 1
}
it("can parse a comment token") {
let parser = TokenParser(tokens: [
.comment(value: "Secret stuff!", at: .unknown)
], environment: Environment())
func testErrorUnknownTag() throws {
let tokens: [Token] = [.block(value: "unknown", at: .unknown)]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
try expect(nodes.count) == 0
}
try expect(try parser.parse()).toThrow(TemplateSyntaxError(
reason: "Unknown template tag 'unknown'",
token: tokens.first
))
}
it("can parse a tag token") {
let simpleExtension = Extension()
simpleExtension.registerSimpleTag("known") { _ in
""
}
func testTransformWhitespaceBehaviourToTrimBehaviour() throws {
let simpleExtension = Extension()
simpleExtension.registerSimpleTag("known") { _ in "" }
let parser = TokenParser(tokens: [
.block(value: "known", at: .unknown)
], environment: Environment(extensions: [simpleExtension]))
let parser = TokenParser(tokens: [
.block(value: "known", at: .unknown, whitespace: WhitespaceBehaviour(leading: .unspecified, trailing: .trim)),
.text(value: " \nSome text ", at: .unknown),
.block(value: "known", at: .unknown, whitespace: WhitespaceBehaviour(leading: .keep, trailing: .trim))
], environment: Environment(extensions: [simpleExtension]))
let nodes = try parser.parse()
try expect(nodes.count) == 1
}
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
))
}
let nodes = try parser.parse()
try expect(nodes.count) == 3
let textNode = nodes[1] as? TextNode
try expect(textNode?.text) == " \nSome text "
try expect(textNode?.trimBehaviour) == TrimBehaviour(leading: .whitespaceAndNewLines, trailing: .nothing)
}
}

View File

@@ -0,0 +1,137 @@
import Spectre
import Stencil
import XCTest
final class TrimBehaviourTests: XCTestCase {
func testSmartTrimCanRemoveNewlines() throws {
let templateString = """
{% for item in items %}
- {{item}}
{% endfor %}
text
"""
let context = ["items": ["item 1", "item 2"]]
let template = Template(templateString: templateString, environment: .init(trimBehaviour: .smart))
let result = try template.render(context)
// swiftlint:disable indentation_width
try expect(result) == """
- item 1
- item 2
text
"""
// swiftlint:enable indentation_width
}
func testSmartTrimOnlyRemoveSingleNewlines() throws {
let templateString = """
{% for item in items %}
- {{item}}
{% endfor %}
text
"""
let context = ["items": ["item 1", "item 2"]]
let template = Template(templateString: templateString, environment: .init(trimBehaviour: .smart))
let result = try template.render(context)
// swiftlint:disable indentation_width
try expect(result) == """
- item 1
- item 2
text
"""
// swiftlint:enable indentation_width
}
func testSmartTrimCanRemoveNewlinesWhileKeepingWhitespace() throws {
// swiftlint:disable indentation_width
let templateString = """
Items:
{% for item in items %}
- {{item}}
{% endfor %}
"""
// swiftlint:enable indentation_width
let context = ["items": ["item 1", "item 2"]]
let template = Template(templateString: templateString, environment: .init(trimBehaviour: .smart))
let result = try template.render(context)
// swiftlint:disable indentation_width
try expect(result) == """
Items:
- item 1
- item 2
"""
// swiftlint:enable indentation_width
}
func testTrimSymbols() {
it("Respects whitespace control symbols in for tags") {
// swiftlint:disable indentation_width
let template: Template = """
{% for num in numbers -%}
{{num}}
{%- endfor %}
"""
// swiftlint:enable indentation_width
let result = try template.render([ "numbers": Array(1...9) ])
try expect(result) == "123456789"
}
it("Respects whitespace control symbols in if tags") {
let template: Template = """
{% if value -%}
{{text}}
{%- endif %}
"""
let result = try template.render([ "text": "hello", "value": true ])
try expect(result) == "hello"
}
}
func testTrimSymbolsOverridingEnvironment() {
let environment = Environment(trimBehaviour: .all)
it("respects whitespace control symbols in if tags") {
// swiftlint:disable indentation_width
let templateString = """
{% if value +%}
{{text}}
{%+ endif %}
"""
// swiftlint:enable indentation_width
let template = Template(templateString: templateString, environment: environment)
let result = try template.render([ "text": "hello", "value": true ])
try expect(result) == "\n hello\n"
}
it("can customize blocks on same line as text") {
// swiftlint:disable indentation_width
let templateString = """
Items:{% for item in items +%}
- {{item}}
{%- endfor %}
"""
// swiftlint:enable indentation_width
let context = ["items": ["item 1", "item 2"]]
let template = Template(templateString: templateString, environment: environment)
let result = try template.render(context)
// swiftlint:disable indentation_width
try expect(result) == """
Items:
- item 1
- item 2
"""
// swiftlint:enable indentation_width
}
}
}