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  # Dotted Names
165
166  - name: Dotted Names - Truthy
167    desc: Dotted names should be valid for Section tags.
168    data: { a: { b: { c: true } } }
169    template: '"{{#a.b.c}}Here{{/a.b.c}}" == "Here"'
170    expected: '"Here" == "Here"'
171
172  - name: Dotted Names - Falsey
173    desc: Dotted names should be valid for Section tags.
174    data: { a: { b: { c: false } } }
175    template: '"{{#a.b.c}}Here{{/a.b.c}}" == ""'
176    expected: '"" == ""'
177
178  - name: Dotted Names - Broken Chains
179    desc: Dotted names that cannot be resolved should be considered falsey.
180    data: { a: { } }
181    template: '"{{#a.b.c}}Here{{/a.b.c}}" == ""'
182    expected: '"" == ""'
183
184  # Whitespace Sensitivity
185
186  - name: Surrounding Whitespace
187    desc: Sections should not alter surrounding whitespace.
188    data: { boolean: true }
189    template: " | {{#boolean}}\t|\t{{/boolean}} | \n"
190    expected: " | \t|\t | \n"
191
192  - name: Internal Whitespace
193    desc: Sections should not alter internal whitespace.
194    data: { boolean: true }
195    template: " | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n"
196    expected: " |  \n  | \n"
197
198  - name: Indented Inline Sections
199    desc: Single-line sections should not alter surrounding whitespace.
200    data: { boolean: true }
201    template: " {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n"
202    expected: " YES\n GOOD\n"
203
204  - name: Standalone Lines
205    desc: Standalone lines should be removed from the template.
206    data: { boolean: true }
207    template: |
208      | This Is
209      {{#boolean}}
210      |
211      {{/boolean}}
212      | A Line
213    expected: |
214      | This Is
215      |
216      | A Line
217
218  - name: Indented Standalone Lines
219    desc: Indented standalone lines should be removed from the template.
220    data: { boolean: true }
221    template: |
222      | This Is
223        {{#boolean}}
224      |
225        {{/boolean}}
226      | A Line
227    expected: |
228      | This Is
229      |
230      | A Line
231
232  - name: Standalone Line Endings
233    desc: '"\r\n" should be considered a newline for standalone tags.'
234    data: { boolean: true }
235    template: "|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|"
236    expected: "|\r\n|"
237
238  - name: Standalone Without Previous Line
239    desc: Standalone tags should not require a newline to precede them.
240    data: { boolean: true }
241    template: "  {{#boolean}}\n#{{/boolean}}\n/"
242    expected: "#\n/"
243
244  - name: Standalone Without Newline
245    desc: Standalone tags should not require a newline to follow them.
246    data: { boolean: true }
247    template: "#{{#boolean}}\n/\n  {{/boolean}}"
248    expected: "#\n/\n"
249
250  # Whitespace Insensitivity
251
252  - name: Padding
253    desc: Superfluous in-tag whitespace should be ignored.
254    data: { boolean: true }
255    template: '|{{# boolean }}={{/ boolean }}|'
256    expected: '|=|'
257