flavour_saver 4.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +14 -0
- data/.standard.yml +1 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +48 -5
- data/README.md +153 -271
- data/flavour_saver.gemspec +17 -19
- data/lib/flavour_saver/error.rb +5 -0
- data/lib/flavour_saver/helpers.rb +19 -18
- data/lib/flavour_saver/lexer.rb +123 -50
- data/lib/flavour_saver/nodes.rb +107 -19
- data/lib/flavour_saver/parser.rb +339 -161
- data/lib/flavour_saver/partial.rb +4 -6
- data/lib/flavour_saver/rails_partial.rb +10 -4
- data/lib/flavour_saver/runtime.rb +76 -81
- data/lib/flavour_saver/template.rb +3 -5
- data/lib/flavour_saver/version.rb +1 -1
- data/lib/flavour_saver.rb +17 -17
- data/spec/acceptance/backtrack_spec.rb +9 -9
- data/spec/acceptance/comment_spec.rb +7 -7
- data/spec/acceptance/custom_block_helper_spec.rb +12 -12
- data/spec/acceptance/custom_helper_spec.rb +8 -8
- data/spec/acceptance/ensure_no_rce_spec.rb +64 -53
- data/spec/acceptance/handlebars_qunit_spec.rb +295 -297
- data/spec/acceptance/if_else_spec.rb +6 -6
- data/spec/acceptance/multi_level_with_spec.rb +10 -11
- data/spec/acceptance/nesting_depth_spec.rb +34 -0
- data/spec/acceptance/one_character_identifier_spec.rb +8 -8
- data/spec/acceptance/raw_block_spec.rb +7 -7
- data/spec/acceptance/sections_spec.rb +17 -14
- data/spec/acceptance/segment_literals_spec.rb +5 -5
- data/spec/acceptance/simple_expression_spec.rb +9 -9
- data/spec/acceptance/subexpression_spec.rb +18 -17
- data/spec/acceptance/unless_spec.rb +19 -6
- data/spec/lib/flavour_saver/lexer_spec.rb +103 -89
- data/spec/lib/flavour_saver/nodes_spec.rb +85 -0
- data/spec/lib/flavour_saver/parser_spec.rb +155 -95
- data/spec/lib/flavour_saver/runtime_spec.rb +94 -96
- data/spec/lib/flavour_saver/template_spec.rb +1 -2
- metadata +19 -48
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
# to run against FlavourSaver. Yes, this is a more brittle way of
|
|
3
3
|
# doing it.
|
|
4
4
|
|
|
5
|
-
require
|
|
6
|
-
|
|
5
|
+
require "active_support"
|
|
6
|
+
require "active_support/core_ext/string/output_safety"
|
|
7
7
|
|
|
8
|
-
require
|
|
8
|
+
require "flavour_saver"
|
|
9
9
|
|
|
10
10
|
describe FlavourSaver do
|
|
11
11
|
let(:context) { double(:context) }
|
|
@@ -18,85 +18,85 @@ describe FlavourSaver do
|
|
|
18
18
|
describe "basic context" do
|
|
19
19
|
before { FS.register_helper(:link_to) { "<a>#{context}</a>" } }
|
|
20
20
|
|
|
21
|
-
describe
|
|
21
|
+
describe "most basic" do
|
|
22
22
|
let(:template) { "{{foo}}" }
|
|
23
23
|
|
|
24
24
|
it 'returns "foo"' do
|
|
25
|
-
allow(context).to receive(:foo).and_return(
|
|
26
|
-
expect(subject).to eq
|
|
25
|
+
allow(context).to receive(:foo).and_return("foo")
|
|
26
|
+
expect(subject).to eq "foo"
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
describe
|
|
30
|
+
describe "compiling with a basic context" do
|
|
31
31
|
let(:template) { "Goodbye\n{{cruel}}\n{{world}}!" }
|
|
32
32
|
|
|
33
|
-
it
|
|
34
|
-
expect(context).to receive(:cruel).and_return(
|
|
35
|
-
expect(context).to receive(:world).and_return(
|
|
33
|
+
it "it works if all the required keys are provided" do
|
|
34
|
+
expect(context).to receive(:cruel).and_return("cruel")
|
|
35
|
+
expect(context).to receive(:world).and_return("world")
|
|
36
36
|
expect(subject).to eq "Goodbye\ncruel\nworld!"
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
describe
|
|
41
|
-
let(:template) {"{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!"}
|
|
40
|
+
describe "comments" do
|
|
41
|
+
let(:template) { "{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!" }
|
|
42
42
|
|
|
43
|
-
it
|
|
44
|
-
expect(context).to receive(:cruel).and_return(
|
|
45
|
-
expect(context).to receive(:world).and_return(
|
|
43
|
+
it "comments are ignored" do
|
|
44
|
+
expect(context).to receive(:cruel).and_return("cruel")
|
|
45
|
+
expect(context).to receive(:world).and_return("world")
|
|
46
46
|
expect(subject).to eq "Goodbye\ncruel\nworld!"
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
describe
|
|
50
|
+
describe "boolean" do
|
|
51
51
|
let(:template) { "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!" }
|
|
52
52
|
|
|
53
|
-
it
|
|
53
|
+
it "booleans show the contents when true" do
|
|
54
54
|
allow(context).to receive(:goodbye).and_return(true)
|
|
55
|
-
allow(context).to receive(:world).and_return(
|
|
55
|
+
allow(context).to receive(:world).and_return("world")
|
|
56
56
|
expect(subject).to eq "GOODBYE cruel world!"
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
it
|
|
59
|
+
it "booleans do not show the contents when false" do
|
|
60
60
|
allow(context).to receive(:goodbye).and_return(false)
|
|
61
|
-
allow(context).to receive(:world).and_return(
|
|
61
|
+
allow(context).to receive(:world).and_return("world")
|
|
62
62
|
expect(subject).to eq "cruel world!"
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
describe
|
|
67
|
-
describe
|
|
68
|
-
let
|
|
66
|
+
describe "zeros" do
|
|
67
|
+
describe "{num1: 42, num2: 0}" do
|
|
68
|
+
let(:template) { "num1: {{num1}}, num2: {{num2}}" }
|
|
69
69
|
|
|
70
70
|
it 'should compile to "num1: 42, num2: 0"' do
|
|
71
71
|
allow(context).to receive(:num1).and_return(42)
|
|
72
72
|
allow(context).to receive(:num2).and_return(0)
|
|
73
|
-
expect(subject).to eq
|
|
73
|
+
expect(subject).to eq "num1: 42, num2: 0"
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
describe 0 do
|
|
78
|
-
let
|
|
78
|
+
let(:template) { "num: {{.}}" }
|
|
79
79
|
|
|
80
80
|
it 'should compile to "num: 0"' do
|
|
81
|
-
expect(FlavourSaver.evaluate(template,0)).to eq
|
|
81
|
+
expect(FlavourSaver.evaluate(template, 0)).to eq "num: 0"
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
describe
|
|
86
|
-
let(:template) {
|
|
85
|
+
describe "{num1: {num2: 0}}" do
|
|
86
|
+
let(:template) { "num: {{num1/num2}}" }
|
|
87
87
|
|
|
88
88
|
it 'should compile to "num: 0"' do
|
|
89
89
|
allow(context).to receive_message_chain(:num1, :num2).and_return(0)
|
|
90
|
-
expect(subject).to eq
|
|
90
|
+
expect(subject).to eq "num: 0"
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
-
describe
|
|
95
|
+
describe "newlines" do
|
|
96
96
|
describe '\n' do
|
|
97
97
|
let(:template) { "Alan's\nTest" }
|
|
98
98
|
|
|
99
|
-
it
|
|
99
|
+
it "works" do
|
|
100
100
|
expect(subject).to eq "Alan's\nTest"
|
|
101
101
|
end
|
|
102
102
|
end
|
|
@@ -104,22 +104,22 @@ describe FlavourSaver do
|
|
|
104
104
|
describe '\r' do
|
|
105
105
|
let(:template) { "Alan's\rTest" }
|
|
106
106
|
|
|
107
|
-
it
|
|
107
|
+
it "works" do
|
|
108
108
|
expect(subject).to eq "Alan's\rTest"
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
describe
|
|
114
|
-
describe
|
|
115
|
-
let(:template) {"Awesome's"}
|
|
113
|
+
describe "esaping text" do
|
|
114
|
+
describe "apostrophes" do
|
|
115
|
+
let(:template) { "Awesome's" }
|
|
116
116
|
|
|
117
117
|
it "text is escapes so that it doesn't get caught in single quites" do
|
|
118
118
|
expect(subject).to eq "Awesome's"
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
describe
|
|
122
|
+
describe "backslashes" do
|
|
123
123
|
let(:template) { "Awesome \\" }
|
|
124
124
|
|
|
125
125
|
it "text is escaped so that the closing quote can't be ignored" do
|
|
@@ -127,7 +127,7 @@ describe FlavourSaver do
|
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
describe
|
|
130
|
+
describe "more backslashes" do
|
|
131
131
|
let(:template) { "Awesome\\\\ foo" }
|
|
132
132
|
|
|
133
133
|
it "text is escapes so that it doesn't mess up the backslashes" do
|
|
@@ -135,16 +135,16 @@ describe FlavourSaver do
|
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
describe
|
|
138
|
+
describe "helper output containing backslashes" do
|
|
139
139
|
let(:template) { "Awesome {{foo}}" }
|
|
140
140
|
|
|
141
141
|
it "text is escaped so that it doesn't mess up backslashes" do
|
|
142
|
-
allow(context).to receive(:foo).and_return(
|
|
142
|
+
allow(context).to receive(:foo).and_return("\\")
|
|
143
143
|
expect(subject).to eq "Awesome \\"
|
|
144
144
|
end
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
-
describe
|
|
147
|
+
describe "doubled quotes" do
|
|
148
148
|
let(:template) { ' " " ' }
|
|
149
149
|
|
|
150
150
|
it "double quotes never produce invalid javascript" do
|
|
@@ -153,8 +153,8 @@ describe FlavourSaver do
|
|
|
153
153
|
end
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
-
describe
|
|
157
|
-
describe
|
|
156
|
+
describe "escaping expressions" do
|
|
157
|
+
describe "expressions with 3 handlebars" do
|
|
158
158
|
let(:template) { "{{{awesome}}}" }
|
|
159
159
|
|
|
160
160
|
it "shouldn't be escaped" do
|
|
@@ -163,7 +163,7 @@ describe FlavourSaver do
|
|
|
163
163
|
end
|
|
164
164
|
end
|
|
165
165
|
|
|
166
|
-
describe
|
|
166
|
+
describe "expressions with {{& handlebars" do
|
|
167
167
|
let(:template) { "{{&awesome}}" }
|
|
168
168
|
|
|
169
169
|
it "shouldn't be escaped" do
|
|
@@ -172,12 +172,12 @@ describe FlavourSaver do
|
|
|
172
172
|
end
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
-
describe
|
|
175
|
+
describe "expressions" do
|
|
176
176
|
let(:template) { "{{awesome}}" }
|
|
177
177
|
|
|
178
178
|
it "should be escaped" do
|
|
179
179
|
allow(context).to receive(:awesome).and_return("&\"'`\\<>")
|
|
180
|
-
if RUBY_VERSION >=
|
|
180
|
+
if RUBY_VERSION >= "2.0.0"
|
|
181
181
|
expect(subject).to eq "&"'`\\<>"
|
|
182
182
|
else
|
|
183
183
|
expect(subject).to eq "&"'`\\<>"
|
|
@@ -185,7 +185,7 @@ describe FlavourSaver do
|
|
|
185
185
|
end
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
describe
|
|
188
|
+
describe "ampersands" do
|
|
189
189
|
let(:template) { "{{awesome}}" }
|
|
190
190
|
|
|
191
191
|
it "should be escaped" do
|
|
@@ -204,7 +204,7 @@ describe FlavourSaver do
|
|
|
204
204
|
end
|
|
205
205
|
end
|
|
206
206
|
|
|
207
|
-
describe
|
|
207
|
+
describe "functions" do
|
|
208
208
|
let(:template) { "{{awesome}}" }
|
|
209
209
|
|
|
210
210
|
it "are called and render their output" do
|
|
@@ -213,127 +213,127 @@ describe FlavourSaver do
|
|
|
213
213
|
end
|
|
214
214
|
end
|
|
215
215
|
|
|
216
|
-
describe
|
|
217
|
-
describe
|
|
216
|
+
describe "paths with hyphens" do
|
|
217
|
+
describe "{{foo-bar}}" do
|
|
218
218
|
let(:template) { "{{foo-bar}}" }
|
|
219
219
|
|
|
220
|
-
it
|
|
221
|
-
expect(context).to receive(:[]).with(
|
|
222
|
-
expect(subject).to eq
|
|
220
|
+
it "paths can contain hyphens (-)" do
|
|
221
|
+
expect(context).to receive(:[]).with("foo-bar").and_return("baz")
|
|
222
|
+
expect(subject).to eq "baz"
|
|
223
223
|
end
|
|
224
224
|
end
|
|
225
225
|
|
|
226
|
-
describe
|
|
226
|
+
describe "{{foo.foo-bar}}" do
|
|
227
227
|
let(:template) { "{{foo.foo-bar}}" }
|
|
228
228
|
|
|
229
|
-
it
|
|
230
|
-
allow(context).to receive_message_chain(:foo, :[]).with(
|
|
231
|
-
expect(subject).to eq
|
|
229
|
+
it "paths can contain hyphens (-)" do
|
|
230
|
+
allow(context).to receive_message_chain(:foo, :[]).with("foo-bar").and_return(proc { "baz" })
|
|
231
|
+
expect(subject).to eq "baz"
|
|
232
232
|
end
|
|
233
233
|
end
|
|
234
234
|
|
|
235
|
-
describe
|
|
235
|
+
describe "{{foo/foo-bar}}" do
|
|
236
236
|
let(:template) { "{{foo/foo-bar}}" }
|
|
237
237
|
|
|
238
|
-
it
|
|
239
|
-
allow(context).to receive_message_chain(:foo, :[]).with(
|
|
240
|
-
expect(subject).to eq
|
|
238
|
+
it "paths can contain hyphens (-)" do
|
|
239
|
+
allow(context).to receive_message_chain(:foo, :[]).with("foo-bar").and_return("baz")
|
|
240
|
+
expect(subject).to eq "baz"
|
|
241
241
|
end
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
-
describe
|
|
245
|
-
let(:template) {"Goodbye {{alan/expression}} world!"}
|
|
244
|
+
describe "nested paths" do
|
|
245
|
+
let(:template) { "Goodbye {{alan/expression}} world!" }
|
|
246
246
|
|
|
247
|
-
it
|
|
248
|
-
allow(context).to receive_message_chain(:alan, :expression).and_return(
|
|
249
|
-
expect(subject).to eq
|
|
247
|
+
it "access nested object" do
|
|
248
|
+
allow(context).to receive_message_chain(:alan, :expression).and_return("beautiful")
|
|
249
|
+
expect(subject).to eq "Goodbye beautiful world!"
|
|
250
250
|
end
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
-
describe
|
|
254
|
-
let(:template) {"Goodbye {{alan/expression}} world!"}
|
|
253
|
+
describe "nested path with empty string value" do
|
|
254
|
+
let(:template) { "Goodbye {{alan/expression}} world!" }
|
|
255
255
|
|
|
256
|
-
it
|
|
257
|
-
allow(context).to receive_message_chain(:alan, :expression).and_return(
|
|
258
|
-
expect(subject).to eq
|
|
256
|
+
it "access nested object" do
|
|
257
|
+
allow(context).to receive_message_chain(:alan, :expression).and_return("")
|
|
258
|
+
expect(subject).to eq "Goodbye world!"
|
|
259
259
|
end
|
|
260
260
|
end
|
|
261
261
|
|
|
262
|
-
describe
|
|
262
|
+
describe "literal paths" do
|
|
263
263
|
let(:template) { "Goodbye {{[@alan]/expression}} world!" }
|
|
264
264
|
|
|
265
|
-
it
|
|
265
|
+
it "literal paths can be used" do
|
|
266
266
|
alan = double(:alan)
|
|
267
|
-
expect(context).to receive(:[]).with(
|
|
268
|
-
expect(alan).to receive(:expression).and_return(
|
|
269
|
-
expect(subject).to eq
|
|
267
|
+
expect(context).to receive(:[]).with("@alan").and_return(alan)
|
|
268
|
+
expect(alan).to receive(:expression).and_return("beautiful")
|
|
269
|
+
expect(subject).to eq "Goodbye beautiful world!"
|
|
270
270
|
end
|
|
271
271
|
end
|
|
272
272
|
|
|
273
|
-
describe
|
|
274
|
-
let(:template) {
|
|
273
|
+
describe "complex but empty paths" do
|
|
274
|
+
let(:template) { "{{person/name}}" }
|
|
275
275
|
|
|
276
|
-
it
|
|
277
|
-
allow(context).to receive_message_chain(:person
|
|
278
|
-
expect(subject).to eq
|
|
276
|
+
it "returns empty string from nested paths" do
|
|
277
|
+
allow(context).to receive_message_chain(:person, :name).and_return("")
|
|
278
|
+
expect(subject).to eq ""
|
|
279
279
|
end
|
|
280
280
|
|
|
281
|
-
it
|
|
282
|
-
allow(context).to receive_message_chain(:person
|
|
283
|
-
expect(subject).to eq
|
|
281
|
+
it "returns empty string from nil objects" do
|
|
282
|
+
allow(context).to receive_message_chain(:person, :name)
|
|
283
|
+
expect(subject).to eq ""
|
|
284
284
|
end
|
|
285
285
|
end
|
|
286
286
|
|
|
287
287
|
describe '"this" keyword' do
|
|
288
|
-
describe
|
|
288
|
+
describe "in a block" do
|
|
289
289
|
let(:template) { "{{#goodbyes}}{{this}}{{/goodbyes}}" }
|
|
290
290
|
|
|
291
|
-
it
|
|
291
|
+
it "evaluates to the current context" do
|
|
292
292
|
allow(context).to receive(:goodbyes).and_return(["goodbye", "Goodbye", "GOODBYE"])
|
|
293
293
|
expect(subject).to eq "goodbyeGoodbyeGOODBYE"
|
|
294
294
|
end
|
|
295
295
|
end
|
|
296
296
|
|
|
297
|
-
describe
|
|
297
|
+
describe "in a block in a path" do
|
|
298
298
|
let(:template) { "{{#hellos}}{{this/text}}{{/hellos}}" }
|
|
299
299
|
|
|
300
|
-
it
|
|
300
|
+
it "evaluates in more complex paths" do
|
|
301
301
|
hellos = []
|
|
302
302
|
hellos << double(:hello)
|
|
303
|
-
expect(hellos[0]).to receive(:text).and_return(
|
|
303
|
+
expect(hellos[0]).to receive(:text).and_return("hello")
|
|
304
304
|
hellos << double(:Hello)
|
|
305
|
-
expect(hellos[1]).to receive(:text).and_return(
|
|
305
|
+
expect(hellos[1]).to receive(:text).and_return("Hello")
|
|
306
306
|
hellos << double(:HELLO)
|
|
307
|
-
expect(hellos[2]).to receive(:text).and_return(
|
|
307
|
+
expect(hellos[2]).to receive(:text).and_return("HELLO")
|
|
308
308
|
allow(context).to receive(:hellos).and_return(hellos)
|
|
309
309
|
expect(subject).to eq "helloHelloHELLO"
|
|
310
310
|
end
|
|
311
311
|
end
|
|
312
312
|
end
|
|
313
313
|
|
|
314
|
-
describe
|
|
314
|
+
describe "this keyword in helpers" do
|
|
315
315
|
before { FS.register_helper(:foo) { |value| "bar #{value}" } }
|
|
316
316
|
|
|
317
|
-
describe
|
|
317
|
+
describe "this keyword in arguments" do
|
|
318
318
|
let(:template) { "{{#goodbyes}}{{foo this}}{{/goodbyes}}" }
|
|
319
319
|
|
|
320
|
-
it
|
|
320
|
+
it "evaluates to current context" do
|
|
321
321
|
allow(context).to receive(:goodbyes).and_return(["goodbye", "Goodbye", "GOODBYE"])
|
|
322
322
|
expect(subject).to eq "bar goodbyebar Goodbyebar GOODBYE"
|
|
323
323
|
end
|
|
324
324
|
end
|
|
325
325
|
|
|
326
|
-
describe
|
|
326
|
+
describe "this keyword in object path arguments" do
|
|
327
327
|
let(:template) { "{{#hellos}}{{foo this/text}}{{/hellos}}" }
|
|
328
328
|
|
|
329
|
-
it
|
|
329
|
+
it "evaluates to current context" do
|
|
330
330
|
hellos = []
|
|
331
331
|
hellos << double(:hello)
|
|
332
|
-
expect(hellos[0]).to receive(:text).and_return(
|
|
332
|
+
expect(hellos[0]).to receive(:text).and_return("hello")
|
|
333
333
|
hellos << double(:Hello)
|
|
334
|
-
expect(hellos[1]).to receive(:text).and_return(
|
|
334
|
+
expect(hellos[1]).to receive(:text).and_return("Hello")
|
|
335
335
|
hellos << double(:HELLO)
|
|
336
|
-
expect(hellos[2]).to receive(:text).and_return(
|
|
336
|
+
expect(hellos[2]).to receive(:text).and_return("HELLO")
|
|
337
337
|
allow(context).to receive(:hellos).and_return(hellos)
|
|
338
338
|
expect(subject).to eq "bar hellobar Hellobar HELLO"
|
|
339
339
|
end
|
|
@@ -342,208 +342,208 @@ describe FlavourSaver do
|
|
|
342
342
|
end
|
|
343
343
|
end
|
|
344
344
|
|
|
345
|
-
describe
|
|
345
|
+
describe "Inverted sections" do
|
|
346
346
|
let(:template) { "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}" }
|
|
347
347
|
|
|
348
|
-
describe
|
|
349
|
-
it
|
|
348
|
+
describe "with unset value" do
|
|
349
|
+
it "renders" do
|
|
350
350
|
allow(context).to receive(:goodbyes)
|
|
351
|
-
expect(subject).to eq
|
|
351
|
+
expect(subject).to eq "Right On!"
|
|
352
352
|
end
|
|
353
353
|
end
|
|
354
354
|
|
|
355
|
-
describe
|
|
356
|
-
it
|
|
355
|
+
describe "with false value" do
|
|
356
|
+
it "renders" do
|
|
357
357
|
allow(context).to receive(:goodbyes).and_return(false)
|
|
358
|
-
expect(subject).to eq
|
|
358
|
+
expect(subject).to eq "Right On!"
|
|
359
359
|
end
|
|
360
360
|
end
|
|
361
361
|
|
|
362
|
-
describe
|
|
363
|
-
it
|
|
362
|
+
describe "with an empty set" do
|
|
363
|
+
it "renders" do
|
|
364
364
|
allow(context).to receive(:goodbyes).and_return([])
|
|
365
|
-
expect(subject).to eq
|
|
365
|
+
expect(subject).to eq "Right On!"
|
|
366
366
|
end
|
|
367
367
|
end
|
|
368
368
|
end
|
|
369
369
|
|
|
370
|
-
describe
|
|
370
|
+
describe "Blocks" do
|
|
371
371
|
let(:template) { "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!" }
|
|
372
372
|
|
|
373
|
-
it
|
|
373
|
+
it "arrays iterate the contents with non-empty" do
|
|
374
374
|
goodbyes = []
|
|
375
375
|
goodbyes << double(:goodbye)
|
|
376
|
-
expect(goodbyes[0]).to receive(:text).and_return(
|
|
376
|
+
expect(goodbyes[0]).to receive(:text).and_return("goodbye")
|
|
377
377
|
goodbyes << double(:Goodbye)
|
|
378
|
-
expect(goodbyes[1]).to receive(:text).and_return(
|
|
378
|
+
expect(goodbyes[1]).to receive(:text).and_return("Goodbye")
|
|
379
379
|
goodbyes << double(:GOODBYE)
|
|
380
|
-
expect(goodbyes[2]).to receive(:text).and_return(
|
|
380
|
+
expect(goodbyes[2]).to receive(:text).and_return("GOODBYE")
|
|
381
381
|
allow(context).to receive(:goodbyes).and_return(goodbyes)
|
|
382
|
-
allow(context).to receive(:world).and_return(
|
|
382
|
+
allow(context).to receive(:world).and_return("world")
|
|
383
383
|
expect(subject).to eq "goodbye! Goodbye! GOODBYE! cruel world!"
|
|
384
384
|
end
|
|
385
385
|
|
|
386
|
-
it
|
|
386
|
+
it "ignores the contents when the array is empty" do
|
|
387
387
|
allow(context).to receive(:goodbyes).and_return([])
|
|
388
|
-
allow(context).to receive(:world).and_return(
|
|
388
|
+
allow(context).to receive(:world).and_return("world")
|
|
389
389
|
expect(subject).to eq "cruel world!"
|
|
390
390
|
end
|
|
391
391
|
|
|
392
|
-
describe
|
|
393
|
-
let(:template) {"{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!"}
|
|
392
|
+
describe "array with @index" do
|
|
393
|
+
let(:template) { "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!" }
|
|
394
394
|
|
|
395
|
-
it
|
|
395
|
+
it "the @index variable is used" do
|
|
396
396
|
goodbyes = []
|
|
397
397
|
goodbyes << double(:goodbye)
|
|
398
|
-
expect(goodbyes[0]).to receive(:text).and_return(
|
|
398
|
+
expect(goodbyes[0]).to receive(:text).and_return("goodbye")
|
|
399
399
|
goodbyes << double(:Goodbye)
|
|
400
|
-
expect(goodbyes[1]).to receive(:text).and_return(
|
|
400
|
+
expect(goodbyes[1]).to receive(:text).and_return("Goodbye")
|
|
401
401
|
goodbyes << double(:GOODBYE)
|
|
402
|
-
expect(goodbyes[2]).to receive(:text).and_return(
|
|
402
|
+
expect(goodbyes[2]).to receive(:text).and_return("GOODBYE")
|
|
403
403
|
allow(context).to receive(:goodbyes).and_return(goodbyes)
|
|
404
|
-
allow(context).to receive(:world).and_return(
|
|
404
|
+
allow(context).to receive(:world).and_return("world")
|
|
405
405
|
expect(subject).to eq "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!"
|
|
406
406
|
end
|
|
407
407
|
end
|
|
408
408
|
|
|
409
|
-
describe
|
|
410
|
-
let(:template) {"{{goodbyes.0.text}} cruel {{world}}!"}
|
|
409
|
+
describe "array with numeric dereference" do
|
|
410
|
+
let(:template) { "{{goodbyes.0.text}} cruel {{world}}!" }
|
|
411
411
|
|
|
412
|
-
it
|
|
412
|
+
it "properly inserts the referenced element" do
|
|
413
413
|
goodbyes = []
|
|
414
414
|
goodbyes << double(:goodbye)
|
|
415
|
-
expect(goodbyes[0]).to receive(:text).and_return(
|
|
415
|
+
expect(goodbyes[0]).to receive(:text).and_return("goodbye")
|
|
416
416
|
|
|
417
417
|
allow(context).to receive(:goodbyes).and_return(goodbyes)
|
|
418
|
-
allow(context).to receive(:world).and_return(
|
|
418
|
+
allow(context).to receive(:world).and_return("world")
|
|
419
419
|
|
|
420
420
|
expect(subject).to eq "goodbye cruel world!"
|
|
421
421
|
end
|
|
422
422
|
end
|
|
423
423
|
|
|
424
|
-
describe
|
|
424
|
+
describe "empty block" do
|
|
425
425
|
let(:template) { "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!" }
|
|
426
426
|
|
|
427
|
-
it
|
|
427
|
+
it "arrays iterate the contents with non-empty" do
|
|
428
428
|
goodbyes = []
|
|
429
429
|
goodbyes << double(:goodbye)
|
|
430
|
-
allow(goodbyes[0]).to receive(:text).and_return(
|
|
430
|
+
allow(goodbyes[0]).to receive(:text).and_return("goodbye")
|
|
431
431
|
goodbyes << double(:Goodbye)
|
|
432
|
-
allow(goodbyes[1]).to receive(:text).and_return(
|
|
432
|
+
allow(goodbyes[1]).to receive(:text).and_return("Goodbye")
|
|
433
433
|
goodbyes << double(:GOODBYE)
|
|
434
|
-
allow(goodbyes[2]).to receive(:text).and_return(
|
|
434
|
+
allow(goodbyes[2]).to receive(:text).and_return("GOODBYE")
|
|
435
435
|
allow(context).to receive(:goodbyes).and_return(goodbyes)
|
|
436
|
-
allow(context).to receive(:world).and_return(
|
|
436
|
+
allow(context).to receive(:world).and_return("world")
|
|
437
437
|
expect(subject).to eq "cruel world!"
|
|
438
438
|
end
|
|
439
439
|
|
|
440
|
-
it
|
|
440
|
+
it "ignores the contents when the array is empty" do
|
|
441
441
|
allow(context).to receive(:goodbyes).and_return([])
|
|
442
|
-
allow(context).to receive(:world).and_return(
|
|
442
|
+
allow(context).to receive(:world).and_return("world")
|
|
443
443
|
expect(subject).to eq "cruel world!"
|
|
444
444
|
end
|
|
445
445
|
end
|
|
446
446
|
|
|
447
|
-
describe
|
|
447
|
+
describe "nested iteration"
|
|
448
448
|
|
|
449
|
-
describe
|
|
450
|
-
let(:template) {"{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}"}
|
|
449
|
+
describe "block with complex lookup" do
|
|
450
|
+
let(:template) { "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}" }
|
|
451
451
|
|
|
452
|
-
it
|
|
453
|
-
allow(context).to receive(:name).and_return(
|
|
452
|
+
it "templates can access variables in contexts up the stack with relative path syntax" do
|
|
453
|
+
allow(context).to receive(:name).and_return("Alan")
|
|
454
454
|
goodbyes = []
|
|
455
455
|
goodbyes << double(:goodbye)
|
|
456
|
-
expect(goodbyes[0]).to receive(:text).and_return(
|
|
456
|
+
expect(goodbyes[0]).to receive(:text).and_return("goodbye")
|
|
457
457
|
goodbyes << double(:Goodbye)
|
|
458
|
-
expect(goodbyes[1]).to receive(:text).and_return(
|
|
458
|
+
expect(goodbyes[1]).to receive(:text).and_return("Goodbye")
|
|
459
459
|
goodbyes << double(:GOODBYE)
|
|
460
|
-
expect(goodbyes[2]).to receive(:text).and_return(
|
|
460
|
+
expect(goodbyes[2]).to receive(:text).and_return("GOODBYE")
|
|
461
461
|
allow(context).to receive(:goodbyes).and_return(goodbyes)
|
|
462
462
|
expect(subject).to eq "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! "
|
|
463
463
|
end
|
|
464
464
|
end
|
|
465
465
|
|
|
466
|
-
describe
|
|
467
|
-
let(:template) {"{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"}
|
|
466
|
+
describe "helper with complex lookup" do
|
|
467
|
+
let(:template) { "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}" }
|
|
468
468
|
before do
|
|
469
469
|
FS.register_helper(:link) do |prefix|
|
|
470
470
|
"<a href='#{prefix}/#{url}'>#{text}</a>"
|
|
471
471
|
end
|
|
472
472
|
end
|
|
473
473
|
|
|
474
|
-
it
|
|
475
|
-
allow(context).to receive(:prefix).and_return(
|
|
474
|
+
it "renders correctly" do
|
|
475
|
+
allow(context).to receive(:prefix).and_return("/root")
|
|
476
476
|
goodbyes = []
|
|
477
477
|
goodbyes << double(:Goodbye)
|
|
478
|
-
expect(goodbyes[0]).to receive(:text).and_return(
|
|
479
|
-
expect(goodbyes[0]).to receive(:url).and_return(
|
|
478
|
+
expect(goodbyes[0]).to receive(:text).and_return("Goodbye")
|
|
479
|
+
expect(goodbyes[0]).to receive(:url).and_return("goodbye")
|
|
480
480
|
allow(context).to receive(:goodbyes).and_return(goodbyes)
|
|
481
481
|
expect(subject).to eq "<a href='/root/goodbye'>Goodbye</a>"
|
|
482
482
|
end
|
|
483
483
|
end
|
|
484
484
|
|
|
485
|
-
describe
|
|
485
|
+
describe "helper with complex lookup expression" do
|
|
486
486
|
let(:template) { "{{#goodbyes}}{{../name}}{{/goodbyes}}" }
|
|
487
487
|
before do
|
|
488
488
|
FS.register_helper(:goodbyes) do |&b|
|
|
489
489
|
["Goodbye", "goodbye", "GOODBYE"].map do |bye|
|
|
490
490
|
"#{bye} #{b.call.contents}! "
|
|
491
|
-
end.join(
|
|
491
|
+
end.join("")
|
|
492
492
|
end
|
|
493
493
|
end
|
|
494
494
|
|
|
495
|
-
it
|
|
496
|
-
allow(context).to receive(:name).and_return(
|
|
495
|
+
it "renders correctly" do
|
|
496
|
+
allow(context).to receive(:name).and_return("Alan")
|
|
497
497
|
expect(subject).to eq "Goodbye Alan! goodbye Alan! GOODBYE Alan! "
|
|
498
498
|
end
|
|
499
499
|
end
|
|
500
500
|
|
|
501
|
-
describe
|
|
501
|
+
describe "helper with complex lookup and nested template" do
|
|
502
502
|
let(:template) { "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}" }
|
|
503
503
|
before do
|
|
504
|
-
FS.register_helper(:link) do |prefix
|
|
504
|
+
FS.register_helper(:link) do |prefix, &b|
|
|
505
505
|
"<a href='#{prefix}/#{url}'>#{b.call.contents}</a>"
|
|
506
506
|
end
|
|
507
507
|
end
|
|
508
508
|
|
|
509
|
-
it
|
|
510
|
-
allow(context).to receive(:prefix).and_return(
|
|
509
|
+
it "renders correctly" do
|
|
510
|
+
allow(context).to receive(:prefix).and_return("/root")
|
|
511
511
|
goodbye = double(:goodbye)
|
|
512
|
-
allow(goodbye).to receive(:text).and_return(
|
|
513
|
-
allow(goodbye).to receive(:url).and_return(
|
|
512
|
+
allow(goodbye).to receive(:text).and_return("Goodbye")
|
|
513
|
+
allow(goodbye).to receive(:url).and_return("goodbye")
|
|
514
514
|
allow(context).to receive(:goodbyes).and_return([goodbye])
|
|
515
515
|
expect(subject).to eq "<a href='/root/goodbye'>Goodbye</a>"
|
|
516
516
|
end
|
|
517
517
|
end
|
|
518
518
|
|
|
519
|
-
describe
|
|
519
|
+
describe "block with deep nested complex lookup" do
|
|
520
520
|
let(:template) { "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}" }
|
|
521
521
|
|
|
522
522
|
example do
|
|
523
523
|
goodbye = double(:goodbye)
|
|
524
|
-
allow(goodbye).to receive(:text).and_return(
|
|
524
|
+
allow(goodbye).to receive(:text).and_return("goodbye")
|
|
525
525
|
inner = double(:inner)
|
|
526
526
|
allow(inner).to receive(:inner).and_return([goodbye])
|
|
527
|
-
allow(context).to receive(:omg).and_return(
|
|
527
|
+
allow(context).to receive(:omg).and_return("OMG!")
|
|
528
528
|
allow(context).to receive(:outer).and_return([inner])
|
|
529
529
|
expect(subject).to eq "Goodbye cruel OMG!"
|
|
530
530
|
end
|
|
531
531
|
end
|
|
532
532
|
|
|
533
|
-
describe
|
|
533
|
+
describe "block helper" do
|
|
534
534
|
let(:template) { "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!" }
|
|
535
535
|
before do
|
|
536
536
|
FS.register_helper(:goodbyes) do |&block|
|
|
537
|
-
block.call.contents Struct.new(:text).new(
|
|
537
|
+
block.call.contents Struct.new(:text).new("GOODBYE")
|
|
538
538
|
end
|
|
539
539
|
end
|
|
540
540
|
|
|
541
541
|
example do
|
|
542
|
-
allow(context).to receive(:world).and_return(
|
|
542
|
+
allow(context).to receive(:world).and_return("world")
|
|
543
543
|
end
|
|
544
544
|
end
|
|
545
545
|
|
|
546
|
-
describe
|
|
546
|
+
describe "block helper staying in the same context" do
|
|
547
547
|
let(:template) { "{{#form}}<p>{{name}}</p>{{/form}}" }
|
|
548
548
|
before do
|
|
549
549
|
FS.register_helper(:form) do |&block|
|
|
@@ -552,12 +552,12 @@ describe FlavourSaver do
|
|
|
552
552
|
end
|
|
553
553
|
|
|
554
554
|
example do
|
|
555
|
-
allow(context).to receive(:name).and_return(
|
|
555
|
+
allow(context).to receive(:name).and_return("Yehuda")
|
|
556
556
|
expect(subject).to eq "<form><p>Yehuda</p></form>"
|
|
557
557
|
end
|
|
558
558
|
end
|
|
559
559
|
|
|
560
|
-
describe
|
|
560
|
+
describe "block helper should have context in this" do
|
|
561
561
|
let(:template) { "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>" }
|
|
562
562
|
before do
|
|
563
563
|
FS.register_helper(:link) do |&block|
|
|
@@ -566,64 +566,64 @@ describe FlavourSaver do
|
|
|
566
566
|
end
|
|
567
567
|
example do
|
|
568
568
|
person = Struct.new(:name, :id)
|
|
569
|
-
allow(context).to receive(:people).and_return([person.new(
|
|
569
|
+
allow(context).to receive(:people).and_return([person.new("Alan", 1), person.new("Yehuda", 2)])
|
|
570
570
|
expect(subject).to eq "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>"
|
|
571
571
|
end
|
|
572
572
|
end
|
|
573
573
|
|
|
574
|
-
describe
|
|
574
|
+
describe "block helper for undefined value" do
|
|
575
575
|
let(:template) { "{{#empty}}shoulnd't render{{/empty}}" }
|
|
576
576
|
example do
|
|
577
577
|
expect { subject }.to raise_exception(FlavourSaver::UnknownHelperException)
|
|
578
578
|
end
|
|
579
579
|
end
|
|
580
580
|
|
|
581
|
-
describe
|
|
581
|
+
describe "block helper passing a new context" do
|
|
582
582
|
let(:template) { "{{#form yehuda}}<p>{{name}}</p>{{/form}}" }
|
|
583
583
|
before do
|
|
584
|
-
FS.register_helper(:form) do |whom
|
|
584
|
+
FS.register_helper(:form) do |whom, &block|
|
|
585
585
|
"<form>#{block.call.contents whom}</form>"
|
|
586
586
|
end
|
|
587
587
|
end
|
|
588
588
|
example do
|
|
589
|
-
allow(context).to receive_message_chain(:yehuda
|
|
589
|
+
allow(context).to receive_message_chain(:yehuda, :name).and_return("Yehuda")
|
|
590
590
|
expect(subject).to eq "<form><p>Yehuda</p></form>"
|
|
591
591
|
end
|
|
592
592
|
end
|
|
593
593
|
|
|
594
|
-
describe
|
|
594
|
+
describe "block helper passing a complex path context" do
|
|
595
595
|
let(:template) { "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}" }
|
|
596
596
|
before do
|
|
597
|
-
FS.register_helper(:form) do |context
|
|
597
|
+
FS.register_helper(:form) do |context, &block|
|
|
598
598
|
"<form>#{block.call.contents context}</form>"
|
|
599
599
|
end
|
|
600
600
|
end
|
|
601
601
|
example do
|
|
602
602
|
yehuda = double(:yehuda)
|
|
603
|
-
allow(yehuda).to receive(:name).and_return(
|
|
604
|
-
allow(yehuda).to receive_message_chain(:cat
|
|
603
|
+
allow(yehuda).to receive(:name).and_return("Yehuda")
|
|
604
|
+
allow(yehuda).to receive_message_chain(:cat, :name).and_return("Harold")
|
|
605
605
|
allow(context).to receive(:yehuda).and_return(yehuda)
|
|
606
606
|
expect(subject).to eq "<form><p>Harold</p></form>"
|
|
607
607
|
end
|
|
608
608
|
end
|
|
609
609
|
|
|
610
|
-
describe
|
|
610
|
+
describe "nested block helpers" do
|
|
611
611
|
let(:template) { "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}" }
|
|
612
612
|
before do
|
|
613
613
|
FS.register_helper(:link) do |&block|
|
|
614
614
|
"<a href='#{name}'>#{block.call.contents}</a>"
|
|
615
615
|
end
|
|
616
|
-
FS.register_helper(:form) do |context
|
|
616
|
+
FS.register_helper(:form) do |context, &block|
|
|
617
617
|
"<form>#{block.call.contents context}</form>"
|
|
618
618
|
end
|
|
619
619
|
end
|
|
620
620
|
example do
|
|
621
|
-
allow(context).to receive_message_chain(:yehuda
|
|
621
|
+
allow(context).to receive_message_chain(:yehuda, :name).and_return("Yehuda")
|
|
622
622
|
expect(subject).to eq "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>"
|
|
623
623
|
end
|
|
624
624
|
end
|
|
625
625
|
|
|
626
|
-
describe
|
|
626
|
+
describe "block inverted sections" do
|
|
627
627
|
let(:template) { "{{#people}}{{name}}{{^}}{{none}}{{/people}}" }
|
|
628
628
|
example do
|
|
629
629
|
allow(context).to receive(:none).and_return("No people")
|
|
@@ -632,22 +632,22 @@ describe FlavourSaver do
|
|
|
632
632
|
end
|
|
633
633
|
end
|
|
634
634
|
|
|
635
|
-
describe
|
|
635
|
+
describe "block inverted sections with empty arrays" do
|
|
636
636
|
let(:template) { "{{#people}}{{name}}{{^}}{{none}}{{/people}}" }
|
|
637
637
|
example do
|
|
638
|
-
allow(context).to receive(:none).and_return(
|
|
638
|
+
allow(context).to receive(:none).and_return("No people")
|
|
639
639
|
allow(context).to receive(:people).and_return([])
|
|
640
640
|
expect(subject).to eq "No people"
|
|
641
641
|
end
|
|
642
642
|
end
|
|
643
643
|
|
|
644
|
-
describe
|
|
645
|
-
let
|
|
644
|
+
describe "block helpers with inverted sections" do
|
|
645
|
+
let(:template) { "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}" }
|
|
646
646
|
before do
|
|
647
|
-
FS.register_helper(:list) do |context
|
|
647
|
+
FS.register_helper(:list) do |context, &block|
|
|
648
648
|
if context.any?
|
|
649
649
|
"<ul>" +
|
|
650
|
-
context.map { |e| "<li>#{block.call.contents e}</li>" }.join(
|
|
650
|
+
context.map { |e| "<li>#{block.call.contents e}</li>" }.join("") +
|
|
651
651
|
"</ul>"
|
|
652
652
|
else
|
|
653
653
|
"<p>#{block.call.inverse}</p>"
|
|
@@ -655,23 +655,23 @@ describe FlavourSaver do
|
|
|
655
655
|
end
|
|
656
656
|
end
|
|
657
657
|
|
|
658
|
-
example
|
|
658
|
+
example "an inverse wrapper is passed in as a new context" do
|
|
659
659
|
person = Struct.new(:name)
|
|
660
|
-
allow(context).to receive(:people).and_return([person.new(
|
|
660
|
+
allow(context).to receive(:people).and_return([person.new("Alan"), person.new("Yehuda")])
|
|
661
661
|
expect(subject).to eq "<ul><li>Alan</li><li>Yehuda</li></ul>"
|
|
662
662
|
end
|
|
663
663
|
|
|
664
|
-
example
|
|
664
|
+
example "an inverse wrapper can optionally be called" do
|
|
665
665
|
allow(context).to receive(:people).and_return([])
|
|
666
666
|
expect(subject).to eq "<p><em>Nobody's here</em></p>"
|
|
667
667
|
end
|
|
668
668
|
|
|
669
|
-
describe
|
|
669
|
+
describe "the context of an inverse is the parent of the block" do
|
|
670
670
|
let(:template) { "{{#list people}}Hello{{^}}{{message}}{{/list}}" }
|
|
671
671
|
example do
|
|
672
672
|
allow(context).to receive(:people).and_return([])
|
|
673
673
|
allow(context).to receive(:message).and_return("Nobody's here")
|
|
674
|
-
if RUBY_VERSION >=
|
|
674
|
+
if RUBY_VERSION >= "2.0.0"
|
|
675
675
|
expect(subject).to eq "<p>Nobody's here</p>"
|
|
676
676
|
else
|
|
677
677
|
expect(subject).to eq "<p>Nobody's here</p>"
|
|
@@ -681,51 +681,51 @@ describe FlavourSaver do
|
|
|
681
681
|
end
|
|
682
682
|
end
|
|
683
683
|
|
|
684
|
-
describe
|
|
684
|
+
describe "partials" do
|
|
685
685
|
let(:template) { "Dudes: {{#dudes}}{{> dude}}{{/dudes}}" }
|
|
686
686
|
before do
|
|
687
687
|
FS.register_partial(:dude, "{{name}} ({{url}}) ")
|
|
688
688
|
end
|
|
689
689
|
example do
|
|
690
690
|
person = Struct.new(:name, :url)
|
|
691
|
-
allow(context).to receive(:dudes).and_return([person.new(
|
|
691
|
+
allow(context).to receive(:dudes).and_return([person.new("Yehuda", "http://yehuda"), person.new("Alan", "http://alan")])
|
|
692
692
|
expect(subject).to eq "Dudes: Yehuda (http://yehuda) Alan (http://alan) "
|
|
693
693
|
end
|
|
694
694
|
end
|
|
695
695
|
|
|
696
|
-
describe
|
|
697
|
-
let(:template) {"Dudes: {{>dude dudes}}"}
|
|
696
|
+
describe "partials with context" do
|
|
697
|
+
let(:template) { "Dudes: {{>dude dudes}}" }
|
|
698
698
|
before do
|
|
699
699
|
FS.register_partial(:dude, "{{#this}}{{name}} ({{url}}) {{/this}}")
|
|
700
700
|
end
|
|
701
701
|
example "Partials can be passed a context" do
|
|
702
702
|
person = Struct.new(:name, :url)
|
|
703
|
-
allow(context).to receive(:dudes).and_return([person.new(
|
|
703
|
+
allow(context).to receive(:dudes).and_return([person.new("Yehuda", "http://yehuda"), person.new("Alan", "http://alan")])
|
|
704
704
|
expect(subject).to eq "Dudes: Yehuda (http://yehuda) Alan (http://alan) "
|
|
705
705
|
end
|
|
706
706
|
end
|
|
707
707
|
|
|
708
|
-
describe
|
|
709
|
-
let(:template) {"Dudes: {{#dudes}}{{>dude}}{{/dudes}}"}
|
|
708
|
+
describe "partial in a partial" do
|
|
709
|
+
let(:template) { "Dudes: {{#dudes}}{{>dude}}{{/dudes}}" }
|
|
710
710
|
before do
|
|
711
711
|
FS.register_partial(:dude, "{{name}} {{>url}} ")
|
|
712
712
|
FS.register_partial(:url, "<a href='{{url}}'>{{url}}</a>")
|
|
713
713
|
end
|
|
714
714
|
example "Partials can be passed a context" do
|
|
715
715
|
person = Struct.new(:name, :url)
|
|
716
|
-
allow(context).to receive(:dudes).and_return([person.new(
|
|
716
|
+
allow(context).to receive(:dudes).and_return([person.new("Yehuda", "http://yehuda"), person.new("Alan", "http://alan")])
|
|
717
717
|
expect(subject).to eq "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> "
|
|
718
718
|
end
|
|
719
719
|
end
|
|
720
720
|
|
|
721
|
-
describe
|
|
721
|
+
describe "rendering undefined partial throws an exception" do
|
|
722
722
|
let(:template) { "{{> whatever}}" }
|
|
723
723
|
example do
|
|
724
724
|
expect { subject }.to raise_error(FS::UnknownPartialException)
|
|
725
725
|
end
|
|
726
726
|
end
|
|
727
727
|
|
|
728
|
-
describe
|
|
728
|
+
describe "rendering a function partial" do
|
|
729
729
|
let(:template) { "Dudes: {{#dudes}}{{> dude}}{{/dudes}}" }
|
|
730
730
|
before do
|
|
731
731
|
FS.register_partial(:dude) do |context|
|
|
@@ -734,53 +734,52 @@ describe FlavourSaver do
|
|
|
734
734
|
end
|
|
735
735
|
example do
|
|
736
736
|
person = Struct.new(:name, :url)
|
|
737
|
-
allow(context).to receive(:dudes).and_return([person.new(
|
|
737
|
+
allow(context).to receive(:dudes).and_return([person.new("Yehuda", "http://yehuda"), person.new("Alan", "http://alan")])
|
|
738
738
|
expect(subject).to eq "Dudes: Yehuda (http://yehuda) Alan (http://alan) "
|
|
739
739
|
end
|
|
740
740
|
end
|
|
741
741
|
|
|
742
|
-
describe
|
|
742
|
+
describe "a partial preceding a selector" do
|
|
743
743
|
let(:template) { "Dudes: {{>dude}} {{another_dude}}" }
|
|
744
744
|
before do
|
|
745
745
|
FS.register_partial(:dude, "{{name}}")
|
|
746
746
|
end
|
|
747
747
|
example do
|
|
748
|
-
allow(context).to receive(:name).and_return(
|
|
749
|
-
allow(context).to receive(:another_dude).and_return(
|
|
748
|
+
allow(context).to receive(:name).and_return("Jeepers")
|
|
749
|
+
allow(context).to receive(:another_dude).and_return("Creepers")
|
|
750
750
|
expect(subject).to eq "Dudes: Jeepers Creepers"
|
|
751
751
|
end
|
|
752
752
|
end
|
|
753
753
|
|
|
754
|
-
describe
|
|
754
|
+
describe "partials with literal paths" do
|
|
755
755
|
let(:template) { "Dudes: {{> [dude]}}" }
|
|
756
756
|
before do
|
|
757
757
|
FS.register_partial(:dude, "{{name}}")
|
|
758
758
|
end
|
|
759
759
|
example do
|
|
760
|
-
allow(context).to receive(:name).and_return(
|
|
761
|
-
allow(context).to receive(:another_dude).and_return(
|
|
760
|
+
allow(context).to receive(:name).and_return("Jeepers")
|
|
761
|
+
allow(context).to receive(:another_dude).and_return("Creepers")
|
|
762
762
|
expect(subject).to eq "Dudes: Jeepers"
|
|
763
763
|
end
|
|
764
764
|
end
|
|
765
765
|
|
|
766
|
-
describe
|
|
766
|
+
describe "partials with string paths" do
|
|
767
767
|
let(:template) { "Dudes: {{> \"dude/man\"}}" }
|
|
768
768
|
before do
|
|
769
769
|
FS.register_partial("dude/man", "{{name}}")
|
|
770
770
|
end
|
|
771
771
|
example do
|
|
772
|
-
allow(context).to receive(:name).and_return(
|
|
773
|
-
allow(context).to receive(:another_dude).and_return(
|
|
772
|
+
allow(context).to receive(:name).and_return("Jeepers")
|
|
773
|
+
allow(context).to receive(:another_dude).and_return("Creepers")
|
|
774
774
|
expect(subject).to eq "Dudes: Jeepers"
|
|
775
775
|
end
|
|
776
776
|
end
|
|
777
777
|
|
|
778
|
-
describe
|
|
779
|
-
|
|
780
|
-
describe 'simple literals work' do
|
|
778
|
+
describe "string literal parameters" do
|
|
779
|
+
describe "simple literals work" do
|
|
781
780
|
let(:template) { "Message: {{hello \"world\" 12 true false}}" }
|
|
782
781
|
before do
|
|
783
|
-
FS.register_helper(:hello) do |param,times,bool1,bool2|
|
|
782
|
+
FS.register_helper(:hello) do |param, times, bool1, bool2|
|
|
784
783
|
times = "NaN" unless times.is_a? Integer
|
|
785
784
|
bool1 = "NaB" unless bool1 == true
|
|
786
785
|
bool2 = "NaB" unless bool2 == false
|
|
@@ -792,14 +791,14 @@ describe FlavourSaver do
|
|
|
792
791
|
end
|
|
793
792
|
end
|
|
794
793
|
|
|
795
|
-
describe
|
|
794
|
+
describe "using a quote in the middle of a parameter raises an error" do
|
|
796
795
|
let(:template) { "Message: {{hello wo\"rld\"}}" }
|
|
797
796
|
example do
|
|
798
|
-
expect { subject }.to raise_error(
|
|
797
|
+
expect { subject }.to raise_error(FlavourSaver::Parser::NotInLanguage)
|
|
799
798
|
end
|
|
800
799
|
end
|
|
801
800
|
|
|
802
|
-
describe
|
|
801
|
+
describe "escaping a string is possible" do
|
|
803
802
|
let(:template) { 'Message: {{{hello "\"world\""}}}' }
|
|
804
803
|
before do
|
|
805
804
|
FS.register_helper(:hello) do |param|
|
|
@@ -811,7 +810,7 @@ describe FlavourSaver do
|
|
|
811
810
|
end
|
|
812
811
|
end
|
|
813
812
|
|
|
814
|
-
describe
|
|
813
|
+
describe "string work with ticks" do
|
|
815
814
|
let(:template) { 'Message: {{{hello "Alan\'s world"}}}' }
|
|
816
815
|
before do
|
|
817
816
|
FS.register_helper(:hello) do |param|
|
|
@@ -822,202 +821,201 @@ describe FlavourSaver do
|
|
|
822
821
|
expect(subject).to eq "Message: Hello Alan's world"
|
|
823
822
|
end
|
|
824
823
|
end
|
|
825
|
-
|
|
826
824
|
end
|
|
827
825
|
|
|
828
|
-
describe
|
|
829
|
-
describe
|
|
826
|
+
describe "multi-params" do
|
|
827
|
+
describe "simple multi-params work" do
|
|
830
828
|
let(:template) { "Message: {{goodbye cruel world}}" }
|
|
831
|
-
before { FS.register_helper(:goodbye) { |cruel,world| "Goodbye #{cruel} #{world}" } }
|
|
829
|
+
before { FS.register_helper(:goodbye) { |cruel, world| "Goodbye #{cruel} #{world}" } }
|
|
832
830
|
example do
|
|
833
|
-
allow(context).to receive(:cruel).and_return(
|
|
834
|
-
allow(context).to receive(:world).and_return(
|
|
831
|
+
allow(context).to receive(:cruel).and_return("cruel")
|
|
832
|
+
allow(context).to receive(:world).and_return("world")
|
|
835
833
|
expect(subject).to eq "Message: Goodbye cruel world"
|
|
836
834
|
end
|
|
837
835
|
end
|
|
838
836
|
|
|
839
|
-
describe
|
|
837
|
+
describe "block multi-params" do
|
|
840
838
|
let(:template) { "Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}" }
|
|
841
|
-
before { FS.register_helper(:goodbye) { |adj,noun
|
|
839
|
+
before { FS.register_helper(:goodbye) { |adj, noun, &b| b.call.contents Struct.new(:greeting, :adj, :noun).new("Goodbye", adj, noun) } }
|
|
842
840
|
example do
|
|
843
|
-
allow(context).to receive(:cruel).and_return(
|
|
844
|
-
allow(context).to receive(:world).and_return(
|
|
841
|
+
allow(context).to receive(:cruel).and_return("cruel")
|
|
842
|
+
allow(context).to receive(:world).and_return("world")
|
|
845
843
|
expect(subject).to eq "Message: Goodbye cruel world"
|
|
846
844
|
end
|
|
847
845
|
end
|
|
848
846
|
end
|
|
849
847
|
|
|
850
|
-
describe
|
|
851
|
-
describe
|
|
848
|
+
describe "built-in helpers" do
|
|
849
|
+
describe "with" do
|
|
852
850
|
let(:template) { "{{#with person}}{{first}} {{last}}{{/with}}" }
|
|
853
851
|
example do
|
|
854
|
-
allow(context).to receive(:person).and_return(Struct.new(:first
|
|
855
|
-
expect(subject).to eq
|
|
852
|
+
allow(context).to receive(:person).and_return(Struct.new(:first, :last).new("Alan", "Johnson"))
|
|
853
|
+
expect(subject).to eq "Alan Johnson"
|
|
856
854
|
end
|
|
857
855
|
end
|
|
858
856
|
|
|
859
|
-
describe
|
|
857
|
+
describe "if" do
|
|
860
858
|
let(:template) { "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!" }
|
|
861
859
|
|
|
862
|
-
example
|
|
860
|
+
example "if with boolean argument shows the contents when true" do
|
|
863
861
|
allow(context).to receive(:goodbye).and_return(true)
|
|
864
|
-
allow(context).to receive(:world).and_return(
|
|
862
|
+
allow(context).to receive(:world).and_return("world")
|
|
865
863
|
expect(subject).to eq "GOODBYE cruel world!"
|
|
866
864
|
end
|
|
867
865
|
|
|
868
|
-
example
|
|
869
|
-
allow(context).to receive(:goodbye).and_return(
|
|
870
|
-
allow(context).to receive(:world).and_return(
|
|
866
|
+
example "if with string argument shows the contents with true" do
|
|
867
|
+
allow(context).to receive(:goodbye).and_return("dummy")
|
|
868
|
+
allow(context).to receive(:world).and_return("world")
|
|
871
869
|
expect(subject).to eq "GOODBYE cruel world!"
|
|
872
870
|
end
|
|
873
871
|
|
|
874
|
-
example
|
|
872
|
+
example "if with boolean argument does not show the contents when false" do
|
|
875
873
|
allow(context).to receive(:goodbye).and_return(false)
|
|
876
|
-
allow(context).to receive(:world).and_return(
|
|
874
|
+
allow(context).to receive(:world).and_return("world")
|
|
877
875
|
expect(subject).to eq "cruel world!"
|
|
878
876
|
end
|
|
879
877
|
|
|
880
|
-
example
|
|
878
|
+
example "if with undefined does not show the contents" do
|
|
881
879
|
allow(context).to receive(:goodbye)
|
|
882
|
-
allow(context).to receive(:world).and_return(
|
|
880
|
+
allow(context).to receive(:world).and_return("world")
|
|
883
881
|
expect(subject).to eq "cruel world!"
|
|
884
882
|
end
|
|
885
883
|
|
|
886
|
-
example
|
|
887
|
-
allow(context).to receive(:goodbye).and_return([
|
|
888
|
-
allow(context).to receive(:world).and_return(
|
|
884
|
+
example "if with non-empty array shows the contents" do
|
|
885
|
+
allow(context).to receive(:goodbye).and_return(["foo"])
|
|
886
|
+
allow(context).to receive(:world).and_return("world")
|
|
889
887
|
expect(subject).to eq "GOODBYE cruel world!"
|
|
890
888
|
end
|
|
891
889
|
|
|
892
|
-
example
|
|
890
|
+
example "if with empty array does not show the contents" do
|
|
893
891
|
allow(context).to receive(:goodbye).and_return([])
|
|
894
|
-
allow(context).to receive(:world).and_return(
|
|
892
|
+
allow(context).to receive(:world).and_return("world")
|
|
895
893
|
expect(subject).to eq "cruel world!"
|
|
896
894
|
end
|
|
897
895
|
end
|
|
898
896
|
|
|
899
|
-
describe
|
|
897
|
+
describe "#each" do
|
|
900
898
|
let(:template) { "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!" }
|
|
901
899
|
|
|
902
|
-
example
|
|
900
|
+
example "each with array iterates over the contents with non-empty" do
|
|
903
901
|
g = Struct.new(:text)
|
|
904
|
-
allow(context).to receive(:goodbyes).and_return([g.new(
|
|
905
|
-
allow(context).to receive(:world).and_return(
|
|
902
|
+
allow(context).to receive(:goodbyes).and_return([g.new("goodbye"), g.new("Goodbye"), g.new("GOODBYE")])
|
|
903
|
+
allow(context).to receive(:world).and_return("world")
|
|
906
904
|
expect(subject).to eq "goodbye! Goodbye! GOODBYE! cruel world!"
|
|
907
905
|
end
|
|
908
906
|
|
|
909
|
-
example
|
|
907
|
+
example "each with array ignores the contents when empty" do
|
|
910
908
|
allow(context).to receive(:goodbyes).and_return([])
|
|
911
|
-
allow(context).to receive(:world).and_return(
|
|
909
|
+
allow(context).to receive(:world).and_return("world")
|
|
912
910
|
expect(subject).to eq "cruel world!"
|
|
913
911
|
end
|
|
914
912
|
end
|
|
915
913
|
|
|
916
|
-
describe
|
|
914
|
+
describe "each with @index" do
|
|
917
915
|
let(:template) { "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!" }
|
|
918
916
|
|
|
919
|
-
example
|
|
917
|
+
example "the @index variable is used" do
|
|
920
918
|
g = Struct.new(:text)
|
|
921
|
-
allow(context).to receive(:goodbyes).and_return([g.new(
|
|
922
|
-
allow(context).to receive(:world).and_return(
|
|
919
|
+
allow(context).to receive(:goodbyes).and_return([g.new("goodbye"), g.new("Goodbye"), g.new("GOODBYE")])
|
|
920
|
+
allow(context).to receive(:world).and_return("world")
|
|
923
921
|
expect(subject).to eq "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!"
|
|
924
922
|
end
|
|
925
923
|
end
|
|
926
924
|
|
|
927
|
-
describe
|
|
925
|
+
describe "each with @index in a nested block" do
|
|
928
926
|
let(:template) { "{{#each goodbyes}}{{#if @last}}{{@index}}. {{/if}}{{text}}! {{/each}}cruel {{world}}!" }
|
|
929
927
|
|
|
930
|
-
example
|
|
928
|
+
example "the @index variable is used" do
|
|
931
929
|
g = Struct.new(:text)
|
|
932
|
-
allow(context).to receive(:goodbyes).and_return([g.new(
|
|
933
|
-
allow(context).to receive(:world).and_return(
|
|
930
|
+
allow(context).to receive(:goodbyes).and_return([g.new("goodbye"), g.new("Goodbye"), g.new("GOODBYE")])
|
|
931
|
+
allow(context).to receive(:world).and_return("world")
|
|
934
932
|
expect(subject).to eq "goodbye! Goodbye! 2. GOODBYE! cruel world!"
|
|
935
933
|
end
|
|
936
934
|
end
|
|
937
935
|
|
|
938
|
-
describe
|
|
936
|
+
describe "each with @last" do
|
|
939
937
|
let(:template) { "{{#each goodbyes}}{{@index}}. {{text}}! {{#if @last}}last{{/if}}{{/each}} cruel {{world}}!" }
|
|
940
938
|
|
|
941
|
-
example
|
|
939
|
+
example "the @last variable is used" do
|
|
942
940
|
g = Struct.new(:text)
|
|
943
|
-
allow(context).to receive(:goodbyes).and_return([g.new(
|
|
944
|
-
allow(context).to receive(:world).and_return(
|
|
941
|
+
allow(context).to receive(:goodbyes).and_return([g.new("goodbye"), g.new("Goodbye"), g.new("GOODBYE")])
|
|
942
|
+
allow(context).to receive(:world).and_return("world")
|
|
945
943
|
expect(subject).to eq "0. goodbye! 1. Goodbye! 2. GOODBYE! last cruel world!"
|
|
946
944
|
end
|
|
947
945
|
end
|
|
948
946
|
|
|
949
|
-
describe
|
|
947
|
+
describe "each with @first" do
|
|
950
948
|
let(:template) { "{{#each goodbyes}}{{@index}}. {{text}} {{#if @first}}first{{/if}}! {{/each}}cruel {{world}}!" }
|
|
951
949
|
|
|
952
|
-
example
|
|
950
|
+
example "the first variable is used" do
|
|
953
951
|
g = Struct.new(:text)
|
|
954
|
-
allow(context).to receive(:goodbyes).and_return([g.new(
|
|
955
|
-
allow(context).to receive(:world).and_return(
|
|
952
|
+
allow(context).to receive(:goodbyes).and_return([g.new("goodbye"), g.new("Goodbye"), g.new("GOODBYE")])
|
|
953
|
+
allow(context).to receive(:world).and_return("world")
|
|
956
954
|
expect(subject).to eq "0. goodbye first! 1. Goodbye ! 2. GOODBYE ! cruel world!"
|
|
957
955
|
end
|
|
958
956
|
end
|
|
959
957
|
|
|
960
|
-
describe
|
|
958
|
+
describe "each with @root" do
|
|
961
959
|
let(:template) { "{{#each goodbyes}}{{@index}}. {{text}} cruel {{@root.place}}! {{/each}}" }
|
|
962
960
|
|
|
963
|
-
example
|
|
961
|
+
example "the root variable is used" do
|
|
964
962
|
g = Struct.new(:text)
|
|
965
|
-
allow(context).to receive(:goodbyes).and_return([g.new(
|
|
966
|
-
allow(context).to receive(:place).and_return(
|
|
963
|
+
allow(context).to receive(:goodbyes).and_return([g.new("goodbye"), g.new("Goodbye"), g.new("GOODBYE")])
|
|
964
|
+
allow(context).to receive(:place).and_return("world")
|
|
967
965
|
expect(subject).to eq "0. goodbye cruel world! 1. Goodbye cruel world! 2. GOODBYE cruel world! "
|
|
968
966
|
end
|
|
969
967
|
end
|
|
970
968
|
|
|
971
|
-
describe
|
|
969
|
+
describe "each with @root in a nested block" do
|
|
972
970
|
let(:template) { "{{#each goodbyes}}{{@index}}. {{text}}{{#if @last}} cruel {{@root.place}}{{/if}}! {{/each}}" }
|
|
973
971
|
|
|
974
|
-
example
|
|
972
|
+
example "the root variable is used" do
|
|
975
973
|
g = Struct.new(:text)
|
|
976
|
-
allow(context).to receive(:goodbyes).and_return([g.new(
|
|
977
|
-
allow(context).to receive(:place).and_return(
|
|
974
|
+
allow(context).to receive(:goodbyes).and_return([g.new("goodbye"), g.new("Goodbye"), g.new("GOODBYE")])
|
|
975
|
+
allow(context).to receive(:place).and_return("world")
|
|
978
976
|
expect(subject).to eq "0. goodbye! 1. Goodbye! 2. GOODBYE cruel world! "
|
|
979
977
|
end
|
|
980
978
|
end
|
|
981
979
|
|
|
982
|
-
describe
|
|
980
|
+
describe "each with @key" do
|
|
983
981
|
let(:template) { "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!" }
|
|
984
982
|
|
|
985
|
-
example
|
|
983
|
+
example "the @key variable is used" do
|
|
986
984
|
g = Struct.new(:text)
|
|
987
985
|
allow(context).to receive(:goodbyes).and_return({
|
|
988
|
-
one: g.new(
|
|
989
|
-
two: g.new(
|
|
990
|
-
three: g.new(
|
|
986
|
+
one: g.new("goodbye"),
|
|
987
|
+
two: g.new("Goodbye"),
|
|
988
|
+
three: g.new("GOODBYE")
|
|
991
989
|
})
|
|
992
|
-
allow(context).to receive(:world).and_return(
|
|
990
|
+
allow(context).to receive(:world).and_return("world")
|
|
993
991
|
expect(subject).to eq "one. goodbye! two. Goodbye! three. GOODBYE! cruel world!"
|
|
994
992
|
end
|
|
995
993
|
end
|
|
996
994
|
|
|
997
|
-
describe
|
|
995
|
+
describe "each with @key in a nested block" do
|
|
998
996
|
let(:template) { "{{#each goodbyes}}{{#if @last}}{{@key}}. {{/if}}{{text}}! {{/each}}cruel {{world}}!" }
|
|
999
997
|
|
|
1000
|
-
example
|
|
998
|
+
example "the @key variable is used" do
|
|
1001
999
|
g = Struct.new(:text)
|
|
1002
1000
|
allow(context).to receive(:goodbyes).and_return({
|
|
1003
|
-
one: g.new(
|
|
1004
|
-
two: g.new(
|
|
1005
|
-
three: g.new(
|
|
1001
|
+
one: g.new("goodbye"),
|
|
1002
|
+
two: g.new("Goodbye"),
|
|
1003
|
+
three: g.new("GOODBYE")
|
|
1006
1004
|
})
|
|
1007
|
-
allow(context).to receive(:world).and_return(
|
|
1005
|
+
allow(context).to receive(:world).and_return("world")
|
|
1008
1006
|
expect(subject).to eq "goodbye! Goodbye! three. GOODBYE! cruel world!"
|
|
1009
1007
|
end
|
|
1010
1008
|
end
|
|
1011
1009
|
|
|
1012
|
-
describe
|
|
1010
|
+
describe "log" do
|
|
1013
1011
|
let(:template) { "{{log blah}}" }
|
|
1014
1012
|
let(:log) { double(:log) }
|
|
1015
1013
|
before { FS.logger = log }
|
|
1016
|
-
after
|
|
1014
|
+
after { FS.logger = nil }
|
|
1017
1015
|
example do
|
|
1018
|
-
allow(context).to receive(:blah).and_return(
|
|
1019
|
-
expect(log).to receive(:debug).with(
|
|
1020
|
-
expect(subject).to eq
|
|
1016
|
+
allow(context).to receive(:blah).and_return("whee")
|
|
1017
|
+
expect(log).to receive(:debug).with("FlavourSaver: whee")
|
|
1018
|
+
expect(subject).to eq ""
|
|
1021
1019
|
end
|
|
1022
1020
|
end
|
|
1023
1021
|
end
|