How to use Context class

Best Mockingbird code snippet using Context

SpecTests.swift

Source:SpecTests.swift Github

copy

Full Screen

...38 return [39 ("testTruthy", testTruthy),40 ("testFalsey", testFalsey),41 ("testNullisfalsey", testNullisfalsey),42 ("testContext", testContext),43 ("testParentcontexts", testParentcontexts),44 ("testVariabletest", testVariabletest),45 ("testListContexts", testListContexts),46 ("testDeeplyNestedContexts", testDeeplyNestedContexts),47 ("testList", testList),48 ("testEmptyList", testEmptyList),49 ("testDoubled", testDoubled),50 ("testNested_Truthy", testNested_Truthy),51 ("testNested_Falsey", testNested_Falsey),52 ("testContextMisses", testContextMisses),53 ("testImplicitIterator_String", testImplicitIterator_String),54 ("testImplicitIterator_Integer", testImplicitIterator_Integer),55 ("testImplicitIterator_Decimal", testImplicitIterator_Decimal),56 ("testImplicitIterator_Array", testImplicitIterator_Array),57 ("testDottedNames_Truthy", testDottedNames_Truthy),58 ("testDottedNames_Falsey", testDottedNames_Falsey),59 ("testDottedNames_BrokenChains", testDottedNames_BrokenChains),60 ("testSurroundingWhitespace", testSurroundingWhitespace),61 ("testInternalWhitespace", testInternalWhitespace),62 ("testIndentedInlineSections", testIndentedInlineSections),63 ("testStandaloneLines", testStandaloneLines),64 ("testIndentedStandaloneLines", testIndentedStandaloneLines),65 ("testStandaloneLineEndings", testStandaloneLineEndings),66 ("testStandaloneWithoutPreviousLine", testStandaloneWithoutPreviousLine),67 ("testStandaloneWithoutNewline", testStandaloneWithoutNewline),68 ("testPadding", testPadding),69 ]70 }71 func testTruthy() throws {72 let templateString = "\"{{#boolean}}This should be rendered.{{/boolean}}\""73 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!74 let expected = "\"This should be rendered.\""75 let context = try JSONDecoder().decode(Context.self, from: contextJSON)76 let template = try Template(templateString)77 let rendered = template.render(with: context)78 XCTAssertEqual(rendered, expected, "Truthy sections should have their contents rendered.")79 }80 func testFalsey() throws {81 let templateString = "\"{{#boolean}}This should not be rendered.{{/boolean}}\""82 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!83 let expected = "\"\""84 let context = try JSONDecoder().decode(Context.self, from: contextJSON)85 let template = try Template(templateString)86 let rendered = template.render(with: context)87 XCTAssertEqual(rendered, expected, "Falsey sections should have their contents omitted.")88 }89 func testNullisfalsey() throws {90 let templateString = "\"{{#null}}This should not be rendered.{{/null}}\""91 let contextJSON = "{\"null\":null}".data(using: .utf8)!92 let expected = "\"\""93 let context = try JSONDecoder().decode(Context.self, from: contextJSON)94 let template = try Template(templateString)95 let rendered = template.render(with: context)96 XCTAssertEqual(rendered, expected, "Null is falsey.")97 }98 func testContext() throws {99 let templateString = "\"{{#context}}Hi {{name}}.{{/context}}\""100 let contextJSON = "{\"context\":{\"name\":\"Joe\"}}".data(using: .utf8)!101 let expected = "\"Hi Joe.\""102 let context = try JSONDecoder().decode(Context.self, from: contextJSON)103 let template = try Template(templateString)104 let rendered = template.render(with: context)105 XCTAssertEqual(rendered, expected, "Objects and hashes should be pushed onto the context stack.")106 }107 func testParentcontexts() throws {108 let templateString = "\"{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}\""109 let contextJSON = "{\"sec\":{\"b\":\"bar\"},\"b\":\"wrong\",\"c\":{\"d\":\"baz\"},\"a\":\"foo\"}".data(using: .utf8)!110 let expected = "\"foo, bar, baz\""111 let context = try JSONDecoder().decode(Context.self, from: contextJSON)112 let template = try Template(templateString)113 let rendered = template.render(with: context)114 XCTAssertEqual(rendered, expected, "Names missing in the current context are looked up in the stack.")115 }116 func testVariabletest() throws {117 let templateString = "\"{{#foo}}{{.}} is {{foo}}{{/foo}}\""118 let contextJSON = "{\"foo\":\"bar\"}".data(using: .utf8)!119 let expected = "\"bar is bar\""120 let context = try JSONDecoder().decode(Context.self, from: contextJSON)121 let template = try Template(templateString)122 let rendered = template.render(with: context)123 XCTAssertEqual(rendered, expected, "Non-false sections have their value at the top of context, accessible as {{.}} or through the parent context. This gives a simple way to display content conditionally if a variable exists. ")124 }125 func testListContexts() throws {126 let templateString = "{{#tops}}{{#middles}}{{tname.lower}}{{mname}}.{{#bottoms}}{{tname.upper}}{{mname}}{{bname}}.{{/bottoms}}{{/middles}}{{/tops}}"127 let contextJSON = "{\"tops\":[{\"middles\":[{\"mname\":\"1\",\"bottoms\":[{\"bname\":\"x\"},{\"bname\":\"y\"}]}],\"tname\":{\"lower\":\"a\",\"upper\":\"A\"}}]}".data(using: .utf8)!128 let expected = "a1.A1x.A1y."129 let context = try JSONDecoder().decode(Context.self, from: contextJSON)130 let template = try Template(templateString)131 let rendered = template.render(with: context)132 XCTAssertEqual(rendered, expected, "All elements on the context stack should be accessible within lists.")133 }134 func testDeeplyNestedContexts() throws {135 let templateString = "{{#a}}\n{{one}}\n{{#b}}\n{{one}}{{two}}{{one}}\n{{#c}}\n{{one}}{{two}}{{three}}{{two}}{{one}}\n{{#d}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}\n{{#five}}\n{{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}\n{{one}}{{two}}{{three}}{{four}}{{.}}6{{.}}{{four}}{{three}}{{two}}{{one}}\n{{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}\n{{/five}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}\n{{/d}}\n{{one}}{{two}}{{three}}{{two}}{{one}}\n{{/c}}\n{{one}}{{two}}{{one}}\n{{/b}}\n{{one}}\n{{/a}}\n"136 let contextJSON = "{\"b\":{\"two\":2},\"c\":{\"three\":3,\"d\":{\"four\":4,\"five\":5}},\"a\":{\"one\":1}}".data(using: .utf8)!137 let expected = "1\n121\n12321\n1234321\n123454321\n12345654321\n123454321\n1234321\n12321\n121\n1\n"138 let context = try JSONDecoder().decode(Context.self, from: contextJSON)139 let template = try Template(templateString)140 let rendered = template.render(with: context)141 XCTAssertEqual(rendered, expected, "All elements on the context stack should be accessible.")142 }143 func testList() throws {144 let templateString = "\"{{#list}}{{item}}{{/list}}\""145 let contextJSON = "{\"list\":[{\"item\":1},{\"item\":2},{\"item\":3}]}".data(using: .utf8)!146 let expected = "\"123\""147 let context = try JSONDecoder().decode(Context.self, from: contextJSON)148 let template = try Template(templateString)149 let rendered = template.render(with: context)150 XCTAssertEqual(rendered, expected, "Lists should be iterated; list items should visit the context stack.")151 }152 func testEmptyList() throws {153 let templateString = "\"{{#list}}Yay lists!{{/list}}\""154 let contextJSON = "{\"list\":[]}".data(using: .utf8)!155 let expected = "\"\""156 let context = try JSONDecoder().decode(Context.self, from: contextJSON)157 let template = try Template(templateString)158 let rendered = template.render(with: context)159 XCTAssertEqual(rendered, expected, "Empty lists should behave like falsey values.")160 }161 func testDoubled() throws {162 let templateString = "{{#bool}}\n* first\n{{/bool}}\n* {{two}}\n{{#bool}}\n* third\n{{/bool}}\n"163 let contextJSON = "{\"two\":\"second\",\"bool\":true}".data(using: .utf8)!164 let expected = "* first\n* second\n* third\n"165 let context = try JSONDecoder().decode(Context.self, from: contextJSON)166 let template = try Template(templateString)167 let rendered = template.render(with: context)168 XCTAssertEqual(rendered, expected, "Multiple sections per template should be permitted.")169 }170 func testNested_Truthy() throws {171 let templateString = "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |"172 let contextJSON = "{\"bool\":true}".data(using: .utf8)!173 let expected = "| A B C D E |"174 let context = try JSONDecoder().decode(Context.self, from: contextJSON)175 let template = try Template(templateString)176 let rendered = template.render(with: context)177 XCTAssertEqual(rendered, expected, "Nested truthy sections should have their contents rendered.")178 }179 func testNested_Falsey() throws {180 let templateString = "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |"181 let contextJSON = "{\"bool\":false}".data(using: .utf8)!182 let expected = "| A E |"183 let context = try JSONDecoder().decode(Context.self, from: contextJSON)184 let template = try Template(templateString)185 let rendered = template.render(with: context)186 XCTAssertEqual(rendered, expected, "Nested falsey sections should be omitted.")187 }188 func testContextMisses() throws {189 let templateString = "[{{#missing}}Found key 'missing'!{{/missing}}]"190 let contextJSON = "{}".data(using: .utf8)!191 let expected = "[]"192 let context = try JSONDecoder().decode(Context.self, from: contextJSON)193 let template = try Template(templateString)194 let rendered = template.render(with: context)195 XCTAssertEqual(rendered, expected, "Failed context lookups should be considered falsey.")196 }197 func testImplicitIterator_String() throws {198 let templateString = "\"{{#list}}({{.}}){{/list}}\""199 let contextJSON = "{\"list\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}".data(using: .utf8)!200 let expected = "\"(a)(b)(c)(d)(e)\""201 let context = try JSONDecoder().decode(Context.self, from: contextJSON)202 let template = try Template(templateString)203 let rendered = template.render(with: context)204 XCTAssertEqual(rendered, expected, "Implicit iterators should directly interpolate strings.")205 }206 func testImplicitIterator_Integer() throws {207 let templateString = "\"{{#list}}({{.}}){{/list}}\""208 let contextJSON = "{\"list\":[1,2,3,4,5]}".data(using: .utf8)!209 let expected = "\"(1)(2)(3)(4)(5)\""210 let context = try JSONDecoder().decode(Context.self, from: contextJSON)211 let template = try Template(templateString)212 let rendered = template.render(with: context)213 XCTAssertEqual(rendered, expected, "Implicit iterators should cast integers to strings and interpolate.")214 }215 func testImplicitIterator_Decimal() throws {216 let templateString = "\"{{#list}}({{.}}){{/list}}\""217 let contextJSON = "{\"list\":[1.1000000000000001,2.2000000000000002,3.2999999999999998,4.4000000000000004,5.5]}".data(using: .utf8)!218 let expected = "\"(1.1)(2.2)(3.3)(4.4)(5.5)\""219 let context = try JSONDecoder().decode(Context.self, from: contextJSON)220 let template = try Template(templateString)221 let rendered = template.render(with: context)222 XCTAssertEqual(rendered, expected, "Implicit iterators should cast decimals to strings and interpolate.")223 }224 func testImplicitIterator_Array() throws {225 let templateString = "\"{{#list}}({{#.}}{{.}}{{/.}}){{/list}}\""226 let contextJSON = "{\"list\":[[1,2,3],[\"a\",\"b\",\"c\"]]}".data(using: .utf8)!227 let expected = "\"(123)(abc)\""228 let context = try JSONDecoder().decode(Context.self, from: contextJSON)229 let template = try Template(templateString)230 let rendered = template.render(with: context)231 XCTAssertEqual(rendered, expected, "Implicit iterators should allow iterating over nested arrays.")232 }233 func testDottedNames_Truthy() throws {234 let templateString = "\"{{#a.b.c}}Here{{/a.b.c}}\" == \"Here\""235 let contextJSON = "{\"a\":{\"b\":{\"c\":true}}}".data(using: .utf8)!236 let expected = "\"Here\" == \"Here\""237 let context = try JSONDecoder().decode(Context.self, from: contextJSON)238 let template = try Template(templateString)239 let rendered = template.render(with: context)240 XCTAssertEqual(rendered, expected, "Dotted names should be valid for Section tags.")241 }242 func testDottedNames_Falsey() throws {243 let templateString = "\"{{#a.b.c}}Here{{/a.b.c}}\" == \"\""244 let contextJSON = "{\"a\":{\"b\":{\"c\":false}}}".data(using: .utf8)!245 let expected = "\"\" == \"\""246 let context = try JSONDecoder().decode(Context.self, from: contextJSON)247 let template = try Template(templateString)248 let rendered = template.render(with: context)249 XCTAssertEqual(rendered, expected, "Dotted names should be valid for Section tags.")250 }251 func testDottedNames_BrokenChains() throws {252 let templateString = "\"{{#a.b.c}}Here{{/a.b.c}}\" == \"\""253 let contextJSON = "{\"a\":{}}".data(using: .utf8)!254 let expected = "\"\" == \"\""255 let context = try JSONDecoder().decode(Context.self, from: contextJSON)256 let template = try Template(templateString)257 let rendered = template.render(with: context)258 XCTAssertEqual(rendered, expected, "Dotted names that cannot be resolved should be considered falsey.")259 }260 func testSurroundingWhitespace() throws {261 let templateString = " | {{#boolean}}\t|\t{{/boolean}} | \n"262 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!263 let expected = " | \t|\t | \n"264 let context = try JSONDecoder().decode(Context.self, from: contextJSON)265 let template = try Template(templateString)266 let rendered = template.render(with: context)267 XCTAssertEqual(rendered, expected, "Sections should not alter surrounding whitespace.")268 }269 func testInternalWhitespace() throws {270 let templateString = " | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n"271 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!272 let expected = " | \n | \n"273 let context = try JSONDecoder().decode(Context.self, from: contextJSON)274 let template = try Template(templateString)275 let rendered = template.render(with: context)276 XCTAssertEqual(rendered, expected, "Sections should not alter internal whitespace.")277 }278 func testIndentedInlineSections() throws {279 let templateString = " {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n"280 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!281 let expected = " YES\n GOOD\n"282 let context = try JSONDecoder().decode(Context.self, from: contextJSON)283 let template = try Template(templateString)284 let rendered = template.render(with: context)285 XCTAssertEqual(rendered, expected, "Single-line sections should not alter surrounding whitespace.")286 }287 func testStandaloneLines() throws {288 let templateString = "| This Is\n{{#boolean}}\n|\n{{/boolean}}\n| A Line\n"289 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!290 let expected = "| This Is\n|\n| A Line\n"291 let context = try JSONDecoder().decode(Context.self, from: contextJSON)292 let template = try Template(templateString)293 let rendered = template.render(with: context)294 XCTAssertEqual(rendered, expected, "Standalone lines should be removed from the template.")295 }296 func testIndentedStandaloneLines() throws {297 let templateString = "| This Is\n {{#boolean}}\n|\n {{/boolean}}\n| A Line\n"298 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!299 let expected = "| This Is\n|\n| A Line\n"300 let context = try JSONDecoder().decode(Context.self, from: contextJSON)301 let template = try Template(templateString)302 let rendered = template.render(with: context)303 XCTAssertEqual(rendered, expected, "Indented standalone lines should be removed from the template.")304 }305 func testStandaloneLineEndings() throws {306 let templateString = "|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|"307 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!308 let expected = "|\r\n|"309 let context = try JSONDecoder().decode(Context.self, from: contextJSON)310 let template = try Template(templateString)311 let rendered = template.render(with: context)312 XCTAssertEqual(rendered, expected, "\"\r\n\" should be considered a newline for standalone tags.")313 }314 func testStandaloneWithoutPreviousLine() throws {315 let templateString = " {{#boolean}}\n#{{/boolean}}\n/"316 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!317 let expected = "#\n/"318 let context = try JSONDecoder().decode(Context.self, from: contextJSON)319 let template = try Template(templateString)320 let rendered = template.render(with: context)321 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to precede them.")322 }323 func testStandaloneWithoutNewline() throws {324 let templateString = "#{{#boolean}}\n/\n {{/boolean}}"325 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!326 let expected = "#\n/\n"327 let context = try JSONDecoder().decode(Context.self, from: contextJSON)328 let template = try Template(templateString)329 let rendered = template.render(with: context)330 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to follow them.")331 }332 func testPadding() throws {333 let templateString = "|{{# boolean }}={{/ boolean }}|"334 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!335 let expected = "|=|"336 let context = try JSONDecoder().decode(Context.self, from: contextJSON)337 let template = try Template(templateString)338 let rendered = template.render(with: context)339 XCTAssertEqual(rendered, expected, "Superfluous in-tag whitespace should be ignored.")340 }341}342/**343Interpolation tags are used to integrate dynamic content into the template.344The tag's content MUST be a non-whitespace character sequence NOT containing345the current closing delimiter.346This tag's content names the data to replace the tag. A single period (`.`)347indicates that the item currently sitting atop the context stack should be348used; otherwise, name resolution is as follows:349 1) Split the name on periods; the first part is the name to resolve, any350 remaining parts should be retained.351 2) Walk the context stack from top to bottom, finding the first context352 that is a) a hash containing the name as a key OR b) an object responding353 to a method with the given name.354 3) If the context is a hash, the data is the value associated with the355 name.356 4) If the context is an object, the data is the value returned by the357 method with the given name.358 5) If any name parts were retained in step 1, each should be resolved359 against a context stack containing only the result from the former360 resolution. If any part fails resolution, the result should be considered361 falsey, and should interpolate as the empty string.362Data should be coerced into a string (and escaped, if appropriate) before363interpolation.364The Interpolation tags MUST NOT be treated as standalone.365 */366final class InterpolationTests: XCTestCase {367 static var allTests: [(String, (InterpolationTests) -> () throws -> Void)] {368 return [369 ("testNoInterpolation", testNoInterpolation),370 ("testBasicInterpolation", testBasicInterpolation),371 ("testHTMLEscaping", testHTMLEscaping),372 ("testTripleMustache", testTripleMustache),373 ("testAmpersand", testAmpersand),374 ("testBasicIntegerInterpolation", testBasicIntegerInterpolation),375 ("testTripleMustacheIntegerInterpolation", testTripleMustacheIntegerInterpolation),376 ("testAmpersandIntegerInterpolation", testAmpersandIntegerInterpolation),377 ("testBasicDecimalInterpolation", testBasicDecimalInterpolation),378 ("testTripleMustacheDecimalInterpolation", testTripleMustacheDecimalInterpolation),379 ("testAmpersandDecimalInterpolation", testAmpersandDecimalInterpolation),380 ("testBasicNullInterpolation", testBasicNullInterpolation),381 ("testTripleMustacheNullInterpolation", testTripleMustacheNullInterpolation),382 ("testAmpersandNullInterpolation", testAmpersandNullInterpolation),383 ("testBasicContextMissInterpolation", testBasicContextMissInterpolation),384 ("testTripleMustacheContextMissInterpolation", testTripleMustacheContextMissInterpolation),385 ("testAmpersandContextMissInterpolation", testAmpersandContextMissInterpolation),386 ("testDottedNames_BasicInterpolation", testDottedNames_BasicInterpolation),387 ("testDottedNames_TripleMustacheInterpolation", testDottedNames_TripleMustacheInterpolation),388 ("testDottedNames_AmpersandInterpolation", testDottedNames_AmpersandInterpolation),389 ("testDottedNames_ArbitraryDepth", testDottedNames_ArbitraryDepth),390 ("testDottedNames_BrokenChains", testDottedNames_BrokenChains),391 ("testDottedNames_BrokenChainResolution", testDottedNames_BrokenChainResolution),392 ("testDottedNames_InitialResolution", testDottedNames_InitialResolution),393 ("testDottedNames_ContextPrecedence", testDottedNames_ContextPrecedence),394 ("testImplicitIterators_BasicInterpolation", testImplicitIterators_BasicInterpolation),395 ("testImplicitIterators_HTMLEscaping", testImplicitIterators_HTMLEscaping),396 ("testImplicitIterators_TripleMustache", testImplicitIterators_TripleMustache),397 ("testImplicitIterators_Ampersand", testImplicitIterators_Ampersand),398 ("testImplicitIterators_BasicIntegerInterpolation", testImplicitIterators_BasicIntegerInterpolation),399 ("testInterpolation_SurroundingWhitespace", testInterpolation_SurroundingWhitespace),400 ("testTripleMustache_SurroundingWhitespace", testTripleMustache_SurroundingWhitespace),401 ("testAmpersand_SurroundingWhitespace", testAmpersand_SurroundingWhitespace),402 ("testInterpolation_Standalone", testInterpolation_Standalone),403 ("testTripleMustache_Standalone", testTripleMustache_Standalone),404 ("testAmpersand_Standalone", testAmpersand_Standalone),405 ("testInterpolationWithPadding", testInterpolationWithPadding),406 ("testTripleMustacheWithPadding", testTripleMustacheWithPadding),407 ("testAmpersandWithPadding", testAmpersandWithPadding),408 ]409 }410 func testNoInterpolation() throws {411 let templateString = "Hello from {Mustache}!\n"412 let contextJSON = "{}".data(using: .utf8)!413 let expected = "Hello from {Mustache}!\n"414 let context = try JSONDecoder().decode(Context.self, from: contextJSON)415 let template = try Template(templateString)416 let rendered = template.render(with: context)417 XCTAssertEqual(rendered, expected, "Mustache-free templates should render as-is.")418 }419 func testBasicInterpolation() throws {420 let templateString = "Hello, {{subject}}!\n"421 let contextJSON = "{\"subject\":\"world\"}".data(using: .utf8)!422 let expected = "Hello, world!\n"423 let context = try JSONDecoder().decode(Context.self, from: contextJSON)424 let template = try Template(templateString)425 let rendered = template.render(with: context)426 XCTAssertEqual(rendered, expected, "Unadorned tags should interpolate content into the template.")427 }428 func testHTMLEscaping() throws {429 let templateString = "These characters should be HTML escaped: {{forbidden}}\n"430 let contextJSON = "{\"forbidden\":\"& \\\" < >\"}".data(using: .utf8)!431 let expected = "These characters should be HTML escaped: &amp; &quot; &lt; &gt;\n"432 let context = try JSONDecoder().decode(Context.self, from: contextJSON)433 let template = try Template(templateString)434 let rendered = template.render(with: context)435 XCTAssertEqual(rendered, expected, "Basic interpolation should be HTML escaped.")436 }437 func testTripleMustache() throws {438 let templateString = "These characters should not be HTML escaped: {{{forbidden}}}\n"439 let contextJSON = "{\"forbidden\":\"& \\\" < >\"}".data(using: .utf8)!440 let expected = "These characters should not be HTML escaped: & \" < >\n"441 let context = try JSONDecoder().decode(Context.self, from: contextJSON)442 let template = try Template(templateString)443 let rendered = template.render(with: context)444 XCTAssertEqual(rendered, expected, "Triple mustaches should interpolate without HTML escaping.")445 }446 func testAmpersand() throws {447 let templateString = "These characters should not be HTML escaped: {{&forbidden}}\n"448 let contextJSON = "{\"forbidden\":\"& \\\" < >\"}".data(using: .utf8)!449 let expected = "These characters should not be HTML escaped: & \" < >\n"450 let context = try JSONDecoder().decode(Context.self, from: contextJSON)451 let template = try Template(templateString)452 let rendered = template.render(with: context)453 XCTAssertEqual(rendered, expected, "Ampersand should interpolate without HTML escaping.")454 }455 func testBasicIntegerInterpolation() throws {456 let templateString = "\"{{mph}} miles an hour!\""457 let contextJSON = "{\"mph\":85}".data(using: .utf8)!458 let expected = "\"85 miles an hour!\""459 let context = try JSONDecoder().decode(Context.self, from: contextJSON)460 let template = try Template(templateString)461 let rendered = template.render(with: context)462 XCTAssertEqual(rendered, expected, "Integers should interpolate seamlessly.")463 }464 func testTripleMustacheIntegerInterpolation() throws {465 let templateString = "\"{{{mph}}} miles an hour!\""466 let contextJSON = "{\"mph\":85}".data(using: .utf8)!467 let expected = "\"85 miles an hour!\""468 let context = try JSONDecoder().decode(Context.self, from: contextJSON)469 let template = try Template(templateString)470 let rendered = template.render(with: context)471 XCTAssertEqual(rendered, expected, "Integers should interpolate seamlessly.")472 }473 func testAmpersandIntegerInterpolation() throws {474 let templateString = "\"{{&mph}} miles an hour!\""475 let contextJSON = "{\"mph\":85}".data(using: .utf8)!476 let expected = "\"85 miles an hour!\""477 let context = try JSONDecoder().decode(Context.self, from: contextJSON)478 let template = try Template(templateString)479 let rendered = template.render(with: context)480 XCTAssertEqual(rendered, expected, "Integers should interpolate seamlessly.")481 }482 func testBasicDecimalInterpolation() throws {483 let templateString = "\"{{power}} jiggawatts!\""484 let contextJSON = "{\"power\":1.21}".data(using: .utf8)!485 let expected = "\"1.21 jiggawatts!\""486 let context = try JSONDecoder().decode(Context.self, from: contextJSON)487 let template = try Template(templateString)488 let rendered = template.render(with: context)489 XCTAssertEqual(rendered, expected, "Decimals should interpolate seamlessly with proper significance.")490 }491 func testTripleMustacheDecimalInterpolation() throws {492 let templateString = "\"{{{power}}} jiggawatts!\""493 let contextJSON = "{\"power\":1.21}".data(using: .utf8)!494 let expected = "\"1.21 jiggawatts!\""495 let context = try JSONDecoder().decode(Context.self, from: contextJSON)496 let template = try Template(templateString)497 let rendered = template.render(with: context)498 XCTAssertEqual(rendered, expected, "Decimals should interpolate seamlessly with proper significance.")499 }500 func testAmpersandDecimalInterpolation() throws {501 let templateString = "\"{{&power}} jiggawatts!\""502 let contextJSON = "{\"power\":1.21}".data(using: .utf8)!503 let expected = "\"1.21 jiggawatts!\""504 let context = try JSONDecoder().decode(Context.self, from: contextJSON)505 let template = try Template(templateString)506 let rendered = template.render(with: context)507 XCTAssertEqual(rendered, expected, "Decimals should interpolate seamlessly with proper significance.")508 }509 func testBasicNullInterpolation() throws {510 let templateString = "I ({{cannot}}) be seen!"511 let contextJSON = "{\"cannot\":null}".data(using: .utf8)!512 let expected = "I () be seen!"513 let context = try JSONDecoder().decode(Context.self, from: contextJSON)514 let template = try Template(templateString)515 let rendered = template.render(with: context)516 XCTAssertEqual(rendered, expected, "Nulls should interpolate as the empty string.")517 }518 func testTripleMustacheNullInterpolation() throws {519 let templateString = "I ({{{cannot}}}) be seen!"520 let contextJSON = "{\"cannot\":null}".data(using: .utf8)!521 let expected = "I () be seen!"522 let context = try JSONDecoder().decode(Context.self, from: contextJSON)523 let template = try Template(templateString)524 let rendered = template.render(with: context)525 XCTAssertEqual(rendered, expected, "Nulls should interpolate as the empty string.")526 }527 func testAmpersandNullInterpolation() throws {528 let templateString = "I ({{&cannot}}) be seen!"529 let contextJSON = "{\"cannot\":null}".data(using: .utf8)!530 let expected = "I () be seen!"531 let context = try JSONDecoder().decode(Context.self, from: contextJSON)532 let template = try Template(templateString)533 let rendered = template.render(with: context)534 XCTAssertEqual(rendered, expected, "Nulls should interpolate as the empty string.")535 }536 func testBasicContextMissInterpolation() throws {537 let templateString = "I ({{cannot}}) be seen!"538 let contextJSON = "{}".data(using: .utf8)!539 let expected = "I () be seen!"540 let context = try JSONDecoder().decode(Context.self, from: contextJSON)541 let template = try Template(templateString)542 let rendered = template.render(with: context)543 XCTAssertEqual(rendered, expected, "Failed context lookups should default to empty strings.")544 }545 func testTripleMustacheContextMissInterpolation() throws {546 let templateString = "I ({{{cannot}}}) be seen!"547 let contextJSON = "{}".data(using: .utf8)!548 let expected = "I () be seen!"549 let context = try JSONDecoder().decode(Context.self, from: contextJSON)550 let template = try Template(templateString)551 let rendered = template.render(with: context)552 XCTAssertEqual(rendered, expected, "Failed context lookups should default to empty strings.")553 }554 func testAmpersandContextMissInterpolation() throws {555 let templateString = "I ({{&cannot}}) be seen!"556 let contextJSON = "{}".data(using: .utf8)!557 let expected = "I () be seen!"558 let context = try JSONDecoder().decode(Context.self, from: contextJSON)559 let template = try Template(templateString)560 let rendered = template.render(with: context)561 XCTAssertEqual(rendered, expected, "Failed context lookups should default to empty strings.")562 }563 func testDottedNames_BasicInterpolation() throws {564 let templateString = "\"{{person.name}}\" == \"{{#person}}{{name}}{{/person}}\""565 let contextJSON = "{\"person\":{\"name\":\"Joe\"}}".data(using: .utf8)!566 let expected = "\"Joe\" == \"Joe\""567 let context = try JSONDecoder().decode(Context.self, from: contextJSON)568 let template = try Template(templateString)569 let rendered = template.render(with: context)570 XCTAssertEqual(rendered, expected, "Dotted names should be considered a form of shorthand for sections.")571 }572 func testDottedNames_TripleMustacheInterpolation() throws {573 let templateString = "\"{{{person.name}}}\" == \"{{#person}}{{{name}}}{{/person}}\""574 let contextJSON = "{\"person\":{\"name\":\"Joe\"}}".data(using: .utf8)!575 let expected = "\"Joe\" == \"Joe\""576 let context = try JSONDecoder().decode(Context.self, from: contextJSON)577 let template = try Template(templateString)578 let rendered = template.render(with: context)579 XCTAssertEqual(rendered, expected, "Dotted names should be considered a form of shorthand for sections.")580 }581 func testDottedNames_AmpersandInterpolation() throws {582 let templateString = "\"{{&person.name}}\" == \"{{#person}}{{&name}}{{/person}}\""583 let contextJSON = "{\"person\":{\"name\":\"Joe\"}}".data(using: .utf8)!584 let expected = "\"Joe\" == \"Joe\""585 let context = try JSONDecoder().decode(Context.self, from: contextJSON)586 let template = try Template(templateString)587 let rendered = template.render(with: context)588 XCTAssertEqual(rendered, expected, "Dotted names should be considered a form of shorthand for sections.")589 }590 func testDottedNames_ArbitraryDepth() throws {591 let templateString = "\"{{a.b.c.d.e.name}}\" == \"Phil\""592 let contextJSON = "{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"name\":\"Phil\"}}}}}}".data(using: .utf8)!593 let expected = "\"Phil\" == \"Phil\""594 let context = try JSONDecoder().decode(Context.self, from: contextJSON)595 let template = try Template(templateString)596 let rendered = template.render(with: context)597 XCTAssertEqual(rendered, expected, "Dotted names should be functional to any level of nesting.")598 }599 func testDottedNames_BrokenChains() throws {600 let templateString = "\"{{a.b.c}}\" == \"\""601 let contextJSON = "{\"a\":{}}".data(using: .utf8)!602 let expected = "\"\" == \"\""603 let context = try JSONDecoder().decode(Context.self, from: contextJSON)604 let template = try Template(templateString)605 let rendered = template.render(with: context)606 XCTAssertEqual(rendered, expected, "Any falsey value prior to the last part of the name should yield ''.")607 }608 func testDottedNames_BrokenChainResolution() throws {609 let templateString = "\"{{a.b.c.name}}\" == \"\""610 let contextJSON = "{\"a\":{\"b\":{}},\"c\":{\"name\":\"Jim\"}}".data(using: .utf8)!611 let expected = "\"\" == \"\""612 let context = try JSONDecoder().decode(Context.self, from: contextJSON)613 let template = try Template(templateString)614 let rendered = template.render(with: context)615 XCTAssertEqual(rendered, expected, "Each part of a dotted name should resolve only against its parent.")616 }617 func testDottedNames_InitialResolution() throws {618 let templateString = "\"{{#a}}{{b.c.d.e.name}}{{/a}}\" == \"Phil\""619 let contextJSON = "{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"name\":\"Phil\"}}}}},\"b\":{\"c\":{\"d\":{\"e\":{\"name\":\"Wrong\"}}}}}".data(using: .utf8)!620 let expected = "\"Phil\" == \"Phil\""621 let context = try JSONDecoder().decode(Context.self, from: contextJSON)622 let template = try Template(templateString)623 let rendered = template.render(with: context)624 XCTAssertEqual(rendered, expected, "The first part of a dotted name should resolve as any other name.")625 }626 func testDottedNames_ContextPrecedence() throws {627 let templateString = "{{#a}}{{b.c}}{{/a}}"628 let contextJSON = "{\"b\":{\"c\":\"ERROR\"},\"a\":{\"b\":{}}}".data(using: .utf8)!629 let expected = ""630 let context = try JSONDecoder().decode(Context.self, from: contextJSON)631 let template = try Template(templateString)632 let rendered = template.render(with: context)633 XCTAssertEqual(rendered, expected, "Dotted names should be resolved against former resolutions.")634 }635 func testImplicitIterators_BasicInterpolation() throws {636 let templateString = "Hello, {{.}}!\n"637 let contextJSON = "\"world\"".data(using: .utf8)!638 let expected = "Hello, world!\n"639 let context = try JSONDecoder().decode(Context.self, from: contextJSON)640 let template = try Template(templateString)641 let rendered = template.render(with: context)642 XCTAssertEqual(rendered, expected, "Unadorned tags should interpolate content into the template.")643 }644 func testImplicitIterators_HTMLEscaping() throws {645 let templateString = "These characters should be HTML escaped: {{.}}\n"646 let contextJSON = "\"& \\\" < >\"".data(using: .utf8)!647 let expected = "These characters should be HTML escaped: &amp; &quot; &lt; &gt;\n"648 let context = try JSONDecoder().decode(Context.self, from: contextJSON)649 let template = try Template(templateString)650 let rendered = template.render(with: context)651 XCTAssertEqual(rendered, expected, "Basic interpolation should be HTML escaped.")652 }653 func testImplicitIterators_TripleMustache() throws {654 let templateString = "These characters should not be HTML escaped: {{{.}}}\n"655 let contextJSON = "\"& \\\" < >\"".data(using: .utf8)!656 let expected = "These characters should not be HTML escaped: & \" < >\n"657 let context = try JSONDecoder().decode(Context.self, from: contextJSON)658 let template = try Template(templateString)659 let rendered = template.render(with: context)660 XCTAssertEqual(rendered, expected, "Triple mustaches should interpolate without HTML escaping.")661 }662 func testImplicitIterators_Ampersand() throws {663 let templateString = "These characters should not be HTML escaped: {{&.}}\n"664 let contextJSON = "\"& \\\" < >\"".data(using: .utf8)!665 let expected = "These characters should not be HTML escaped: & \" < >\n"666 let context = try JSONDecoder().decode(Context.self, from: contextJSON)667 let template = try Template(templateString)668 let rendered = template.render(with: context)669 XCTAssertEqual(rendered, expected, "Ampersand should interpolate without HTML escaping.")670 }671 func testImplicitIterators_BasicIntegerInterpolation() throws {672 let templateString = "\"{{.}} miles an hour!\""673 let contextJSON = "85".data(using: .utf8)!674 let expected = "\"85 miles an hour!\""675 let context = try JSONDecoder().decode(Context.self, from: contextJSON)676 let template = try Template(templateString)677 let rendered = template.render(with: context)678 XCTAssertEqual(rendered, expected, "Integers should interpolate seamlessly.")679 }680 func testInterpolation_SurroundingWhitespace() throws {681 let templateString = "| {{string}} |"682 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!683 let expected = "| --- |"684 let context = try JSONDecoder().decode(Context.self, from: contextJSON)685 let template = try Template(templateString)686 let rendered = template.render(with: context)687 XCTAssertEqual(rendered, expected, "Interpolation should not alter surrounding whitespace.")688 }689 func testTripleMustache_SurroundingWhitespace() throws {690 let templateString = "| {{{string}}} |"691 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!692 let expected = "| --- |"693 let context = try JSONDecoder().decode(Context.self, from: contextJSON)694 let template = try Template(templateString)695 let rendered = template.render(with: context)696 XCTAssertEqual(rendered, expected, "Interpolation should not alter surrounding whitespace.")697 }698 func testAmpersand_SurroundingWhitespace() throws {699 let templateString = "| {{&string}} |"700 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!701 let expected = "| --- |"702 let context = try JSONDecoder().decode(Context.self, from: contextJSON)703 let template = try Template(templateString)704 let rendered = template.render(with: context)705 XCTAssertEqual(rendered, expected, "Interpolation should not alter surrounding whitespace.")706 }707 func testInterpolation_Standalone() throws {708 let templateString = " {{string}}\n"709 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!710 let expected = " ---\n"711 let context = try JSONDecoder().decode(Context.self, from: contextJSON)712 let template = try Template(templateString)713 let rendered = template.render(with: context)714 XCTAssertEqual(rendered, expected, "Standalone interpolation should not alter surrounding whitespace.")715 }716 func testTripleMustache_Standalone() throws {717 let templateString = " {{{string}}}\n"718 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!719 let expected = " ---\n"720 let context = try JSONDecoder().decode(Context.self, from: contextJSON)721 let template = try Template(templateString)722 let rendered = template.render(with: context)723 XCTAssertEqual(rendered, expected, "Standalone interpolation should not alter surrounding whitespace.")724 }725 func testAmpersand_Standalone() throws {726 let templateString = " {{&string}}\n"727 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!728 let expected = " ---\n"729 let context = try JSONDecoder().decode(Context.self, from: contextJSON)730 let template = try Template(templateString)731 let rendered = template.render(with: context)732 XCTAssertEqual(rendered, expected, "Standalone interpolation should not alter surrounding whitespace.")733 }734 func testInterpolationWithPadding() throws {735 let templateString = "|{{ string }}|"736 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!737 let expected = "|---|"738 let context = try JSONDecoder().decode(Context.self, from: contextJSON)739 let template = try Template(templateString)740 let rendered = template.render(with: context)741 XCTAssertEqual(rendered, expected, "Superfluous in-tag whitespace should be ignored.")742 }743 func testTripleMustacheWithPadding() throws {744 let templateString = "|{{{ string }}}|"745 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!746 let expected = "|---|"747 let context = try JSONDecoder().decode(Context.self, from: contextJSON)748 let template = try Template(templateString)749 let rendered = template.render(with: context)750 XCTAssertEqual(rendered, expected, "Superfluous in-tag whitespace should be ignored.")751 }752 func testAmpersandWithPadding() throws {753 let templateString = "|{{& string }}|"754 let contextJSON = "{\"string\":\"---\"}".data(using: .utf8)!755 let expected = "|---|"756 let context = try JSONDecoder().decode(Context.self, from: contextJSON)757 let template = try Template(templateString)758 let rendered = template.render(with: context)759 XCTAssertEqual(rendered, expected, "Superfluous in-tag whitespace should be ignored.")760 }761}762/**763Inverted Section tags and End Section tags are used in combination to wrap a764section of the template.765These tags' content MUST be a non-whitespace character sequence NOT766containing the current closing delimiter; each Inverted Section tag MUST be767followed by an End Section tag with the same content within the same768section.769This tag's content names the data to replace the tag. Name resolution is as770follows:771 1) Split the name on periods; the first part is the name to resolve, any772 remaining parts should be retained.773 2) Walk the context stack from top to bottom, finding the first context774 that is a) a hash containing the name as a key OR b) an object responding775 to a method with the given name.776 3) If the context is a hash, the data is the value associated with the777 name.778 4) If the context is an object and the method with the given name has an779 arity of 1, the method SHOULD be called with a String containing the780 unprocessed contents of the sections; the data is the value returned.781 5) Otherwise, the data is the value returned by calling the method with782 the given name.783 6) If any name parts were retained in step 1, each should be resolved784 against a context stack containing only the result from the former785 resolution. If any part fails resolution, the result should be considered786 falsey, and should interpolate as the empty string.787If the data is not of a list type, it is coerced into a list as follows: if788the data is truthy (e.g. `!!data == true`), use a single-element list789containing the data, otherwise use an empty list.790This section MUST NOT be rendered unless the data list is empty.791Inverted Section and End Section tags SHOULD be treated as standalone when792appropriate.793 */794final class InvertedTests: XCTestCase {795 static var allTests: [(String, (InvertedTests) -> () throws -> Void)] {796 return [797 ("testFalsey", testFalsey),798 ("testTruthy", testTruthy),799 ("testNullisfalsey", testNullisfalsey),800 ("testContext", testContext),801 ("testList", testList),802 ("testEmptyList", testEmptyList),803 ("testDoubled", testDoubled),804 ("testNested_Falsey", testNested_Falsey),805 ("testNested_Truthy", testNested_Truthy),806 ("testContextMisses", testContextMisses),807 ("testDottedNames_Truthy", testDottedNames_Truthy),808 ("testDottedNames_Falsey", testDottedNames_Falsey),809 ("testDottedNames_BrokenChains", testDottedNames_BrokenChains),810 ("testSurroundingWhitespace", testSurroundingWhitespace),811 ("testInternalWhitespace", testInternalWhitespace),812 ("testIndentedInlineSections", testIndentedInlineSections),813 ("testStandaloneLines", testStandaloneLines),814 ("testStandaloneIndentedLines", testStandaloneIndentedLines),815 ("testStandaloneLineEndings", testStandaloneLineEndings),816 ("testStandaloneWithoutPreviousLine", testStandaloneWithoutPreviousLine),817 ("testStandaloneWithoutNewline", testStandaloneWithoutNewline),818 ("testPadding", testPadding),819 ]820 }821 func testFalsey() throws {822 let templateString = "\"{{^boolean}}This should be rendered.{{/boolean}}\""823 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!824 let expected = "\"This should be rendered.\""825 let context = try JSONDecoder().decode(Context.self, from: contextJSON)826 let template = try Template(templateString)827 let rendered = template.render(with: context)828 XCTAssertEqual(rendered, expected, "Falsey sections should have their contents rendered.")829 }830 func testTruthy() throws {831 let templateString = "\"{{^boolean}}This should not be rendered.{{/boolean}}\""832 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!833 let expected = "\"\""834 let context = try JSONDecoder().decode(Context.self, from: contextJSON)835 let template = try Template(templateString)836 let rendered = template.render(with: context)837 XCTAssertEqual(rendered, expected, "Truthy sections should have their contents omitted.")838 }839 func testNullisfalsey() throws {840 let templateString = "\"{{^null}}This should be rendered.{{/null}}\""841 let contextJSON = "{\"null\":null}".data(using: .utf8)!842 let expected = "\"This should be rendered.\""843 let context = try JSONDecoder().decode(Context.self, from: contextJSON)844 let template = try Template(templateString)845 let rendered = template.render(with: context)846 XCTAssertEqual(rendered, expected, "Null is falsey.")847 }848 func testContext() throws {849 let templateString = "\"{{^context}}Hi {{name}}.{{/context}}\""850 let contextJSON = "{\"context\":{\"name\":\"Joe\"}}".data(using: .utf8)!851 let expected = "\"\""852 let context = try JSONDecoder().decode(Context.self, from: contextJSON)853 let template = try Template(templateString)854 let rendered = template.render(with: context)855 XCTAssertEqual(rendered, expected, "Objects and hashes should behave like truthy values.")856 }857 func testList() throws {858 let templateString = "\"{{^list}}{{n}}{{/list}}\""859 let contextJSON = "{\"list\":[{\"n\":1},{\"n\":2},{\"n\":3}]}".data(using: .utf8)!860 let expected = "\"\""861 let context = try JSONDecoder().decode(Context.self, from: contextJSON)862 let template = try Template(templateString)863 let rendered = template.render(with: context)864 XCTAssertEqual(rendered, expected, "Lists should behave like truthy values.")865 }866 func testEmptyList() throws {867 let templateString = "\"{{^list}}Yay lists!{{/list}}\""868 let contextJSON = "{\"list\":[]}".data(using: .utf8)!869 let expected = "\"Yay lists!\""870 let context = try JSONDecoder().decode(Context.self, from: contextJSON)871 let template = try Template(templateString)872 let rendered = template.render(with: context)873 XCTAssertEqual(rendered, expected, "Empty lists should behave like falsey values.")874 }875 func testDoubled() throws {876 let templateString = "{{^bool}}\n* first\n{{/bool}}\n* {{two}}\n{{^bool}}\n* third\n{{/bool}}\n"877 let contextJSON = "{\"two\":\"second\",\"bool\":false}".data(using: .utf8)!878 let expected = "* first\n* second\n* third\n"879 let context = try JSONDecoder().decode(Context.self, from: contextJSON)880 let template = try Template(templateString)881 let rendered = template.render(with: context)882 XCTAssertEqual(rendered, expected, "Multiple inverted sections per template should be permitted.")883 }884 func testNested_Falsey() throws {885 let templateString = "| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |"886 let contextJSON = "{\"bool\":false}".data(using: .utf8)!887 let expected = "| A B C D E |"888 let context = try JSONDecoder().decode(Context.self, from: contextJSON)889 let template = try Template(templateString)890 let rendered = template.render(with: context)891 XCTAssertEqual(rendered, expected, "Nested falsey sections should have their contents rendered.")892 }893 func testNested_Truthy() throws {894 let templateString = "| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |"895 let contextJSON = "{\"bool\":true}".data(using: .utf8)!896 let expected = "| A E |"897 let context = try JSONDecoder().decode(Context.self, from: contextJSON)898 let template = try Template(templateString)899 let rendered = template.render(with: context)900 XCTAssertEqual(rendered, expected, "Nested truthy sections should be omitted.")901 }902 func testContextMisses() throws {903 let templateString = "[{{^missing}}Cannot find key 'missing'!{{/missing}}]"904 let contextJSON = "{}".data(using: .utf8)!905 let expected = "[Cannot find key 'missing'!]"906 let context = try JSONDecoder().decode(Context.self, from: contextJSON)907 let template = try Template(templateString)908 let rendered = template.render(with: context)909 XCTAssertEqual(rendered, expected, "Failed context lookups should be considered falsey.")910 }911 func testDottedNames_Truthy() throws {912 let templateString = "\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"\""913 let contextJSON = "{\"a\":{\"b\":{\"c\":true}}}".data(using: .utf8)!914 let expected = "\"\" == \"\""915 let context = try JSONDecoder().decode(Context.self, from: contextJSON)916 let template = try Template(templateString)917 let rendered = template.render(with: context)918 XCTAssertEqual(rendered, expected, "Dotted names should be valid for Inverted Section tags.")919 }920 func testDottedNames_Falsey() throws {921 let templateString = "\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"Not Here\""922 let contextJSON = "{\"a\":{\"b\":{\"c\":false}}}".data(using: .utf8)!923 let expected = "\"Not Here\" == \"Not Here\""924 let context = try JSONDecoder().decode(Context.self, from: contextJSON)925 let template = try Template(templateString)926 let rendered = template.render(with: context)927 XCTAssertEqual(rendered, expected, "Dotted names should be valid for Inverted Section tags.")928 }929 func testDottedNames_BrokenChains() throws {930 let templateString = "\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"Not Here\""931 let contextJSON = "{\"a\":{}}".data(using: .utf8)!932 let expected = "\"Not Here\" == \"Not Here\""933 let context = try JSONDecoder().decode(Context.self, from: contextJSON)934 let template = try Template(templateString)935 let rendered = template.render(with: context)936 XCTAssertEqual(rendered, expected, "Dotted names that cannot be resolved should be considered falsey.")937 }938 func testSurroundingWhitespace() throws {939 let templateString = " | {{^boolean}}\t|\t{{/boolean}} | \n"940 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!941 let expected = " | \t|\t | \n"942 let context = try JSONDecoder().decode(Context.self, from: contextJSON)943 let template = try Template(templateString)944 let rendered = template.render(with: context)945 XCTAssertEqual(rendered, expected, "Inverted sections should not alter surrounding whitespace.")946 }947 func testInternalWhitespace() throws {948 let templateString = " | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n"949 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!950 let expected = " | \n | \n"951 let context = try JSONDecoder().decode(Context.self, from: contextJSON)952 let template = try Template(templateString)953 let rendered = template.render(with: context)954 XCTAssertEqual(rendered, expected, "Inverted should not alter internal whitespace.")955 }956 func testIndentedInlineSections() throws {957 let templateString = " {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n"958 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!959 let expected = " NO\n WAY\n"960 let context = try JSONDecoder().decode(Context.self, from: contextJSON)961 let template = try Template(templateString)962 let rendered = template.render(with: context)963 XCTAssertEqual(rendered, expected, "Single-line sections should not alter surrounding whitespace.")964 }965 func testStandaloneLines() throws {966 let templateString = "| This Is\n{{^boolean}}\n|\n{{/boolean}}\n| A Line\n"967 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!968 let expected = "| This Is\n|\n| A Line\n"969 let context = try JSONDecoder().decode(Context.self, from: contextJSON)970 let template = try Template(templateString)971 let rendered = template.render(with: context)972 XCTAssertEqual(rendered, expected, "Standalone lines should be removed from the template.")973 }974 func testStandaloneIndentedLines() throws {975 let templateString = "| This Is\n {{^boolean}}\n|\n {{/boolean}}\n| A Line\n"976 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!977 let expected = "| This Is\n|\n| A Line\n"978 let context = try JSONDecoder().decode(Context.self, from: contextJSON)979 let template = try Template(templateString)980 let rendered = template.render(with: context)981 XCTAssertEqual(rendered, expected, "Standalone indented lines should be removed from the template.")982 }983 func testStandaloneLineEndings() throws {984 let templateString = "|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|"985 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!986 let expected = "|\r\n|"987 let context = try JSONDecoder().decode(Context.self, from: contextJSON)988 let template = try Template(templateString)989 let rendered = template.render(with: context)990 XCTAssertEqual(rendered, expected, "\"\r\n\" should be considered a newline for standalone tags.")991 }992 func testStandaloneWithoutPreviousLine() throws {993 let templateString = " {{^boolean}}\n^{{/boolean}}\n/"994 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!995 let expected = "^\n/"996 let context = try JSONDecoder().decode(Context.self, from: contextJSON)997 let template = try Template(templateString)998 let rendered = template.render(with: context)999 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to precede them.")1000 }1001 func testStandaloneWithoutNewline() throws {1002 let templateString = "^{{^boolean}}\n/\n {{/boolean}}"1003 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!1004 let expected = "^\n/\n"1005 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1006 let template = try Template(templateString)1007 let rendered = template.render(with: context)1008 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to follow them.")1009 }1010 func testPadding() throws {1011 let templateString = "|{{^ boolean }}={{/ boolean }}|"1012 let contextJSON = "{\"boolean\":false}".data(using: .utf8)!1013 let expected = "|=|"1014 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1015 let template = try Template(templateString)1016 let rendered = template.render(with: context)1017 XCTAssertEqual(rendered, expected, "Superfluous in-tag whitespace should be ignored.")1018 }1019}1020/**1021Comment tags represent content that should never appear in the resulting1022output.1023The tag's content may contain any substring (including newlines) EXCEPT the1024closing delimiter.1025Comment tags SHOULD be treated as standalone when appropriate.1026 */1027final class CommentsTests: XCTestCase {1028 static var allTests: [(String, (CommentsTests) -> () throws -> Void)] {1029 return [1030 ("testInline", testInline),1031 ("testMultiline", testMultiline),1032 ("testStandalone", testStandalone),1033 ("testIndentedStandalone", testIndentedStandalone),1034 ("testStandaloneLineEndings", testStandaloneLineEndings),1035 ("testStandaloneWithoutPreviousLine", testStandaloneWithoutPreviousLine),1036 ("testStandaloneWithoutNewline", testStandaloneWithoutNewline),1037 ("testMultilineStandalone", testMultilineStandalone),1038 ("testIndentedMultilineStandalone", testIndentedMultilineStandalone),1039 ("testIndentedInline", testIndentedInline),1040 ("testSurroundingWhitespace", testSurroundingWhitespace),1041 ]1042 }1043 func testInline() throws {1044 let templateString = "12345{{! Comment Block! }}67890"1045 let contextJSON = "{}".data(using: .utf8)!1046 let expected = "1234567890"1047 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1048 let template = try Template(templateString)1049 let rendered = template.render(with: context)1050 XCTAssertEqual(rendered, expected, "Comment blocks should be removed from the template.")1051 }1052 func testMultiline() throws {1053 let templateString = "12345{{!\n This is a\n multi-line comment...\n}}67890\n"1054 let contextJSON = "{}".data(using: .utf8)!1055 let expected = "1234567890\n"1056 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1057 let template = try Template(templateString)1058 let rendered = template.render(with: context)1059 XCTAssertEqual(rendered, expected, "Multiline comments should be permitted.")1060 }1061 func testStandalone() throws {1062 let templateString = "Begin.\n{{! Comment Block! }}\nEnd.\n"1063 let contextJSON = "{}".data(using: .utf8)!1064 let expected = "Begin.\nEnd.\n"1065 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1066 let template = try Template(templateString)1067 let rendered = template.render(with: context)1068 XCTAssertEqual(rendered, expected, "All standalone comment lines should be removed.")1069 }1070 func testIndentedStandalone() throws {1071 let templateString = "Begin.\n {{! Indented Comment Block! }}\nEnd.\n"1072 let contextJSON = "{}".data(using: .utf8)!1073 let expected = "Begin.\nEnd.\n"1074 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1075 let template = try Template(templateString)1076 let rendered = template.render(with: context)1077 XCTAssertEqual(rendered, expected, "All standalone comment lines should be removed.")1078 }1079 func testStandaloneLineEndings() throws {1080 let templateString = "|\r\n{{! Standalone Comment }}\r\n|"1081 let contextJSON = "{}".data(using: .utf8)!1082 let expected = "|\r\n|"1083 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1084 let template = try Template(templateString)1085 let rendered = template.render(with: context)1086 XCTAssertEqual(rendered, expected, "\"\r\n\" should be considered a newline for standalone tags.")1087 }1088 func testStandaloneWithoutPreviousLine() throws {1089 let templateString = " {{! I'm Still Standalone }}\n!"1090 let contextJSON = "{}".data(using: .utf8)!1091 let expected = "!"1092 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1093 let template = try Template(templateString)1094 let rendered = template.render(with: context)1095 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to precede them.")1096 }1097 func testStandaloneWithoutNewline() throws {1098 let templateString = "!\n {{! I'm Still Standalone }}"1099 let contextJSON = "{}".data(using: .utf8)!1100 let expected = "!\n"1101 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1102 let template = try Template(templateString)1103 let rendered = template.render(with: context)1104 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to follow them.")1105 }1106 func testMultilineStandalone() throws {1107 let templateString = "Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n"1108 let contextJSON = "{}".data(using: .utf8)!1109 let expected = "Begin.\nEnd.\n"1110 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1111 let template = try Template(templateString)1112 let rendered = template.render(with: context)1113 XCTAssertEqual(rendered, expected, "All standalone comment lines should be removed.")1114 }1115 func testIndentedMultilineStandalone() throws {1116 let templateString = "Begin.\n {{!\n Something's going on here...\n }}\nEnd.\n"1117 let contextJSON = "{}".data(using: .utf8)!1118 let expected = "Begin.\nEnd.\n"1119 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1120 let template = try Template(templateString)1121 let rendered = template.render(with: context)1122 XCTAssertEqual(rendered, expected, "All standalone comment lines should be removed.")1123 }1124 func testIndentedInline() throws {1125 let templateString = " 12 {{! 34 }}\n"1126 let contextJSON = "{}".data(using: .utf8)!1127 let expected = " 12 \n"1128 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1129 let template = try Template(templateString)1130 let rendered = template.render(with: context)1131 XCTAssertEqual(rendered, expected, "Inline comments should not strip whitespace")1132 }1133 func testSurroundingWhitespace() throws {1134 let templateString = "12345 {{! Comment Block! }} 67890"1135 let contextJSON = "{}".data(using: .utf8)!1136 let expected = "12345 67890"1137 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1138 let template = try Template(templateString)1139 let rendered = template.render(with: context)1140 XCTAssertEqual(rendered, expected, "Comment removal should preserve surrounding whitespace.")1141 }1142}1143/**1144Partial tags are used to expand an external template into the current1145template.1146The tag's content MUST be a non-whitespace character sequence NOT containing1147the current closing delimiter.1148This tag's content names the partial to inject. Set Delimiter tags MUST NOT1149affect the parsing of a partial. The partial MUST be rendered against the1150context stack local to the tag. If the named partial cannot be found, the1151empty string SHOULD be used instead, as in interpolations.1152Partial tags SHOULD be treated as standalone when appropriate. If this tag1153is used standalone, any whitespace preceding the tag should treated as1154indentation, and prepended to each line of the partial before rendering.1155 */1156final class PartialsTests: XCTestCase {1157 static var allTests: [(String, (PartialsTests) -> () throws -> Void)] {1158 return [1159 ("testBasicBehavior", testBasicBehavior),1160 ("testFailedLookup", testFailedLookup),1161 ("testContext", testContext),1162 ("testRecursion", testRecursion),1163 ("testSurroundingWhitespace", testSurroundingWhitespace),1164 ("testInlineIndentation", testInlineIndentation),1165 ("testStandaloneLineEndings", testStandaloneLineEndings),1166 ("testStandaloneWithoutPreviousLine", testStandaloneWithoutPreviousLine),1167 ("testStandaloneWithoutNewline", testStandaloneWithoutNewline),1168 ("testStandaloneIndentation", testStandaloneIndentation),1169 ("testPaddingWhitespace", testPaddingWhitespace),1170 ]1171 }1172 func testBasicBehavior() throws {1173 let templateString = "\"{{>text}}\""1174 let contextJSON = "{}".data(using: .utf8)!1175 let expected = "\"from partial\""1176 let partials = try [1177 "text": Template("from partial"),1178 ]1179 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1180 let template = try Template(templateString)1181 let rendered = template.render(with: context, partials: partials)1182 XCTAssertEqual(rendered, expected, "The greater-than operator should expand to the named partial.")1183 }1184 func testFailedLookup() throws {1185 let templateString = "\"{{>text}}\""1186 let contextJSON = "{}".data(using: .utf8)!1187 let expected = "\"\""1188 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1189 let template = try Template(templateString)1190 let rendered = template.render(with: context)1191 XCTAssertEqual(rendered, expected, "The empty string should be used when the named partial is not found.")1192 }1193 func testContext() throws {1194 let templateString = "\"{{>partial}}\""1195 let contextJSON = "{\"text\":\"content\"}".data(using: .utf8)!1196 let expected = "\"*content*\""1197 let partials = try [1198 "partial": Template("*{{text}}*"),1199 ]1200 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1201 let template = try Template(templateString)1202 let rendered = template.render(with: context, partials: partials)1203 XCTAssertEqual(rendered, expected, "The greater-than operator should operate within the current context.")1204 }1205 func testRecursion() throws {1206 let templateString = "{{>node}}"1207 let contextJSON = "{\"content\":\"X\",\"nodes\":[{\"content\":\"Y\",\"nodes\":[]}]}".data(using: .utf8)!1208 let expected = "X<Y<>>"1209 let partials = try [1210 "node": Template("{{content}}<{{#nodes}}{{>node}}{{/nodes}}>"),1211 ]1212 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1213 let template = try Template(templateString)1214 let rendered = template.render(with: context, partials: partials)1215 XCTAssertEqual(rendered, expected, "The greater-than operator should properly recurse.")1216 }1217 func testSurroundingWhitespace() throws {1218 let templateString = "| {{>partial}} |"1219 let contextJSON = "{}".data(using: .utf8)!1220 let expected = "| \t|\t |"1221 let partials = try [1222 "partial": Template("\t|\t"),1223 ]1224 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1225 let template = try Template(templateString)1226 let rendered = template.render(with: context, partials: partials)1227 XCTAssertEqual(rendered, expected, "The greater-than operator should not alter surrounding whitespace.")1228 }1229 func testInlineIndentation() throws {1230 let templateString = " {{data}} {{> partial}}\n"1231 let contextJSON = "{\"data\":\"|\"}".data(using: .utf8)!1232 let expected = " | >\n>\n"1233 let partials = try [1234 "partial": Template(">\n>"),1235 ]1236 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1237 let template = try Template(templateString)1238 let rendered = template.render(with: context, partials: partials)1239 XCTAssertEqual(rendered, expected, "Whitespace should be left untouched.")1240 }1241 func testStandaloneLineEndings() throws {1242 let templateString = "|\r\n{{>partial}}\r\n|"1243 let contextJSON = "{}".data(using: .utf8)!1244 let expected = "|\r\n>|"1245 let partials = try [1246 "partial": Template(">"),1247 ]1248 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1249 let template = try Template(templateString)1250 let rendered = template.render(with: context, partials: partials)1251 XCTAssertEqual(rendered, expected, "\"\r\n\" should be considered a newline for standalone tags.")1252 }1253 func testStandaloneWithoutPreviousLine() throws {1254 let templateString = " {{>partial}}\n>"1255 let contextJSON = "{}".data(using: .utf8)!1256 let expected = " >\n >>"1257 let partials = try [1258 "partial": Template(">\n>"),1259 ]1260 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1261 let template = try Template(templateString)1262 let rendered = template.render(with: context, partials: partials)1263 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to precede them.")1264 }1265 func testStandaloneWithoutNewline() throws {1266 let templateString = ">\n {{>partial}}"1267 let contextJSON = "{}".data(using: .utf8)!1268 let expected = ">\n >\n >"1269 let partials = try [1270 "partial": Template(">\n>"),1271 ]1272 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1273 let template = try Template(templateString)1274 let rendered = template.render(with: context, partials: partials)1275 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to follow them.")1276 }1277 func testStandaloneIndentation() throws {1278 let templateString = "\\n {{>partial}}\n/\n"1279 let contextJSON = "{\"content\":\"<\n->\"}".data(using: .utf8)!1280 let expected = "\\n |\n <\n->\n |\n/\n"1281 let partials = try [1282 "partial": Template("|\n{{{content}}}\n|\n"),1283 ]1284 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1285 let template = try Template(templateString)1286 let rendered = template.render(with: context, partials: partials)1287 XCTAssertEqual(rendered, expected, "Each line of the partial should be indented before rendering.")1288 }1289 func testPaddingWhitespace() throws {1290 let templateString = "|{{> partial }}|"1291 let contextJSON = "{\"boolean\":true}".data(using: .utf8)!1292 let expected = "|[]|"1293 let partials = try [1294 "partial": Template("[]"),1295 ]1296 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1297 let template = try Template(templateString)1298 let rendered = template.render(with: context, partials: partials)1299 XCTAssertEqual(rendered, expected, "Superfluous in-tag whitespace should be ignored.")1300 }1301}1302/**1303Set Delimiter tags are used to change the tag delimiters for all content1304following the tag in the current compilation unit.1305The tag's content MUST be any two non-whitespace sequences (separated by1306whitespace) EXCEPT an equals sign ('=') followed by the current closing1307delimiter.1308Set Delimiter tags SHOULD be treated as standalone when appropriate.1309 */1310final class DelimitersTests: XCTestCase {1311 static var allTests: [(String, (DelimitersTests) -> () throws -> Void)] {1312 return [1313 ("testPairBehavior", testPairBehavior),1314 ("testSpecialCharacters", testSpecialCharacters),1315 ("testSections", testSections),1316 ("testInvertedSections", testInvertedSections),1317 ("testPartialInheritence", testPartialInheritence),1318 ("testPost_PartialBehavior", testPost_PartialBehavior),1319 ("testSurroundingWhitespace", testSurroundingWhitespace),1320 ("testOutlyingWhitespace_Inline", testOutlyingWhitespace_Inline),1321 ("testStandaloneTag", testStandaloneTag),1322 ("testIndentedStandaloneTag", testIndentedStandaloneTag),1323 ("testStandaloneLineEndings", testStandaloneLineEndings),1324 ("testStandaloneWithoutPreviousLine", testStandaloneWithoutPreviousLine),1325 ("testStandaloneWithoutNewline", testStandaloneWithoutNewline),1326 ("testPairwithPadding", testPairwithPadding),1327 ]1328 }1329 func testPairBehavior() throws {1330 let templateString = "{{=<% %>=}}(<%text%>)"1331 let contextJSON = "{\"text\":\"Hey!\"}".data(using: .utf8)!1332 let expected = "(Hey!)"1333 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1334 let template = try Template(templateString)1335 let rendered = template.render(with: context)1336 XCTAssertEqual(rendered, expected, "The equals sign (used on both sides) should permit delimiter changes.")1337 }1338 func testSpecialCharacters() throws {1339 let templateString = "({{=[ ]=}}[text])"1340 let contextJSON = "{\"text\":\"It worked!\"}".data(using: .utf8)!1341 let expected = "(It worked!)"1342 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1343 let template = try Template(templateString)1344 let rendered = template.render(with: context)1345 XCTAssertEqual(rendered, expected, "Characters with special meaning regexen should be valid delimiters.")1346 }1347 func testSections() throws {1348 let templateString = "[\n{{#section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|#section|\n {{data}}\n |data|\n|/section|\n]\n"1349 let contextJSON = "{\"section\":true,\"data\":\"I got interpolated.\"}".data(using: .utf8)!1350 let expected = "[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n"1351 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1352 let template = try Template(templateString)1353 let rendered = template.render(with: context)1354 XCTAssertEqual(rendered, expected, "Delimiters set outside sections should persist.")1355 }1356 func testInvertedSections() throws {1357 let templateString = "[\n{{^section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|^section|\n {{data}}\n |data|\n|/section|\n]\n"1358 let contextJSON = "{\"section\":false,\"data\":\"I got interpolated.\"}".data(using: .utf8)!1359 let expected = "[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n"1360 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1361 let template = try Template(templateString)1362 let rendered = template.render(with: context)1363 XCTAssertEqual(rendered, expected, "Delimiters set outside inverted sections should persist.")1364 }1365 func testPartialInheritence() throws {1366 let templateString = "[ {{>include}} ]\n{{= | | =}}\n[ |>include| ]\n"1367 let contextJSON = "{\"value\":\"yes\"}".data(using: .utf8)!1368 let expected = "[ .yes. ]\n[ .yes. ]\n"1369 let partials = try [1370 "include": Template(".{{value}}."),1371 ]1372 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1373 let template = try Template(templateString)1374 let rendered = template.render(with: context, partials: partials)1375 XCTAssertEqual(rendered, expected, "Delimiters set in a parent template should not affect a partial.")1376 }1377 func testPost_PartialBehavior() throws {1378 let templateString = "[ {{>include}} ]\n[ .{{value}}. .|value|. ]\n"1379 let contextJSON = "{\"value\":\"yes\"}".data(using: .utf8)!1380 let expected = "[ .yes. .yes. ]\n[ .yes. .|value|. ]\n"1381 let partials = try [1382 "include": Template(".{{value}}. {{= | | =}} .|value|."),1383 ]1384 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1385 let template = try Template(templateString)1386 let rendered = template.render(with: context, partials: partials)1387 XCTAssertEqual(rendered, expected, "Delimiters set in a partial should not affect the parent template.")1388 }1389 func testSurroundingWhitespace() throws {1390 let templateString = "| {{=@ @=}} |"1391 let contextJSON = "{}".data(using: .utf8)!1392 let expected = "| |"1393 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1394 let template = try Template(templateString)1395 let rendered = template.render(with: context)1396 XCTAssertEqual(rendered, expected, "Surrounding whitespace should be left untouched.")1397 }1398 func testOutlyingWhitespace_Inline() throws {1399 let templateString = " | {{=@ @=}}\n"1400 let contextJSON = "{}".data(using: .utf8)!1401 let expected = " | \n"1402 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1403 let template = try Template(templateString)1404 let rendered = template.render(with: context)1405 XCTAssertEqual(rendered, expected, "Whitespace should be left untouched.")1406 }1407 func testStandaloneTag() throws {1408 let templateString = "Begin.\n{{=@ @=}}\nEnd.\n"1409 let contextJSON = "{}".data(using: .utf8)!1410 let expected = "Begin.\nEnd.\n"1411 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1412 let template = try Template(templateString)1413 let rendered = template.render(with: context)1414 XCTAssertEqual(rendered, expected, "Standalone lines should be removed from the template.")1415 }1416 func testIndentedStandaloneTag() throws {1417 let templateString = "Begin.\n {{=@ @=}}\nEnd.\n"1418 let contextJSON = "{}".data(using: .utf8)!1419 let expected = "Begin.\nEnd.\n"1420 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1421 let template = try Template(templateString)1422 let rendered = template.render(with: context)1423 XCTAssertEqual(rendered, expected, "Indented standalone lines should be removed from the template.")1424 }1425 func testStandaloneLineEndings() throws {1426 let templateString = "|\r\n{{= @ @ =}}\r\n|"1427 let contextJSON = "{}".data(using: .utf8)!1428 let expected = "|\r\n|"1429 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1430 let template = try Template(templateString)1431 let rendered = template.render(with: context)1432 XCTAssertEqual(rendered, expected, "\"\r\n\" should be considered a newline for standalone tags.")1433 }1434 func testStandaloneWithoutPreviousLine() throws {1435 let templateString = " {{=@ @=}}\n="1436 let contextJSON = "{}".data(using: .utf8)!1437 let expected = "="1438 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1439 let template = try Template(templateString)1440 let rendered = template.render(with: context)1441 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to precede them.")1442 }1443 func testStandaloneWithoutNewline() throws {1444 let templateString = "=\n {{=@ @=}}"1445 let contextJSON = "{}".data(using: .utf8)!1446 let expected = "=\n"1447 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1448 let template = try Template(templateString)1449 let rendered = template.render(with: context)1450 XCTAssertEqual(rendered, expected, "Standalone tags should not require a newline to follow them.")1451 }1452 func testPairwithPadding() throws {1453 let templateString = "|{{= @ @ =}}|"1454 let contextJSON = "{}".data(using: .utf8)!1455 let expected = "||"1456 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1457 let template = try Template(templateString)1458 let rendered = template.render(with: context)1459 XCTAssertEqual(rendered, expected, "Superfluous in-tag whitespace should be ignored.")1460 }1461}1462/**1463Like partials, Parent tags are used to expand an external template into the1464current template. Unlike partials, Parent tags may contain optional1465arguments delimited by Block tags. For this reason, Parent tags may also be1466referred to as Parametric Partials.1467The Parent tags' content MUST be a non-whitespace character sequence NOT1468containing the current closing delimiter; each Parent tag MUST be followed by1469an End Section tag with the same content within the matching Parent tag.1470This tag's content names the Parent template to inject. Set Delimiter tags1471Preceding a Parent tag MUST NOT affect the parsing of the injected external1472template. The Parent MUST be rendered against the context stack local to the1473tag. If the named Parent cannot be found, the empty string SHOULD be used1474instead, as in interpolations.1475Parent tags SHOULD be treated as standalone when appropriate. If this tag is1476used standalone, any whitespace preceding the tag should be treated as1477indentation, and prepended to each line of the Parent before rendering.1478The Block tags' content MUST be a non-whitespace character sequence NOT1479containing the current closing delimiter. Each Block tag MUST be followed by1480an End Section tag with the same content within the matching Block tag. This1481tag's content determines the parameter or argument name.1482Block tags may appear both inside and outside of Parent tags. In both cases,1483they specify a position within the template that can be overridden; it is a1484parameter of the containing template. The template text between the Block tag1485and its matching End Section tag defines the default content to render when1486the parameter is not overridden from outside.1487In addition, when used inside of a Parent tag, the template text between a1488Block tag and its matching End Section tag defines content that replaces the1489default defined in the Parent template. This content is the argument passed1490to the Parent template.1491The practice of injecting an external template using a Parent tag is referred1492to as inheritance. If the Parent tag includes a Block tag that overrides a1493parameter of the Parent template, this may also be referred to as1494substitution.1495Parent templates are taken from the same namespace as regular Partial1496templates and in fact, injecting a regular Partial is exactly equivalent to1497injecting a Parent without making any substitutions. Parameter and arguments1498names live in a namespace that is distinct from both Partials and the context.1499 */1500final class InheritanceTests: XCTestCase {1501 static var allTests: [(String, (InheritanceTests) -> () throws -> Void)] {1502 return [1503 ("testDefault", testDefault),1504 ("testVariable", testVariable),1505 ("testTripleMustache", testTripleMustache),1506 ("testSections", testSections),1507 ("testNegativeSections", testNegativeSections),1508 ("testMustacheInjection", testMustacheInjection),1509 ("testInherit", testInherit),1510 ("testOverriddencontent", testOverriddencontent),1511 ("testDatadoesnotoverrideblock", testDatadoesnotoverrideblock),1512 ("testDatadoesnotoverrideblockdefault", testDatadoesnotoverrideblockdefault),1513 ("testOverriddenparent", testOverriddenparent),1514 ("testTwooverriddenparents", testTwooverriddenparents),1515 ("testOverrideparentwithnewlines", testOverrideparentwithnewlines),1516 ("testInheritindentation", testInheritindentation),1517 ("testOnlyoneoverride", testOnlyoneoverride),1518 ("testParenttemplate", testParenttemplate),1519 ("testRecursion", testRecursion),1520 ("testMulti_levelinheritance", testMulti_levelinheritance),1521 ("testMulti_levelinheritance_nosubchild", testMulti_levelinheritance_nosubchild),1522 ("testTextinsideparent", testTextinsideparent),1523 ("testTextinsideparent2", testTextinsideparent2),1524 ]1525 }1526 func testDefault() throws {1527 let templateString = "{{$title}}Default title{{/title}}\n"1528 let contextJSON = "{}".data(using: .utf8)!1529 let expected = "Default title\n"1530 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1531 let template = try Template(templateString)1532 let rendered = template.render(with: context)1533 XCTAssertEqual(rendered, expected, "Default content should be rendered if the block isn't overridden")1534 }1535 func testVariable() throws {1536 let templateString = "{{$foo}}default {{bar}} content{{/foo}}\n"1537 let contextJSON = "{\"bar\":\"baz\"}".data(using: .utf8)!1538 let expected = "default baz content\n"1539 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1540 let template = try Template(templateString)1541 let rendered = template.render(with: context)1542 XCTAssertEqual(rendered, expected, "Default content renders variables")1543 }1544 func testTripleMustache() throws {1545 let templateString = "{{$foo}}default {{{bar}}} content{{/foo}}\n"1546 let contextJSON = "{\"bar\":\"<baz>\"}".data(using: .utf8)!1547 let expected = "default <baz> content\n"1548 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1549 let template = try Template(templateString)1550 let rendered = template.render(with: context)1551 XCTAssertEqual(rendered, expected, "Default content renders triple mustache variables")1552 }1553 func testSections() throws {1554 let templateString = "{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}\n"1555 let contextJSON = "{\"bar\":{\"baz\":\"qux\"}}".data(using: .utf8)!1556 let expected = "default qux content\n"1557 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1558 let template = try Template(templateString)1559 let rendered = template.render(with: context)1560 XCTAssertEqual(rendered, expected, "Default content renders sections")1561 }1562 func testNegativeSections() throws {1563 let templateString = "{{$foo}}default {{^bar}}{{baz}}{{/bar}} content{{/foo}}\n"1564 let contextJSON = "{\"baz\":\"three\"}".data(using: .utf8)!1565 let expected = "default three content\n"1566 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1567 let template = try Template(templateString)1568 let rendered = template.render(with: context)1569 XCTAssertEqual(rendered, expected, "Default content renders negative sections")1570 }1571 func testMustacheInjection() throws {1572 let templateString = "{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}\n"1573 let contextJSON = "{\"bar\":{\"baz\":\"{{qux}}\"}}".data(using: .utf8)!1574 let expected = "default {{qux}} content\n"1575 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1576 let template = try Template(templateString)1577 let rendered = template.render(with: context)1578 XCTAssertEqual(rendered, expected, "Mustache injection in default content")1579 }1580 func testInherit() throws {1581 let templateString = "{{<include}}{{/include}}\n"1582 let contextJSON = "{}".data(using: .utf8)!1583 let expected = "default content"1584 let partials = try [1585 "include": Template("{{$foo}}default content{{/foo}}"),1586 ]1587 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1588 let template = try Template(templateString)1589 let rendered = template.render(with: context, partials: partials)1590 XCTAssertEqual(rendered, expected, "Default content rendered inside inherited templates")1591 }1592 func testOverriddencontent() throws {1593 let templateString = "{{<super}}{{$title}}sub template title{{/title}}{{/super}}"1594 let contextJSON = "{}".data(using: .utf8)!1595 let expected = "...sub template title..."1596 let partials = try [1597 "super": Template("...{{$title}}Default title{{/title}}..."),1598 ]1599 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1600 let template = try Template(templateString)1601 let rendered = template.render(with: context, partials: partials)1602 XCTAssertEqual(rendered, expected, "Overridden content")1603 }1604 func testDatadoesnotoverrideblock() throws {1605 let templateString = "{{<include}}{{$var}}var in template{{/var}}{{/include}}"1606 let contextJSON = "{\"var\":\"var in data\"}".data(using: .utf8)!1607 let expected = "var in template"1608 let partials = try [1609 "include": Template("{{$var}}var in include{{/var}}"),1610 ]1611 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1612 let template = try Template(templateString)1613 let rendered = template.render(with: context, partials: partials)1614 XCTAssertEqual(rendered, expected, "Context does not override argument passed into parent")1615 }1616 func testDatadoesnotoverrideblockdefault() throws {1617 let templateString = "{{<include}}{{/include}}"1618 let contextJSON = "{\"var\":\"var in data\"}".data(using: .utf8)!1619 let expected = "var in include"1620 let partials = try [1621 "include": Template("{{$var}}var in include{{/var}}"),1622 ]1623 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1624 let template = try Template(templateString)1625 let rendered = template.render(with: context, partials: partials)1626 XCTAssertEqual(rendered, expected, "Context does not override default content of block")1627 }1628 func testOverriddenparent() throws {1629 let templateString = "test {{<parent}}{{$stuff}}override{{/stuff}}{{/parent}}"1630 let contextJSON = "{}".data(using: .utf8)!1631 let expected = "test override"1632 let partials = try [1633 "parent": Template("{{$stuff}}...{{/stuff}}"),1634 ]1635 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1636 let template = try Template(templateString)1637 let rendered = template.render(with: context, partials: partials)1638 XCTAssertEqual(rendered, expected, "Overridden parent")1639 }1640 func testTwooverriddenparents() throws {1641 let templateString = "test {{<parent}}{{$stuff}}override1{{/stuff}}{{/parent}} {{<parent}}{{$stuff}}override2{{/stuff}}{{/parent}}\n"1642 let contextJSON = "{}".data(using: .utf8)!1643 let expected = "test |override1 default| |override2 default|\n"1644 let partials = try [1645 "parent": Template("|{{$stuff}}...{{/stuff}}{{$default}} default{{/default}}|"),1646 ]1647 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1648 let template = try Template(templateString)1649 let rendered = template.render(with: context, partials: partials)1650 XCTAssertEqual(rendered, expected, "Two overridden parents with different content")1651 }1652 func testOverrideparentwithnewlines() throws {1653 let templateString = "{{<parent}}{{$ballmer}}\npeaked\n\n:(\n{{/ballmer}}{{/parent}}"1654 let contextJSON = "{}".data(using: .utf8)!1655 let expected = "peaked\n\n:(\n"1656 let partials = try [1657 "parent": Template("{{$ballmer}}peaking{{/ballmer}}"),1658 ]1659 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1660 let template = try Template(templateString)1661 let rendered = template.render(with: context, partials: partials)1662 XCTAssertEqual(rendered, expected, "Override parent with newlines")1663 }1664 func testInheritindentation() throws {1665 let templateString = "{{<parent}}{{$nineties}}hammer time{{/nineties}}{{/parent}}"1666 let contextJSON = "{}".data(using: .utf8)!1667 let expected = "stop:\n hammer time\n"1668 let partials = try [1669 "parent": Template("stop:\n {{$nineties}}collaborate and listen{{/nineties}}\n"),1670 ]1671 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1672 let template = try Template(templateString)1673 let rendered = template.render(with: context, partials: partials)1674 XCTAssertEqual(rendered, expected, "Inherit indentation when overriding a parent")1675 }1676 func testOnlyoneoverride() throws {1677 let templateString = "{{<parent}}{{$stuff2}}override two{{/stuff2}}{{/parent}}"1678 let contextJSON = "{}".data(using: .utf8)!1679 let expected = "new default one, override two"1680 let partials = try [1681 "parent": Template("{{$stuff}}new default one{{/stuff}}, {{$stuff2}}new default two{{/stuff2}}"),1682 ]1683 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1684 let template = try Template(templateString)1685 let rendered = template.render(with: context, partials: partials)1686 XCTAssertEqual(rendered, expected, "Override one parameter but not the other")1687 }1688 func testParenttemplate() throws {1689 let templateString = "{{>parent}}|{{<parent}}{{/parent}}"1690 let contextJSON = "{}".data(using: .utf8)!1691 let expected = "default content|default content"1692 let partials = try [1693 "parent": Template("{{$foo}}default content{{/foo}}"),1694 ]1695 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1696 let template = try Template(templateString)1697 let rendered = template.render(with: context, partials: partials)1698 XCTAssertEqual(rendered, expected, "Parent templates behave identically to partials when called with no parameters")1699 }1700 func testRecursion() throws {1701 let templateString = "{{<parent}}{{$foo}}override{{/foo}}{{/parent}}"1702 let contextJSON = "{}".data(using: .utf8)!1703 let expected = "override override override don't recurse"1704 let partials = try [1705 "parent2": Template("{{$foo}}parent2 default content{{/foo}} {{<parent}}{{$bar}}don't recurse{{/bar}}{{/parent}}"),1706 "parent": Template("{{$foo}}default content{{/foo}} {{$bar}}{{<parent2}}{{/parent2}}{{/bar}}"),1707 ]1708 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1709 let template = try Template(templateString)1710 let rendered = template.render(with: context, partials: partials)1711 XCTAssertEqual(rendered, expected, "Recursion in inherited templates")1712 }1713 func testMulti_levelinheritance() throws {1714 let templateString = "{{<parent}}{{$a}}c{{/a}}{{/parent}}"1715 let contextJSON = "{}".data(using: .utf8)!1716 let expected = "c"1717 let partials = try [1718 "grandParent": Template("{{$a}}g{{/a}}"),1719 "older": Template("{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}"),1720 "parent": Template("{{<older}}{{$a}}p{{/a}}{{/older}}"),1721 ]1722 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1723 let template = try Template(templateString)1724 let rendered = template.render(with: context, partials: partials)1725 XCTAssertEqual(rendered, expected, "Top-level substitutions take precedence in multi-level inheritance")1726 }1727 func testMulti_levelinheritance_nosubchild() throws {1728 let templateString = "{{<parent}}{{/parent}}"1729 let contextJSON = "{}".data(using: .utf8)!1730 let expected = "p"1731 let partials = try [1732 "grandParent": Template("{{$a}}g{{/a}}"),1733 "older": Template("{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}"),1734 "parent": Template("{{<older}}{{$a}}p{{/a}}{{/older}}"),1735 ]1736 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1737 let template = try Template(templateString)1738 let rendered = template.render(with: context, partials: partials)1739 XCTAssertEqual(rendered, expected, "Top-level substitutions take precedence in multi-level inheritance")1740 }1741 func testTextinsideparent() throws {1742 let templateString = "{{<parent}} asdfasd {{$foo}}hmm{{/foo}} asdfasdfasdf {{/parent}}"1743 let contextJSON = "{}".data(using: .utf8)!1744 let expected = "hmm"1745 let partials = try [1746 "parent": Template("{{$foo}}default content{{/foo}}"),1747 ]1748 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1749 let template = try Template(templateString)1750 let rendered = template.render(with: context, partials: partials)1751 XCTAssertEqual(rendered, expected, "Ignores text inside parent templates, but does parse $ tags")1752 }1753 func testTextinsideparent2() throws {1754 let templateString = "{{<parent}} asdfasd asdfasdfasdf {{/parent}}"1755 let contextJSON = "{}".data(using: .utf8)!1756 let expected = "default content"1757 let partials = try [1758 "parent": Template("{{$foo}}default content{{/foo}}"),1759 ]1760 let context = try JSONDecoder().decode(Context.self, from: contextJSON)1761 let template = try Template(templateString)1762 let rendered = template.render(with: context, partials: partials)1763 XCTAssertEqual(rendered, expected, "Allows text inside a parent tag, but ignores it")1764 }1765}...

Full Screen

Full Screen

context_unit_test.rb

Source:context_unit_test.rb Github

copy

Full Screen

...11 def non_zero?12 true13 end14end15class ContextSensitiveDrop < Liquid::Drop16 def test17 @context['test']18 end19end20class Category < Liquid::Drop21 attr_accessor :name22 def initialize(name)23 @name = name24 end25 def to_liquid26 CategoryDrop.new(self)27 end28end29class CategoryDrop30 attr_accessor :category, :context31 def initialize(category)32 @category = category33 end34end35class CounterDrop < Liquid::Drop36 def count37 @count ||= 038 @count += 139 end40end41class ArrayLike42 def fetch(index)43 end44 def [](index)45 @counts ||= []46 @counts[index] ||= 047 @counts[index] += 148 end49 def to_liquid50 self51 end52end53class ContextUnitTest < Minitest::Test54 include Liquid55 def setup56 @context = Liquid::Context.new57 end58 def teardown59 Spy.teardown60 end61 def test_variables62 @context['string'] = 'string'63 assert_equal 'string', @context['string']64 @context['num'] = 565 assert_equal 5, @context['num']66 @context['time'] = Time.parse('2006-06-06 12:00:00')67 assert_equal Time.parse('2006-06-06 12:00:00'), @context['time']68 @context['date'] = Date.today69 assert_equal Date.today, @context['date']70 now = DateTime.now71 @context['datetime'] = now72 assert_equal now, @context['datetime']73 @context['bool'] = true74 assert_equal true, @context['bool']75 @context['bool'] = false76 assert_equal false, @context['bool']77 @context['nil'] = nil78 assert_equal nil, @context['nil']79 assert_equal nil, @context['nil']80 end81 def test_variables_not_existing82 assert_equal nil, @context['does_not_exist']83 end84 def test_scoping85 @context.push86 @context.pop87 assert_raises(Liquid::ContextError) do88 @context.pop89 end90 assert_raises(Liquid::ContextError) do91 @context.push92 @context.pop93 @context.pop94 end95 end96 def test_length_query97 @context['numbers'] = [1, 2, 3, 4]98 assert_equal 4, @context['numbers.size']99 @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }100 assert_equal 4, @context['numbers.size']101 @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }102 assert_equal 1000, @context['numbers.size']103 end104 def test_hyphenated_variable105 @context['oh-my'] = 'godz'106 assert_equal 'godz', @context['oh-my']107 end108 def test_add_filter109 filter = Module.new do110 def hi(output)111 output + ' hi!'112 end113 end114 context = Context.new115 context.add_filters(filter)116 assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')117 context = Context.new118 assert_equal 'hi?', context.invoke(:hi, 'hi?')119 context.add_filters(filter)120 assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')121 end122 def test_only_intended_filters_make_it_there123 filter = Module.new do124 def hi(output)125 output + ' hi!'126 end127 end128 context = Context.new129 assert_equal "Wookie", context.invoke("hi", "Wookie")130 context.add_filters(filter)131 assert_equal "Wookie hi!", context.invoke("hi", "Wookie")132 end133 def test_add_item_in_outer_scope134 @context['test'] = 'test'135 @context.push136 assert_equal 'test', @context['test']137 @context.pop138 assert_equal 'test', @context['test']139 end140 def test_add_item_in_inner_scope141 @context.push142 @context['test'] = 'test'143 assert_equal 'test', @context['test']144 @context.pop145 assert_equal nil, @context['test']146 end147 def test_hierachical_data148 @context['hash'] = { "name" => 'tobi' }149 assert_equal 'tobi', @context['hash.name']150 assert_equal 'tobi', @context['hash["name"]']151 end152 def test_keywords153 assert_equal true, @context['true']154 assert_equal false, @context['false']155 end156 def test_digits157 assert_equal 100, @context['100']158 assert_equal 100.00, @context['100.00']159 end160 def test_strings161 assert_equal "hello!", @context['"hello!"']162 assert_equal "hello!", @context["'hello!'"]163 end164 def test_merge165 @context.merge({ "test" => "test" })166 assert_equal 'test', @context['test']167 @context.merge({ "test" => "newvalue", "foo" => "bar" })168 assert_equal 'newvalue', @context['test']169 assert_equal 'bar', @context['foo']170 end171 def test_array_notation172 @context['test'] = [1, 2, 3, 4, 5]173 assert_equal 1, @context['test[0]']174 assert_equal 2, @context['test[1]']175 assert_equal 3, @context['test[2]']176 assert_equal 4, @context['test[3]']177 assert_equal 5, @context['test[4]']178 end179 def test_recoursive_array_notation180 @context['test'] = { 'test' => [1, 2, 3, 4, 5] }181 assert_equal 1, @context['test.test[0]']182 @context['test'] = [{ 'test' => 'worked' }]183 assert_equal 'worked', @context['test[0].test']184 end185 def test_hash_to_array_transition186 @context['colors'] = {187 'Blue' => ['003366', '336699', '6699CC', '99CCFF'],188 'Green' => ['003300', '336633', '669966', '99CC99'],189 'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],190 'Red' => ['660000', '993333', 'CC6666', 'FF9999']191 }192 assert_equal '003366', @context['colors.Blue[0]']193 assert_equal 'FF9999', @context['colors.Red[3]']194 end195 def test_try_first196 @context['test'] = [1, 2, 3, 4, 5]197 assert_equal 1, @context['test.first']198 assert_equal 5, @context['test.last']199 @context['test'] = { 'test' => [1, 2, 3, 4, 5] }200 assert_equal 1, @context['test.test.first']201 assert_equal 5, @context['test.test.last']202 @context['test'] = [1]203 assert_equal 1, @context['test.first']204 assert_equal 1, @context['test.last']205 end206 def test_access_hashes_with_hash_notation207 @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }208 @context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }209 assert_equal 5, @context['products["count"]']210 assert_equal 'deepsnow', @context['products["tags"][0]']211 assert_equal 'deepsnow', @context['products["tags"].first']212 assert_equal 'draft151cm', @context['product["variants"][0]["title"]']213 assert_equal 'element151cm', @context['product["variants"][1]["title"]']214 assert_equal 'draft151cm', @context['product["variants"][0]["title"]']215 assert_equal 'element151cm', @context['product["variants"].last["title"]']216 end217 def test_access_variable_with_hash_notation218 @context['foo'] = 'baz'219 @context['bar'] = 'foo'220 assert_equal 'baz', @context['["foo"]']221 assert_equal 'baz', @context['[bar]']222 end223 def test_access_hashes_with_hash_access_variables224 @context['var'] = 'tags'225 @context['nested'] = { 'var' => 'tags' }226 @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }227 assert_equal 'deepsnow', @context['products[var].first']228 assert_equal 'freestyle', @context['products[nested.var].last']229 end230 def test_hash_notation_only_for_hash_access231 @context['array'] = [1, 2, 3, 4, 5]232 @context['hash'] = { 'first' => 'Hello' }233 assert_equal 1, @context['array.first']234 assert_equal nil, @context['array["first"]']235 assert_equal 'Hello', @context['hash["first"]']236 end237 def test_first_can_appear_in_middle_of_callchain238 @context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }239 assert_equal 'draft151cm', @context['product.variants[0].title']240 assert_equal 'element151cm', @context['product.variants[1].title']241 assert_equal 'draft151cm', @context['product.variants.first.title']242 assert_equal 'element151cm', @context['product.variants.last.title']243 end244 def test_cents245 @context.merge("cents" => HundredCentes.new)246 assert_equal 100, @context['cents']247 end248 def test_nested_cents249 @context.merge("cents" => { 'amount' => HundredCentes.new })250 assert_equal 100, @context['cents.amount']251 @context.merge("cents" => { 'cents' => { 'amount' => HundredCentes.new } })252 assert_equal 100, @context['cents.cents.amount']253 end254 def test_cents_through_drop255 @context.merge("cents" => CentsDrop.new)256 assert_equal 100, @context['cents.amount']257 end258 def test_nested_cents_through_drop259 @context.merge("vars" => { "cents" => CentsDrop.new })260 assert_equal 100, @context['vars.cents.amount']261 end262 def test_drop_methods_with_question_marks263 @context.merge("cents" => CentsDrop.new)264 assert @context['cents.non_zero?']265 end266 def test_context_from_within_drop267 @context.merge("test" => '123', "vars" => ContextSensitiveDrop.new)268 assert_equal '123', @context['vars.test']269 end270 def test_nested_context_from_within_drop271 @context.merge("test" => '123', "vars" => { "local" => ContextSensitiveDrop.new })272 assert_equal '123', @context['vars.local.test']273 end274 def test_ranges275 @context.merge("test" => '5')276 assert_equal (1..5), @context['(1..5)']277 assert_equal (1..5), @context['(1..test)']278 assert_equal (5..5), @context['(test..test)']279 end280 def test_cents_through_drop_nestedly281 @context.merge("cents" => { "cents" => CentsDrop.new })282 assert_equal 100, @context['cents.cents.amount']283 @context.merge("cents" => { "cents" => { "cents" => CentsDrop.new } })284 assert_equal 100, @context['cents.cents.cents.amount']285 end286 def test_drop_with_variable_called_only_once287 @context['counter'] = CounterDrop.new288 assert_equal 1, @context['counter.count']289 assert_equal 2, @context['counter.count']290 assert_equal 3, @context['counter.count']291 end292 def test_drop_with_key_called_only_once293 @context['counter'] = CounterDrop.new294 assert_equal 1, @context['counter["count"]']295 assert_equal 2, @context['counter["count"]']296 assert_equal 3, @context['counter["count"]']297 end298 def test_proc_as_variable299 @context['dynamic'] = proc { 'Hello' }300 assert_equal 'Hello', @context['dynamic']301 end302 def test_lambda_as_variable303 @context['dynamic'] = proc { 'Hello' }304 assert_equal 'Hello', @context['dynamic']305 end306 def test_nested_lambda_as_variable307 @context['dynamic'] = { "lambda" => proc { 'Hello' } }308 assert_equal 'Hello', @context['dynamic.lambda']309 end310 def test_array_containing_lambda_as_variable311 @context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5]312 assert_equal 'Hello', @context['dynamic[2]']313 end314 def test_lambda_is_called_once315 @context['callcount'] = proc { @global ||= 0; @global += 1; @global.to_s }316 assert_equal '1', @context['callcount']317 assert_equal '1', @context['callcount']318 assert_equal '1', @context['callcount']319 @global = nil320 end321 def test_nested_lambda_is_called_once322 @context['callcount'] = { "lambda" => proc { @global ||= 0; @global += 1; @global.to_s } }323 assert_equal '1', @context['callcount.lambda']324 assert_equal '1', @context['callcount.lambda']325 assert_equal '1', @context['callcount.lambda']326 @global = nil327 end328 def test_lambda_in_array_is_called_once329 @context['callcount'] = [1, 2, proc { @global ||= 0; @global += 1; @global.to_s }, 4, 5]330 assert_equal '1', @context['callcount[2]']331 assert_equal '1', @context['callcount[2]']332 assert_equal '1', @context['callcount[2]']333 @global = nil334 end335 def test_access_to_context_from_proc336 @context.registers[:magic] = 345392337 @context['magic'] = proc { @context.registers[:magic] }338 assert_equal 345392, @context['magic']339 end340 def test_to_liquid_and_context_at_first_level341 @context['category'] = Category.new("foobar")342 assert_kind_of CategoryDrop, @context['category']343 assert_equal @context, @context['category'].context344 end345 def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations346 mock_any = Spy.on_instance_method(Array, :any?)347 mock_empty = Spy.on_instance_method(Array, :empty?)348 @context.interrupt?349 refute mock_any.has_been_called?350 assert mock_empty.has_been_called?351 end352 def test_context_initialization_with_a_proc_in_environment353 contx = Context.new([test: ->(c) { c['poutine'] }], { test: :foo })354 assert contx355 assert_nil contx['poutine']356 end357 def test_apply_global_filter358 global_filter_proc = ->(output) { "#{output} filtered" }359 context = Context.new360 context.global_filter = global_filter_proc361 assert_equal 'hi filtered', context.apply_global_filter('hi')362 end363 def test_apply_global_filter_when_no_global_filter_exist364 context = Context.new365 assert_equal 'hi', context.apply_global_filter('hi')366 end367end # ContextTest...

Full Screen

Full Screen

context_test.rb

Source:context_test.rb Github

copy

Full Screen

...11 def non_zero?12 true13 end14end15class ContextSensitiveDrop < Liquid::Drop16 def test17 @context['test']18 end19end20class Category < Liquid::Drop21 attr_accessor :name22 def initialize(name)23 @name = name24 end25 def to_liquid26 CategoryDrop.new(self)27 end28end29class CategoryDrop30 attr_accessor :category, :context31 def initialize(category)32 @category = category33 end34end35class CounterDrop < Liquid::Drop36 def count37 @count ||= 038 @count += 139 end40end41class ArrayLike42 def fetch(index)43 end44 def [](index)45 @counts ||= []46 @counts[index] ||= 047 @counts[index] += 148 end49 def to_liquid50 self51 end52end53class ContextTest < Test::Unit::TestCase54 include Liquid55 def setup56 @context = Liquid::Context.new57 end58 def test_variables59 @context['string'] = 'string'60 assert_equal 'string', @context['string']61 @context['num'] = 562 assert_equal 5, @context['num']63 @context['time'] = Time.parse('2006-06-06 12:00:00')64 assert_equal Time.parse('2006-06-06 12:00:00'), @context['time']65 @context['date'] = Date.today66 assert_equal Date.today, @context['date']67 now = DateTime.now68 @context['datetime'] = now69 assert_equal now, @context['datetime']70 @context['bool'] = true71 assert_equal true, @context['bool']72 @context['bool'] = false73 assert_equal false, @context['bool']74 @context['nil'] = nil75 assert_equal nil, @context['nil']76 assert_equal nil, @context['nil']77 end78 def test_variables_not_existing79 assert_equal nil, @context['does_not_exist']80 end81 def test_scoping82 assert_nothing_raised do83 @context.push84 @context.pop85 end86 assert_raise(Liquid::ContextError) do87 @context.pop88 end89 assert_raise(Liquid::ContextError) do90 @context.push91 @context.pop92 @context.pop93 end94 end95 def test_length_query96 @context['numbers'] = [1,2,3,4]97 assert_equal 4, @context['numbers.size']98 @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4}99 assert_equal 4, @context['numbers.size']100 @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4, 'size' => 1000}101 assert_equal 1000, @context['numbers.size']102 end103 def test_hyphenated_variable104 @context['oh-my'] = 'godz'105 assert_equal 'godz', @context['oh-my']106 end107 def test_add_filter108 filter = Module.new do109 def hi(output)110 output + ' hi!'111 end112 end113 context = Context.new114 context.add_filters(filter)115 assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')116 context = Context.new117 assert_equal 'hi?', context.invoke(:hi, 'hi?')118 context.add_filters(filter)119 assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')120 end121 def test_override_global_filter122 global = Module.new do123 def notice(output)124 "Global #{output}"125 end126 end127 local = Module.new do128 def notice(output)129 "Local #{output}"130 end131 end132 Template.register_filter(global)133 assert_equal 'Global test', Template.parse("{{'test' | notice }}").render134 assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])135 end136 def test_only_intended_filters_make_it_there137 filter = Module.new do138 def hi(output)139 output + ' hi!'140 end141 end142 context = Context.new143 assert_equal "Wookie", context.invoke("hi", "Wookie")144 context.add_filters(filter)145 assert_equal "Wookie hi!", context.invoke("hi", "Wookie")146 end147 def test_add_item_in_outer_scope148 @context['test'] = 'test'149 @context.push150 assert_equal 'test', @context['test']151 @context.pop152 assert_equal 'test', @context['test']153 end154 def test_add_item_in_inner_scope155 @context.push156 @context['test'] = 'test'157 assert_equal 'test', @context['test']158 @context.pop159 assert_equal nil, @context['test']160 end161 def test_hierachical_data162 @context['hash'] = {"name" => 'tobi'}163 assert_equal 'tobi', @context['hash.name']164 assert_equal 'tobi', @context['hash["name"]']165 end166 def test_keywords167 assert_equal true, @context['true']168 assert_equal false, @context['false']169 end170 def test_digits171 assert_equal 100, @context['100']172 assert_equal 100.00, @context['100.00']173 end174 def test_strings175 assert_equal "hello!", @context['"hello!"']176 assert_equal "hello!", @context["'hello!'"]177 end178 def test_merge179 @context.merge({ "test" => "test" })180 assert_equal 'test', @context['test']181 @context.merge({ "test" => "newvalue", "foo" => "bar" })182 assert_equal 'newvalue', @context['test']183 assert_equal 'bar', @context['foo']184 end185 def test_array_notation186 @context['test'] = [1,2,3,4,5]187 assert_equal 1, @context['test[0]']188 assert_equal 2, @context['test[1]']189 assert_equal 3, @context['test[2]']190 assert_equal 4, @context['test[3]']191 assert_equal 5, @context['test[4]']192 end193 def test_recoursive_array_notation194 @context['test'] = {'test' => [1,2,3,4,5]}195 assert_equal 1, @context['test.test[0]']196 @context['test'] = [{'test' => 'worked'}]197 assert_equal 'worked', @context['test[0].test']198 end199 def test_hash_to_array_transition200 @context['colors'] = {201 'Blue' => ['003366','336699', '6699CC', '99CCFF'],202 'Green' => ['003300','336633', '669966', '99CC99'],203 'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'],204 'Red' => ['660000','993333', 'CC6666', 'FF9999']205 }206 assert_equal '003366', @context['colors.Blue[0]']207 assert_equal 'FF9999', @context['colors.Red[3]']208 end209 def test_try_first210 @context['test'] = [1,2,3,4,5]211 assert_equal 1, @context['test.first']212 assert_equal 5, @context['test.last']213 @context['test'] = {'test' => [1,2,3,4,5]}214 assert_equal 1, @context['test.test.first']215 assert_equal 5, @context['test.test.last']216 @context['test'] = [1]217 assert_equal 1, @context['test.first']218 assert_equal 1, @context['test.last']219 end220 def test_access_hashes_with_hash_notation221 @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }222 @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}223 assert_equal 5, @context['products["count"]']224 assert_equal 'deepsnow', @context['products["tags"][0]']225 assert_equal 'deepsnow', @context['products["tags"].first']226 assert_equal 'draft151cm', @context['product["variants"][0]["title"]']227 assert_equal 'element151cm', @context['product["variants"][1]["title"]']228 assert_equal 'draft151cm', @context['product["variants"][0]["title"]']229 assert_equal 'element151cm', @context['product["variants"].last["title"]']230 end231 def test_access_variable_with_hash_notation232 @context['foo'] = 'baz'233 @context['bar'] = 'foo'234 assert_equal 'baz', @context['["foo"]']235 assert_equal 'baz', @context['[bar]']236 end237 def test_access_hashes_with_hash_access_variables238 @context['var'] = 'tags'239 @context['nested'] = {'var' => 'tags'}240 @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }241 assert_equal 'deepsnow', @context['products[var].first']242 assert_equal 'freestyle', @context['products[nested.var].last']243 end244 def test_hash_notation_only_for_hash_access245 @context['array'] = [1,2,3,4,5]246 @context['hash'] = {'first' => 'Hello'}247 assert_equal 1, @context['array.first']248 assert_equal nil, @context['array["first"]']249 assert_equal 'Hello', @context['hash["first"]']250 end251 def test_first_can_appear_in_middle_of_callchain252 @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}253 assert_equal 'draft151cm', @context['product.variants[0].title']254 assert_equal 'element151cm', @context['product.variants[1].title']255 assert_equal 'draft151cm', @context['product.variants.first.title']256 assert_equal 'element151cm', @context['product.variants.last.title']257 end258 def test_cents259 @context.merge( "cents" => HundredCentes.new )260 assert_equal 100, @context['cents']261 end262 def test_nested_cents263 @context.merge( "cents" => { 'amount' => HundredCentes.new} )264 assert_equal 100, @context['cents.amount']265 @context.merge( "cents" => { 'cents' => { 'amount' => HundredCentes.new} } )266 assert_equal 100, @context['cents.cents.amount']267 end268 def test_cents_through_drop269 @context.merge( "cents" => CentsDrop.new )270 assert_equal 100, @context['cents.amount']271 end272 def test_nested_cents_through_drop273 @context.merge( "vars" => {"cents" => CentsDrop.new} )274 assert_equal 100, @context['vars.cents.amount']275 end276 def test_drop_methods_with_question_marks277 @context.merge( "cents" => CentsDrop.new )278 assert @context['cents.non_zero?']279 end280 def test_context_from_within_drop281 @context.merge( "test" => '123', "vars" => ContextSensitiveDrop.new )282 assert_equal '123', @context['vars.test']283 end284 def test_nested_context_from_within_drop285 @context.merge( "test" => '123', "vars" => {"local" => ContextSensitiveDrop.new } )286 assert_equal '123', @context['vars.local.test']287 end288 def test_ranges289 @context.merge( "test" => '5' )290 assert_equal (1..5), @context['(1..5)']291 assert_equal (1..5), @context['(1..test)']292 assert_equal (5..5), @context['(test..test)']293 end294 def test_cents_through_drop_nestedly295 @context.merge( "cents" => {"cents" => CentsDrop.new} )296 assert_equal 100, @context['cents.cents.amount']297 @context.merge( "cents" => { "cents" => {"cents" => CentsDrop.new}} )298 assert_equal 100, @context['cents.cents.cents.amount']299 end300 def test_drop_with_variable_called_only_once301 @context['counter'] = CounterDrop.new302 assert_equal 1, @context['counter.count']303 assert_equal 2, @context['counter.count']304 assert_equal 3, @context['counter.count']305 end306 def test_drop_with_key_called_only_once307 @context['counter'] = CounterDrop.new308 assert_equal 1, @context['counter["count"]']309 assert_equal 2, @context['counter["count"]']310 assert_equal 3, @context['counter["count"]']311 end312 def test_proc_as_variable313 @context['dynamic'] = Proc.new { 'Hello' }314 assert_equal 'Hello', @context['dynamic']315 end316 def test_lambda_as_variable317 @context['dynamic'] = proc { 'Hello' }318 assert_equal 'Hello', @context['dynamic']319 end320 def test_nested_lambda_as_variable321 @context['dynamic'] = { "lambda" => proc { 'Hello' } }322 assert_equal 'Hello', @context['dynamic.lambda']323 end324 def test_array_containing_lambda_as_variable325 @context['dynamic'] = [1,2, proc { 'Hello' } ,4,5]326 assert_equal 'Hello', @context['dynamic[2]']327 end328 def test_lambda_is_called_once329 @context['callcount'] = proc { @global ||= 0; @global += 1; @global.to_s }330 assert_equal '1', @context['callcount']331 assert_equal '1', @context['callcount']332 assert_equal '1', @context['callcount']333 @global = nil334 end335 def test_nested_lambda_is_called_once336 @context['callcount'] = { "lambda" => proc { @global ||= 0; @global += 1; @global.to_s } }337 assert_equal '1', @context['callcount.lambda']338 assert_equal '1', @context['callcount.lambda']339 assert_equal '1', @context['callcount.lambda']340 @global = nil341 end342 def test_lambda_in_array_is_called_once343 @context['callcount'] = [1,2, proc { @global ||= 0; @global += 1; @global.to_s } ,4,5]344 assert_equal '1', @context['callcount[2]']345 assert_equal '1', @context['callcount[2]']346 assert_equal '1', @context['callcount[2]']347 @global = nil348 end349 def test_access_to_context_from_proc350 @context.registers[:magic] = 345392351 @context['magic'] = proc { @context.registers[:magic] }352 assert_equal 345392, @context['magic']353 end354 def test_to_liquid_and_context_at_first_level355 @context['category'] = Category.new("foobar")356 assert_kind_of CategoryDrop, @context['category']357 assert_equal @context, @context['category'].context358 end359end # ContextTest...

