1overview: |
2  Section tags and End Section tags are used in combination to wrap a section
3  of the template for iteration
4
5  These tags' content MUST be a non-whitespace character sequence NOT
6  containing the current closing delimiter; each Section tag MUST be followed
7  by an End Section tag with the same content within the same section.
8
9  This tag's content names the data to replace the tag.  Name resolution is as
10  follows:
11    1) Split the name on periods; the first part is the name to resolve, any
12    remaining parts should be retained.
13    2) Walk the context stack from top to bottom, finding the first context
14    that is a) a hash containing the name as a key OR b) an object responding
15    to a method with the given name.
16    3) If the context is a hash, the data is the value associated with the
17    name.
18    4) If the context is an object and the method with the given name has an
19    arity of 1, the method SHOULD be called with a String containing the
20    unprocessed contents of the sections; the data is the value returned.
21    5) Otherwise, the data is the value returned by calling the method with
22    the given name.
23    6) If any name parts were retained in step 1, each should be resolved
24    against a context stack containing only the result from the former
25    resolution.  If any part fails resolution, the result should be considered
26    falsey, and should interpolate as the empty string.
27  If the data is not of a list type, it is coerced into a list as follows: if
28  the data is truthy (e.g. `!!data == true`), use a single-element list
29  containing the data, otherwise use an empty list.
30
31  For each element in the data list, the element MUST be pushed onto the
32  context stack, the section MUST be rendered, and the element MUST be popped
33  off the context stack.
34
35  Section and End Section tags SHOULD be treated as standalone when
36  appropriate.
37tests:
38  - name: Truthy
39    desc: Truthy sections should have their contents rendered.
40    data: { boolean: true }
41    template: '"{{#boolean}}This should be rendered.{{/boolean}}"'
42    expected: '"This should be rendered."'
43
44  - name: Falsey
45    desc: Falsey sections should have their contents omitted.
46    data: { boolean: false }
47    template: '"{{#boolean}}This should not be rendered.{{/boolean}}"'
48    expected: '""'
49
50  - name: Context
51    desc: Objects and hashes should be pushed onto the context stack.
52    data: { context: { name: 'Joe' } }
53    template: '"{{#context}}Hi {{name}}.{{/context}}"'
54    expected: '"Hi Joe."'
55
56  - name: Deeply Nested Contexts
57    desc: All elements on the context stack should be accessible.
58    data:
59      a: { one: 1 }
60      b: { two: 2 }
61      c: { three: 3 }
62      d: { four: 4 }
63      e: { five: 5 }
64    template: |
65      {{#a}}
66      {{one}}
67      {{#b}}
68      {{one}}{{two}}{{one}}
69      {{#c}}
70      {{one}}{{two}}{{three}}{{two}}{{one}}
71      {{#d}}
72      {{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
73      {{#e}}
74      {{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}
75      {{/e}}
76      {{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
77      {{/d}}
78      {{one}}{{two}}{{three}}{{two}}{{one}}
79      {{/c}}
80      {{one}}{{two}}{{one}}
81      {{/b}}
82      {{one}}
83      {{/a}}
84    expected: |
85      1
86      121
87      12321
88      1234321
89      123454321
90      1234321
91      12321
92      121
93      1
94
95  - name: List
96    desc: Lists should be iterated; list items should visit the context stack.
97    data: { list: [ { item: 1 }, { item: 2 }, { item: 3 } ] }
98    template: '"{{#list}}{{item}}{{/list}}"'
99    expected: '"123"'
100
101  - name: Empty List
102    desc: Empty lists should behave like falsey values.
103    data: { list: [ ] }
104    template: '"{{#list}}Yay lists!{{/list}}"'
105    expected: '""'
106
107  - name: Doubled
108    desc: Multiple sections per template should be permitted.
109    data: { bool: true, two: 'second' }
110    template: |
111      {{#bool}}
112      * first
113      {{/bool}}
114      * {{two}}
115      {{#bool}}
116      * third
117      {{/bool}}
118    expected: |
119      * first
120      * second
121      * third
122
123  - name: Nested (Truthy)
124    desc: Nested truthy sections should have their contents rendered.
125    data: { bool: true }
126    template: "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |"
127    expected: "| A B C D E |"
128
129  - name: Nested (Falsey)
130    desc: Nested falsey sections should be omitted.
131    data: { bool: false }
132    template: "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |"
133    expected: "| A  E |"
134
135  - name: Context Misses
136    desc: Failed context lookups should be considered falsey.
137    data: { }
138    template: "[{{#missing}}Found key 'missing'!{{/missing}}]"
139    expected: "[]"
140
141  # Implicit Iterators
142
143  - name: Implicit Iterator - String
144    desc: Implicit iterators should directly interpolate strings.
145    data:
146      list: [ 'a', 'b', 'c', 'd', 'e' ]
147    template: '"{{#list}}({{.}}){{/list}}"'
148    expected: '"(a)(b)(c)(d)(e)"'
149
150  - name: Implicit Iterator - Integer
151    desc: Implicit iterators should cast integers to strings and interpolate.
152    data:
153      list: [ 1, 2, 3, 4, 5 ]
154    template: '"{{#list}}({{.}}){{/list}}"'
155    expected: '"(1)(2)(3)(4)(5)"'
156
157  - name: Implicit Iterator - Decimal
158    desc: Implicit iterators should cast decimals to strings and interpolate.
159    data:
160      list: [ 1.10, 2.20, 3.30, 4.40, 5.50 ]
161    template: '"{{#list}}({{.}}){{/list}}"'
162    expected: '"(1.1)(2.2)(3.3)(4.4)(5.5)"'
163
164  - name: Implicit Iterator - Array
165    desc: Implicit iterators should allow iterating over nested arrays.
166    data:
167      list: [ [1, 2, 3], ['a', 'b', 'c'] ]
168    template: '"{{#list}}({{#.}}{{.}}{{/.}}){{/list}}"'
169    expected: '"(123)(abc)"'
170
171  # Dotted Names
172
173  - name: Dotted Names - Truthy
174    desc: Dotted names should be valid for Section tags.
175    data: { a: { b: { c: true } } }
176    template: '"{{#a.b.c}}Here{{/a.b.c}}" == "Here"'
177    expected: '"Here" == "Here"'
178
179  - name: Dotted Names - Falsey
180    desc: Dotted names should be valid for Section tags.
181    data: { a: { b: { c: false } } }
182    template: '"{{#a.b.c}}Here{{/a.b.c}}" == ""'
183    expected: '"" == ""'
184
185  - name: Dotted Names - Broken Chains
186    desc: Dotted names that cannot be resolved should be considered falsey.
187    data: { a: { } }
188    template: '"{{#a.b.c}}Here{{/a.b.c}}" == ""'
189    expected: '"" == ""'
190
191  # Whitespace Sensitivity
192
193  - name: Surrounding Whitespace
194    desc: Sections should not alter surrounding whitespace.
195    data: { boolean: true }
196    template: " | {{#boolean}}\t|\t{{/boolean}} | \n"
197    expected: " | \t|\t | \n"
198
199  - name: Internal Whitespace
200    desc: Sections should not alter internal whitespace.
201    data: { boolean: true }
202    template: " | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n"
203    expected: " |  \n  | \n"
204
205  - name: Indented Inline Sections
206    desc: Single-line sections should not alter surrounding whitespace.
207    data: { boolean: true }
208    template: " {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n"
209    expected: " YES\n GOOD\n"
210
211  - name: Standalone Lines
212    desc: Standalone lines should be removed from the template.
213    data: { boolean: true }
214    template: |
215      | This Is
216      {{#boolean}}
217      |
218      {{/boolean}}
219      | A Line
220    expected: |
221      | This Is
222      |
223      | A Line
224
225  - name: Indented Standalone Lines
226    desc: Indented standalone lines should be removed from the template.
227    data: { boolean: true }
228    template: |
229      | This Is
230        {{#boolean}}
231      |
232        {{/boolean}}
233      | A Line
234    expected: |
235      | This Is
236      |
237      | A Line
238
239  - name: Standalone Line Endings
240    desc: '"\r\n" should be considered a newline for standalone tags.'
241    data: { boolean: true }
242    template: "|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|"
243    expected: "|\r\n|"
244
245  - name: Standalone Without Previous Line
246    desc: Standalone tags should not require a newline to precede them.
247    data: { boolean: true }
248    template: "  {{#boolean}}\n#{{/boolean}}\n/"
249    expected: "#\n/"
250
251  - name: Standalone Without Newline
252    desc: Standalone tags should not require a newline to follow them.
253    data: { boolean: true }
254    template: "#{{#boolean}}\n/\n  {{/boolean}}"
255    expected: "#\n/\n"
256
257  # Whitespace Insensitivity
258
259  - name: Padding
260    desc: Superfluous in-tag whitespace should be ignored.
261    data: { boolean: true }
262    template: '|{{# boolean }}={{/ boolean }}|'
263    expected: '|=|'
264