Full Screen

Full Screen

DiffListContextViewModel.swift

Source:DiffListContextViewModel.swift Github

copy

Full Screen

1import Foundation2final class DiffListContextItemViewModel {3 private let text: String4 private let semanticContentAttribute: UISemanticContentAttribute5 var theme: Theme {6 didSet {7 self.textAttributedString = DiffListContextItemViewModel.calculateAttributedString(with: text, semanticContentAttribute: semanticContentAttribute, theme: theme, contextFont: contextFont)8 }9 }10 var contextFont: UIFont {11 didSet {12 self.textAttributedString = DiffListContextItemViewModel.calculateAttributedString(with: text, semanticContentAttribute: semanticContentAttribute, theme: theme, contextFont: contextFont)13 }14 }15 16 private(set) var textAttributedString: NSAttributedString17 18 init(text: String, semanticContentAttribute: UISemanticContentAttribute, theme: Theme, contextFont: UIFont) {19 self.text = text20 self.semanticContentAttribute = semanticContentAttribute21 self.theme = theme22 self.contextFont = contextFont23 24 self.textAttributedString = DiffListContextItemViewModel.calculateAttributedString(with: text, semanticContentAttribute: semanticContentAttribute, theme: theme, contextFont: contextFont)25 }26 27 private static func calculateAttributedString(with text: String, semanticContentAttribute: UISemanticContentAttribute, theme: Theme, contextFont: UIFont) -> NSAttributedString {28 29 let paragraphStyle = NSMutableParagraphStyle()30 let lineSpacing: CGFloat = 431 paragraphStyle.lineSpacing = lineSpacing32 paragraphStyle.lineHeightMultiple = contextFont.lineHeightMultipleToMatch(lineSpacing: lineSpacing)33 switch semanticContentAttribute {34 case .forceRightToLeft:35 paragraphStyle.alignment = .right36 default:37 paragraphStyle.alignment = .left38 }39 let attributes = [NSAttributedString.Key.font: contextFont,40 NSAttributedString.Key.paragraphStyle: paragraphStyle,41 NSAttributedString.Key.foregroundColor: theme.colors.primaryText]42 43 return NSAttributedString(string: text, attributes: attributes)44 }45}46extension DiffListContextItemViewModel: Equatable {47 static func == (lhs: DiffListContextItemViewModel, rhs: DiffListContextItemViewModel) -> Bool {48 return lhs.text == rhs.text49 }50}51final class DiffListContextViewModel: DiffListGroupViewModel {52 let heading: String53 var isExpanded: Bool54 let items: [DiffListContextItemViewModel?]55 var theme: Theme {56 didSet {57 for item in items {58 item?.theme = theme59 }60 }61 }62 var expandButtonTitle: String {63 return isExpanded ? WMFLocalizedString("diff-context-lines-expanded-button-title", value:"Hide", comment:"Expand button title in diff compare context section when section is in expanded state.") : WMFLocalizedString("diff-context-lines-collapsed-button-title", value:"Show", comment:"Expand button title in diff compare context section when section is in collapsed state.")64 }65 66 private(set) var contextFont: UIFont {67 didSet {68 for item in items {69 item?.contextFont = contextFont70 }71 }72 }73 private(set) var headingFont: UIFont74 75 private var _width: CGFloat76 var width: CGFloat {77 get {78 return _width79 }80 set {81 _width = newValue82 expandedHeight = DiffListContextViewModel.calculateExpandedHeight(items: items, heading: heading, availableWidth: availableWidth, innerPadding: innerPadding, contextItemPadding: DiffListContextViewModel.contextItemTextPadding, contextFont: contextFont, headingFont: headingFont, emptyContextLineHeight: emptyContextLineHeight)83 height = DiffListContextViewModel.calculateCollapsedHeight(items: items, heading: heading, availableWidth: availableWidth, innerPadding: innerPadding, contextItemPadding: DiffListContextViewModel.contextItemTextPadding, contextFont: contextFont, headingFont: headingFont)84 }85 }86 var traitCollection: UITraitCollection {87 didSet {88 innerPadding = DiffListContextViewModel.calculateInnerPadding(traitCollection: traitCollection)89 contextFont = UIFont.wmf_font(contextDynamicTextStyle, compatibleWithTraitCollection: traitCollection)90 headingFont = UIFont.wmf_font(.boldFootnote, compatibleWithTraitCollection: traitCollection)91 }92 }93 94 private let contextDynamicTextStyle = DynamicTextStyle.subheadline95 private(set) var height: CGFloat = 096 private(set) var expandedHeight: CGFloat = 097 private(set) var innerPadding: NSDirectionalEdgeInsets98 99 static let contextItemTextPadding = NSDirectionalEdgeInsets(top: 3, leading: 8, bottom: 8, trailing: 8)100 static let contextItemStackSpacing: CGFloat = 5101 static let containerStackSpacing: CGFloat = 15102 103 private var availableWidth: CGFloat {104 return width - innerPadding.leading - innerPadding.trailing - DiffListContextViewModel.contextItemTextPadding.leading - DiffListContextViewModel.contextItemTextPadding.trailing105 }106 var emptyContextLineHeight: CGFloat {107 return contextFont.pointSize * 1.8108 }109 110 init(diffItems: [TransformDiffItem], isExpanded: Bool, theme: Theme, width: CGFloat, traitCollection: UITraitCollection, semanticContentAttribute: UISemanticContentAttribute) {111 self.isExpanded = isExpanded112 self.theme = theme113 self._width = width114 self.traitCollection = traitCollection115 116 if let firstItemLineNumber = diffItems.first?.lineNumber,117 let lastItemLineNumber = diffItems.last?.lineNumber {118 119 if diffItems.count == 1 {120 self.heading = String.localizedStringWithFormat(CommonStrings.diffSingleLineFormat, firstItemLineNumber)121 } else {122 self.heading = String.localizedStringWithFormat(CommonStrings.diffMultiLineFormat, firstItemLineNumber, lastItemLineNumber)123 }124 125 } else {126 self.heading = "" //tonitodo: optional would be better127 }128 129 let contextFont = UIFont.wmf_font(contextDynamicTextStyle, compatibleWithTraitCollection: traitCollection)130 131 self.items = diffItems.map({ (item) -> DiffListContextItemViewModel? in132 if item.text.isEmpty {133 return nil134 }135 136 return DiffListContextItemViewModel(text: item.text, semanticContentAttribute: semanticContentAttribute, theme: theme, contextFont: contextFont)137 })138 139 self.contextFont = contextFont140 self.headingFont = UIFont.wmf_font(.semiboldFootnote, compatibleWithTraitCollection: traitCollection)141 142 innerPadding = DiffListContextViewModel.calculateInnerPadding(traitCollection: traitCollection)143 144 expandedHeight = DiffListContextViewModel.calculateExpandedHeight(items: items, heading: heading, availableWidth: availableWidth, innerPadding: innerPadding, contextItemPadding: DiffListContextViewModel.contextItemTextPadding, contextFont: contextFont, headingFont: headingFont, emptyContextLineHeight: emptyContextLineHeight)145 height = DiffListContextViewModel.calculateCollapsedHeight(items: items, heading: heading, availableWidth: availableWidth, innerPadding: innerPadding, contextItemPadding: DiffListContextViewModel.contextItemTextPadding, contextFont: contextFont, headingFont: headingFont)146 }147 148 private static func calculateInnerPadding(traitCollection: UITraitCollection) -> NSDirectionalEdgeInsets {149 switch (traitCollection.horizontalSizeClass, traitCollection.verticalSizeClass) {150 case (.regular, .regular):151 return NSDirectionalEdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50)152 default:153 return NSDirectionalEdgeInsets(top: 0, leading: 15, bottom: 0, trailing: 15)154 }155 }156 157 private static func calculateExpandedHeight(items: [DiffListContextItemViewModel?], heading: String, availableWidth: CGFloat, innerPadding: NSDirectionalEdgeInsets, contextItemPadding: NSDirectionalEdgeInsets, contextFont: UIFont, headingFont: UIFont, emptyContextLineHeight: CGFloat) -> CGFloat {158 var height: CGFloat = 0159 height = calculateCollapsedHeight(items: items, heading: heading, availableWidth: availableWidth, innerPadding: innerPadding, contextItemPadding: contextItemPadding, contextFont: contextFont, headingFont: headingFont)160 161 for (index, item) in items.enumerated() {162 163 height += contextItemPadding.top164 165 let itemTextHeight: CGFloat166 if let item = item {167 itemTextHeight = ceil(item.textAttributedString.boundingRect(with: CGSize(width: availableWidth, height: CGFloat.infinity), options: [.usesLineFragmentOrigin], context: nil).height)168 } else {169 itemTextHeight = emptyContextLineHeight170 }171 172 height += itemTextHeight173 height += contextItemPadding.bottom174 175 if index < (items.count - 1) {176 height += DiffListContextViewModel.contextItemStackSpacing177 }178 }179 180 height += DiffListContextViewModel.containerStackSpacing181 182 return height183 }184 185 private static func calculateCollapsedHeight(items: [DiffListContextItemViewModel?], heading: String, availableWidth: CGFloat, innerPadding: NSDirectionalEdgeInsets, contextItemPadding: NSDirectionalEdgeInsets, contextFont: UIFont, headingFont: UIFont) -> CGFloat {186 var height: CGFloat = 0187 188 //add heading height189 height += innerPadding.top190 let attributedString = NSAttributedString(string: heading, attributes: [NSAttributedString.Key.font: headingFont])191 height += ceil(attributedString.boundingRect(with: CGSize(width: availableWidth, height: CGFloat.infinity), options: [.usesLineFragmentOrigin], context: nil).height)192 193 height += innerPadding.bottom194 195 return height196 }197 198 func updateSize(width: CGFloat, traitCollection: UITraitCollection) {199 _width = width200 self.traitCollection = traitCollection201 202 expandedHeight = DiffListContextViewModel.calculateExpandedHeight(items: items, heading: heading, availableWidth: availableWidth, innerPadding: innerPadding, contextItemPadding: DiffListContextViewModel.contextItemTextPadding, contextFont: contextFont, headingFont: headingFont, emptyContextLineHeight: emptyContextLineHeight)203 height = DiffListContextViewModel.calculateCollapsedHeight(items: items, heading: heading, availableWidth: availableWidth, innerPadding: innerPadding, contextItemPadding: DiffListContextViewModel.contextItemTextPadding, contextFont: contextFont, headingFont: headingFont)204 }205}...

Full Screen

Full Screen

TokenizeOperation.swift

Source:TokenizeOperation.swift Github

copy

Full Screen

...15public protocol EmancipatedTokenizer {16 func scan(operation:TokenizeOperation)17}18public class TokenizeOperation : CustomStringConvertible {19 public class Context : CustomStringConvertible {20 var tokens = [Token]()21 var consumedCharacters : String {22 let substring = __sourceString[__startIndex..<__currentIndex]23 return substring24 }25 let states : [TokenizationState]26 private let __sourceString : String27 private var __startIndex : String.Index28 private var __currentIndex : String.Index29 var startPosition : Int30 var currentPosition : Int31 private init(atPosition:Int, withMarker:String.Index, withStates:[TokenizationState], forString:String){32 __startIndex = withMarker33 __currentIndex = __startIndex34 __sourceString = forString35 startPosition = atPosition36 currentPosition = atPosition37 states = withStates38 }39 internal func flushConsumedCharacters(){40 __startIndex = __currentIndex41 startPosition = currentPosition42 }43 public var description : String {44 return "Started at: \(startPosition), now at: \(currentPosition), having consumed \(consumedCharacters) and holding \(tokens)"45 }46 }47 var current : Character48 var next : Character?49 var scanAdvanced = false50 private var __tokenHandler : (Token)->Bool51 private let __startingStates : [TokenizationState]52 let eot : Character = "\u{04}"53 private var __marker : IndexingGenerator<String.CharacterView> {54 didSet{55 scanAdvanced = true56 }57 }58 private var __contextStack = [Context]()59 private var __sourceString : String60 var context : Context61 var complete : Bool {62 return current == eot63 }64 public var description : String {65 var output = "Tokenization Operation State\n\tCurrent=\(current) Next=\(next) scanAdvanced=\(scanAdvanced) Complete=\(complete)\n"66 //Print the context stack67 for var i = __contextStack.endIndex-1; i>=0; i -= 1 {68 output+="\t"+__contextStack[i].description+"\n"69 }70 return output71 }72 //For now, to help with compatibility73 init(legacyTokenizer:Tokenizer){74 __sourceString = "\u{0004}"75 __marker = __sourceString.characters.generate()76 current = __marker.next()!77 next = __marker.next()78 __startingStates = legacyTokenizer.branches79 __tokenHandler = {(token:Token)->Bool in80 print("No token handler specified")81 return false82 }83 context = Context(atPosition: 0, withMarker: "".startIndex, withStates: [], forString: "")84 }85 //86 // The primary entry point for the class, the token receiver will be called87 // whenever a token is published88 //89 public func tokenize(string:String, tokenReceiver : (Token)->(Bool)){90 __tokenHandler = tokenReceiver91 //Prepare string92 __sourceString = string93 __marker = __sourceString.characters.generate()94 //Prepare stack and context95 __contextStack.removeAll(keepCapacity: true)96 __contextStack.append(Context(atPosition: 0, withMarker:__sourceString.startIndex, withStates: __startingStates, forString:__sourceString))97 context = __contextStack[0]98 if let first = __marker.next() {99 current = first100 next = __marker.next()101 } else {102 return103 }104 scan(self)105 }106 func debug(operation:String=""){107 if __debugScanning {108 scanDebug("\(operation) \(self)")109 }110 }111 //112 // Moves forward in the supplied string113 //114 public func advance(){115 if next != nil {116 current = next!117 next = __marker.next()118 } else {119 current = eot120 }121 context.__currentIndex = context.__currentIndex.successor()122 context.currentPosition += 1123 }124 public func token(token:Token){125 if !(token is Token.EndOfTransmissionToken) {126 context.tokens.append(token)127 }128 context.startPosition = context.currentPosition129 context.__startIndex = context.__currentIndex130 debug("token()")131 }132 private func __publishTokens(inContext:Context)->Bool{133 //Do we need to do this at all?134 if inContext.tokens.count == 0 {135 return true136 }137 for token in inContext.tokens {138 if !__tokenHandler(token){139 inContext.tokens.removeAll(keepCapacity: true)140 return false141 }142 }143 inContext.tokens.removeAll(keepCapacity: true)144 debug("publishTokens()")145 return true146 }147 public func pushContext(states:[TokenizationState]){148 //Publish any tokens before moving into the new state149 __publishTokens(context)150 let newContext = Context(atPosition: context.currentPosition, withMarker:context.__currentIndex, withStates: states, forString:__sourceString)151 __contextStack.append(newContext)152 context = newContext153 debug("pushContext()")154 }155 public func popContext(publishTokens:Bool=true){156 let publishedTokens = publishTokens && context.tokens.count > 0157 if publishTokens {158 __publishTokens(context)159 }160 if __contextStack.count == 1 {161 debug("popContext()")162 return163 }164 let poppedState = __contextStack.removeLast()165 context = __contextStack[__contextStack.count-1]166 //If we didn't publish tokens merge in the new characters parsed so far167 if !publishedTokens {168 _ = __sourceString[context.__currentIndex..<poppedState.__currentIndex]169 _ = poppedState.consumedCharacters170 }171 //Update the now current context with the progress achieved by the popped state172 context.currentPosition = poppedState.currentPosition173 context.__currentIndex = poppedState.__currentIndex174 debug("popContext()")175 }176}177extension TokenizeOperation : EmancipatedTokenizer {178 public func scan(operation:TokenizeOperation) {179 scanAdvanced = true180 while scanAdvanced && !complete {181 scanAdvanced = false182 debug("rootScan Start")183 //Scan through our branches184 for tokenizer in context.states {185 tokenizer.scan(operation)186 if scanAdvanced {187 break188 }...

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Mockingbird2import XCTest3class ContextTests: XCTestCase {4 override func setUp() {5 super.setUp()6 context = Context()7 }8 override func tearDown() {9 super.tearDown()10 }11 func testContext() {12 XCTAssertNotNil(value)13 }14}15import Mockingbird16import XCTest17class ContextTests: XCTestCase {18 override func setUp() {19 super.setUp()20 context = Context()21 }22 override func tearDown() {23 super.tearDown()24 }25 func testContext() {26 XCTAssertNotNil(value)27 }28}29import Mockingbird30import XCTest31class ContextTests: XCTestCase {32 override func setUp() {33 super.setUp()34 context = Context()35 }36 override func tearDown() {37 super.tearDown()38 }39 func testContext() {40 XCTAssertNotNil(value)41 }42}43import Mockingbird44import XCTest45class ContextTests: XCTestCase {46 override func setUp() {47 super.setUp()48 context = Context()49 }50 override func tearDown() {51 super.tearDown()52 }53 func testContext() {54 XCTAssertNotNil(value)55 }56}57import Mockingbird58import XCTest59class ContextTests: XCTestCase {60 override func setUp() {61 super.setUp()62 context = Context()63 }64 override func tearDown() {65 super.tearDown()66 }67 func testContext() {68 XCTAssertNotNil(value)69 }70}71import Mockingbird72import XCTest73class ContextTests: XCTestCase {74 override func setUp() {

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Mockingbird2let context = Context()3context.addMockingbird("1.swift")4context.addMockingbird("2.swift")5context.addMockingbird("3.swift")6context.addMockingbird("4.swift")7context.addMockingbird("5.swift")8import Mockingbird9func printName(name: String) {10 print(name)11}12import Mockingbird13func printName(name: String) {14 print(name)15}16import Mockingbird17func printName(name: String) {18 print(name)19}20import Mockingbird21func printName(name: String) {22 print(name)23}24import Mockingbird25func printName(name: String) {26 print(name)27}28import Mockingbird29let context = Context()30context.addMockingbird("1.swift")31context.addMockingbird("2.swift")32context.addMockingbird("3.swift")33context.addMockingbird("4.swift")34context.addMockingbird("5.swift")35import Mockingbird36func printName(name: String) {37 print(name)38}39import Mockingbird40func printName(name: String) {41 print(name)42}43import Mockingbird44func printName(name: String) {45 print(name)46}47import Mockingbird48func printName(name: String) {49 print(name)50}51import Mockingbird52func printName(name: String) {53 print(name)54}55import Mockingbird56let context = Context()57context.addMockingbird("1.swift")58context.addMockingbird("2.swift")59context.addMockingbird("3.swift")

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Mockingbird2class Context {3 init(name : String) {4 }5 func printName() {6 println(name)7 }8}9import Mockingbird10class Context {11 init(name : String) {12 }13 func printName() {14 println(name)15 }16}17import Mockingbird18class Context {19 init(name : String) {20 }21 func printName() {22 println(name)23 }24}25import Mockingbird26class Context {27 init(name : String) {28 }29 func printName() {30 println(name)31 }32}33import Mockingbird34class Context {35 init(name : String) {36 }37 func printName() {38 println(name)39 }40}41import Mockingbird42class Context {43 init(name : String) {44 }45 func printName() {46 println(name)47 }48}49import Mockingbird50class Context {51 init(name : String) {52 }53 func printName() {54 println(name)55 }56}57import Mockingbird58class Context {59 init(name : String) {60 }61 func printName() {62 println(name)63 }64}65import Mockingbird66class Context {67 init(name :

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Context2let context = Context()3let result = context.get("test")4print(result)5import Context6let context = Context()7let result = context.get("test")8print(result)9import Context10let context = Context()11let result = context.get("test")12print(result)13import Context14let context = Context()15let result = context.get("test")16print(result)17import Context18let context = Context()19let result = context.get("test")20print(result)21import Context22let context = Context()23let result = context.get("test")24print(result)25import Context26let context = Context()27let result = context.get("test")28print(result)29import Context30let context = Context()31let result = context.get("test")32print(result)33import Context34let context = Context()35let result = context.get("test")36print(result)37import Context38let context = Context()39let result = context.get("test")40print(result)41import Context42let context = Context()43let result = context.get("test")44print(result)45import Context46let context = Context()47let result = context.get("test")48print(result)49import Context50let context = Context()51let result = context.get("test")52print(result)53import Context54let context = Context()55let result = context.get("test")

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Mockingbird2let context = Context()3let mock = context.mock(TestClass.self)4context.stub(mock.method1()).andReturn(1)5context.stub(mock.method2()).andReturn(2)6context.stub(mock.method3()).andReturn(3)7context.stub(mock.method4()).andReturn(4)8context.stub(mock.method5()).andReturn(5)9context.stub(mock.method6()).andReturn(6)10context.stub(mock.method7()).andReturn(7)11context.stub(mock.method8()).andReturn(8)12context.stub(mock.method9()).andReturn(9)13context.stub(mock.method10()).andReturn(10)14context.stub(mock.method11()).andReturn(11)15context.stub(mock.method12()).andReturn(12)16context.stub(mock.method13()).andReturn(13)17context.stub(mock.method14()).andReturn(14)18context.stub(mock.method15()).andReturn(15)19context.stub(mock.method16()).andReturn(16)20context.stub(mock.method17()).andReturn(17)21context.stub(mock.method18()).andReturn(18)22context.stub(mock.method19()).andReturn(19)23context.stub(mock.method20()).andReturn(20)24context.stub(mock.method21()).andReturn(21)25context.stub(mock.method22()).andReturn(22)26context.stub(mock.method23()).andReturn(23)27context.stub(mock.method24()).andReturn(24)28context.stub(mock.method25()).andReturn(25)29context.stub(mock.method26()).andReturn(26)30context.stub(mock.method27()).andReturn(27)31context.stub(mock.method28()).andReturn(28)32context.stub(mock.method29()).andReturn(29)33context.stub(mock.method30()).andReturn(30)34context.stub(mock.method31()).andReturn(31)35context.stub(mock.method32()).andReturn(32)36context.stub(mock.method33()).andReturn(33)37context.stub(mock.method34()).andReturn(34)38context.stub(mock.method35()).andReturn(35)39context.stub(mock.method36()).andReturn(36)40context.stub(mock.method37()).andReturn(37)41context.stub(mock.method38()).andReturn(38)42context.stub(mock.method39()).andReturn(39)43context.stub(mock.method40()).andReturn(40)44context.stub(mock.method41()).andReturn(41)45context.stub(mock.method42()).andReturn(42)46context.stub(mock.method43()).andReturn(43)47context.stub(mock.method44()).andReturn(44)48context.stub(mock.method45()).andReturn(45)49context.stub(mock.method46()).andReturn(46)50context.stub(mock.method47()).andReturn(47)51context.stub(mock.method48()).andReturn(48

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Mockingbird2let context = Context()3context.setMockingbirdPath("Mockingbird.swift")4context.setMockingbirdPath("1.swift")5context.setMockingbirdPath("2.swift")6context.setMockingbirdPath("3.swift")7context.setMockingbirdPath("4.swift")8context.setMockingbirdPath("5.swift")9context.setMockingbirdPath("6.swift")10context.setMockingbirdPath("7.swift")11context.setMockingbirdPath("8.swift")12context.setMockingbirdPath("9.swift")13context.setMockingbirdPath("10.swift")14context.setMockingbirdPath("11.swift")15context.setMockingbirdPath("12.swift")16context.setMockingbirdPath("13.swift")17context.setMockingbirdPath("14.swift")18context.setMockingbirdPath("15.swift")19context.setMockingbirdPath("16.swift")20context.setMockingbirdPath("17.swift")21context.setMockingbirdPath("18.swift")22context.setMockingbirdPath("19.swift")23context.setMockingbirdPath("20.swift")24context.setMockingbirdPath("21.swift")25context.setMockingbirdPath("22.swift")26context.setMockingbirdPath("23.swift")27context.setMockingbirdPath("24.swift")28context.setMockingbirdPath("25.swift")29context.setMockingbirdPath("26.swift")30context.setMockingbirdPath("27.swift")31context.setMockingbirdPath("28.swift")32context.setMockingbirdPath("29.swift")33context.setMockingbirdPath("30.swift")34context.setMockingbirdPath("31.swift")35context.setMockingbirdPath("32.swift")36context.setMockingbirdPath("33.swift")37context.setMockingbirdPath("34.swift")38context.setMockingbirdPath("35.swift")39context.setMockingbirdPath("36.swift")40context.setMockingbirdPath("37.swift")41context.setMockingbirdPath("38.swift")42context.setMockingbirdPath("39.swift")43context.setMockingbirdPath("40.swift")44context.setMockingbirdPath("41.swift")45context.setMockingbirdPath("42.swift")46context.setMockingbirdPath("43.swift")47context.setMockingbirdPath("44.swift")48context.setMockingbirdPath("45.swift")49context.setMockingbirdPath("46.swift")50context.setMockingbirdPath("47.swift")

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Context2let context = Context()3let result = context.execute("1 + 1")4print("Result: \(result)")5import Context6let context = Context()7let result = context.execute("1 + 1")8print("Result: \(result)")9import Context10let context = Context()11let result = context.execute("1 + 1")12print("Result: \(result)")13import Context14let context = Context()15let result = context.execute("1 + 1")16print("Result: \(result)")17import Context18let context = Context()19let result = context.execute("1 + 1")20print("Result: \(result)")21import Context22let context = Context()23let result = context.execute("1 + 1")24print("Result: \(result)")25import Context26let context = Context()27let result = context.execute("1 + 1")28print("Result: \(result)")29import Context30let context = Context()31let result = context.execute("1 + 1")32print("Result: \(result)")33import Context34let context = Context()35let result = context.execute("1 + 1")36print("Result: \(result)")37import Context

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1import Mockingbird2import XCTest3class ContextTests: XCTestCase {4 func testContext() {5 let context = Context()6 let mock = mock(MyProtocol.self)7 context.register(mock)8 let mock2 = context.resolve(MyProtocol.self)9 XCTAssert(mock === mock2)10 }11}12import Mockingbird13import XCTest14class ContextTests: XCTestCase {15 func testContext() {16 let context = Context()17 let mock = mock(MyProtocol.self)18 context.register(mock)19 let mock2 = context.resolve(MyProtocol.self)20 XCTAssert(mock === mock2)21 }22}23import Mockingbird24import XCTest25class ContextTests: XCTestCase {26 func testContext() {27 let context = Context()28 let mock = mock(MyProtocol.self)29 context.register(mock)30 let mock2 = context.resolve(MyProtocol.self)31 XCTAssert(mock === mock2)32 }33}34import Mockingbird35import XCTest36class ContextTests: XCTestCase {37 func testContext() {38 let context = Context()39 let mock = mock(MyProtocol.self)40 context.register(mock)41 let mock2 = context.resolve(MyProtocol.self)42 XCTAssert(mock === mock2)43 }44}45import Mockingbird

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Mockingbird automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Most used methods in Context

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful