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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +14 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +57 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +48 -5
  7. data/README.md +153 -271
  8. data/flavour_saver.gemspec +17 -19
  9. data/lib/flavour_saver/error.rb +5 -0
  10. data/lib/flavour_saver/helpers.rb +19 -18
  11. data/lib/flavour_saver/lexer.rb +123 -50
  12. data/lib/flavour_saver/nodes.rb +107 -19
  13. data/lib/flavour_saver/parser.rb +339 -161
  14. data/lib/flavour_saver/partial.rb +4 -6
  15. data/lib/flavour_saver/rails_partial.rb +10 -4
  16. data/lib/flavour_saver/runtime.rb +76 -81
  17. data/lib/flavour_saver/template.rb +3 -5
  18. data/lib/flavour_saver/version.rb +1 -1
  19. data/lib/flavour_saver.rb +17 -17
  20. data/spec/acceptance/backtrack_spec.rb +9 -9
  21. data/spec/acceptance/comment_spec.rb +7 -7
  22. data/spec/acceptance/custom_block_helper_spec.rb +12 -12
  23. data/spec/acceptance/custom_helper_spec.rb +8 -8
  24. data/spec/acceptance/ensure_no_rce_spec.rb +64 -53
  25. data/spec/acceptance/handlebars_qunit_spec.rb +295 -297
  26. data/spec/acceptance/if_else_spec.rb +6 -6
  27. data/spec/acceptance/multi_level_with_spec.rb +10 -11
  28. data/spec/acceptance/nesting_depth_spec.rb +34 -0
  29. data/spec/acceptance/one_character_identifier_spec.rb +8 -8
  30. data/spec/acceptance/raw_block_spec.rb +7 -7
  31. data/spec/acceptance/sections_spec.rb +17 -14
  32. data/spec/acceptance/segment_literals_spec.rb +5 -5
  33. data/spec/acceptance/simple_expression_spec.rb +9 -9
  34. data/spec/acceptance/subexpression_spec.rb +18 -17
  35. data/spec/acceptance/unless_spec.rb +19 -6
  36. data/spec/lib/flavour_saver/lexer_spec.rb +103 -89
  37. data/spec/lib/flavour_saver/nodes_spec.rb +85 -0
  38. data/spec/lib/flavour_saver/parser_spec.rb +155 -95
  39. data/spec/lib/flavour_saver/runtime_spec.rb +94 -96
  40. data/spec/lib/flavour_saver/template_spec.rb +1 -2
  41. metadata +19 -48
@@ -1,104 +1,100 @@
1
- require 'flavour_saver/parser'
2
- require 'flavour_saver/lexer'
1
+ require "flavour_saver/parser"
2
+ require "flavour_saver/lexer"
3
3
 
4
4
  describe FlavourSaver::Parser do
5
- let (:items) { subject.items }
5
+ let(:items) { subject.items }
6
6
 
7
- it 'is a RLTK::Parser' do
8
- expect(subject).to be_a(RLTK::Parser)
9
- end
10
-
11
- describe 'HTML template' do
12
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('<html><h1>Hello world!</h1></html>')) }
7
+ describe "HTML template" do
8
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("<html><h1>Hello world!</h1></html>")) }
13
9
 
14
- it 'is an output node' do
10
+ it "is an output node" do
15
11
  expect(items.first).to be_a(FlavourSaver::OutputNode)
16
12
  end
17
13
 
18
- it 'has the correct contents' do
19
- expect(items.first.value).to eq '<html><h1>Hello world!</h1></html>'
14
+ it "has the correct contents" do
15
+ expect(items.first.value).to eq "<html><h1>Hello world!</h1></html>"
20
16
  end
21
17
  end
22
18
 
23
- describe 'HTML template containing a handlebars expression' do
24
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('<html>{{foo}}</html>')) }
19
+ describe "HTML template containing a handlebars expression" do
20
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("<html>{{foo}}</html>")) }
25
21
 
26
- it 'has template output either side of the expression' do
22
+ it "has template output either side of the expression" do
27
23
  expect(items.map(&:class)).to eq [FlavourSaver::OutputNode, FlavourSaver::ExpressionNode, FlavourSaver::OutputNode]
28
24
  end
29
25
  end
30
26
 
31
- describe '{{foo}}' do
32
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo}}')) }
27
+ describe "{{foo}}" do
28
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{foo}}")) }
33
29
 
34
- it 'contains an expression node' do
30
+ it "contains an expression node" do
35
31
  expect(items.first).to be_a(FlavourSaver::ExpressionNode)
36
32
  end
37
33
 
38
34
  it 'calls the method "foo" with no arguments' do
39
35
  expect(items.first.method).to be_one
40
36
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
41
- expect(items.first.method.first.name).to eq 'foo'
37
+ expect(items.first.method.first.name).to eq "foo"
42
38
  expect(items.first.method.first.arguments).to be_empty
43
39
  end
44
40
  end
45
41
 
46
- describe '{{foo.bar}}' do
47
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo.bar}}')) }
42
+ describe "{{foo.bar}}" do
43
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{foo.bar}}")) }
48
44
 
49
- it 'calls two methods' do
45
+ it "calls two methods" do
50
46
  expect(items.first.method.size).to eq 2
51
47
  end
52
48
 
53
49
  it 'calls the method "foo" with no arguments first' do
54
50
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
55
- expect(items.first.method.first.name).to eq 'foo'
51
+ expect(items.first.method.first.name).to eq "foo"
56
52
  expect(items.first.method.first.arguments).to be_empty
57
53
  end
58
54
 
59
55
  it 'calls the method "bar" with no arguments second' do
60
56
  expect(items.first.method[1]).to be_a(FlavourSaver::CallNode)
61
- expect(items.first.method[1].name).to eq 'bar'
57
+ expect(items.first.method[1].name).to eq "bar"
62
58
  expect(items.first.method[1].arguments).to be_empty
63
59
  end
64
60
  end
65
61
 
66
- describe '{{foo.[&@^$*].bar}}' do
67
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo.[&@^$*].bar}}')) }
62
+ describe "{{foo.[&@^$*].bar}}" do
63
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{foo.[&@^$*].bar}}")) }
68
64
 
69
- it 'calls three methods' do
65
+ it "calls three methods" do
70
66
  expect(items.first.method.size).to eq 3
71
67
  end
72
68
 
73
69
  it 'calls the method "foo" with no arguments first' do
74
70
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
75
- expect(items.first.method.first.name).to eq 'foo'
71
+ expect(items.first.method.first.name).to eq "foo"
76
72
  expect(items.first.method.first.arguments).to be_empty
77
73
  end
78
74
 
79
75
  it 'calls the method "&@^$*" with no arguments second' do
80
76
  expect(items.first.method[1]).to be_a(FlavourSaver::CallNode)
81
- expect(items.first.method[1].name).to eq '&@^$*'
77
+ expect(items.first.method[1].name).to eq "&@^$*"
82
78
  expect(items.first.method[1].arguments).to be_empty
83
79
  end
84
80
 
85
81
  it 'calls the method "bar" with no arguments third' do
86
82
  expect(items.first.method[2]).to be_a(FlavourSaver::CallNode)
87
- expect(items.first.method[2].name).to eq 'bar'
83
+ expect(items.first.method[2].name).to eq "bar"
88
84
  expect(items.first.method[2].arguments).to be_empty
89
85
  end
90
86
  end
91
87
 
92
- describe '{{foo bar}}' do
93
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo bar}}')) }
88
+ describe "{{foo bar}}" do
89
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{foo bar}}")) }
94
90
 
95
91
  it 'calls the method "foo" with a method argument of "bar"' do
96
92
  expect(items.first.method).to be_one
97
93
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
98
- expect(items.first.method.first.name).to eq 'foo'
94
+ expect(items.first.method.first.name).to eq "foo"
99
95
  expect(items.first.method.first.arguments).to be_one
100
96
  expect(items.first.method.first.arguments.first.first).to be_a(FlavourSaver::CallNode)
101
- expect(items.first.method.first.arguments.first.first.name).to eq 'bar'
97
+ expect(items.first.method.first.arguments.first.first.name).to eq "bar"
102
98
  end
103
99
  end
104
100
 
@@ -108,10 +104,10 @@ describe FlavourSaver::Parser do
108
104
  it 'calls the method "foo" with a string argument of "bar"' do
109
105
  expect(items.first.method).to be_one
110
106
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
111
- expect(items.first.method.first.name).to eq 'foo'
107
+ expect(items.first.method.first.name).to eq "foo"
112
108
  expect(items.first.method.first.arguments).to be_one
113
109
  expect(items.first.method.first.arguments.first).to be_a(FlavourSaver::StringNode)
114
- expect(items.first.method.first.arguments.first.value).to eq 'bar'
110
+ expect(items.first.method.first.arguments.first.value).to eq "bar"
115
111
  end
116
112
  end
117
113
 
@@ -121,23 +117,23 @@ describe FlavourSaver::Parser do
121
117
  it 'calls the method "foo"' do
122
118
  expect(items.first.method).to be_one
123
119
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
124
- expect(items.first.method.first.name).to eq 'foo'
120
+ expect(items.first.method.first.name).to eq "foo"
125
121
  end
126
122
 
127
- describe 'with arguments' do
123
+ describe "with arguments" do
128
124
  subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo bar "baz" }}')).items.first.method.first.arguments }
129
125
 
130
- describe '[0]' do
126
+ describe "[0]" do
131
127
  it 'is the method call "bar" with no arguments' do
132
128
  expect(subject.first.first).to be_a(FlavourSaver::CallNode)
133
- expect(subject.first.first.name).to eq 'bar'
129
+ expect(subject.first.first.name).to eq "bar"
134
130
  end
135
131
  end
136
132
 
137
- describe '[1]' do
133
+ describe "[1]" do
138
134
  it 'is the string "baz"' do
139
135
  expect(subject[1]).to be_a(FlavourSaver::StringNode)
140
- expect(subject[1].value).to eq 'baz'
136
+ expect(subject[1].value).to eq "baz"
141
137
  end
142
138
  end
143
139
  end
@@ -149,19 +145,18 @@ describe FlavourSaver::Parser do
149
145
  it 'calls the method "foo"' do
150
146
  expect(items.first.method).to be_one
151
147
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
152
- expect(items.first.method.first.name).to eq 'foo'
148
+ expect(items.first.method.first.name).to eq "foo"
153
149
  end
154
150
 
155
- describe 'with arguments' do
151
+ describe "with arguments" do
156
152
  subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo (bar "baz") }}')).items.first.method.first.arguments }
157
153
 
158
- describe '[0]' do
159
- it 'is a subexpression' do
154
+ describe "[0]" do
155
+ it "is a subexpression" do
160
156
  expect(subject.first.first).to be_a(FlavourSaver::CallNode)
161
- expect(subject.first.first.name).to eq 'bar'
157
+ expect(subject.first.first.name).to eq "bar"
162
158
  end
163
159
  end
164
-
165
160
  end
166
161
  end
167
162
 
@@ -170,9 +165,9 @@ describe FlavourSaver::Parser do
170
165
 
171
166
  it 'calls the method "foo" with the hash {:bar => "baz"} as arguments' do
172
167
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
173
- expect(items.first.method.first.name).to eq 'foo'
168
+ expect(items.first.method.first.name).to eq "foo"
174
169
  expect(items.first.method.first.arguments.first).to be_a(Hash)
175
- expect(items.first.method.first.arguments.first).to eq({ :bar => FlavourSaver::StringNode.new('baz') })
170
+ expect(items.first.method.first.arguments.first).to eq({bar: FlavourSaver::StringNode.new("baz")})
176
171
  end
177
172
  end
178
173
 
@@ -181,7 +176,7 @@ describe FlavourSaver::Parser do
181
176
 
182
177
  it 'calls the method "foo" with the hash {:bar => (baz "qux")} as arguments' do
183
178
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
184
- expect(items.first.method.first.name).to eq 'foo'
179
+ expect(items.first.method.first.name).to eq "foo"
185
180
  expect(items.first.method.first.arguments.first).to be_a(Hash)
186
181
  expect(items.first.method.first.arguments.first[:bar].first).to be_a(FlavourSaver::CallNode)
187
182
  end
@@ -196,9 +191,9 @@ describe FlavourSaver::Parser do
196
191
 
197
192
  it 'calls the method "foo" with the hash {:bar => 1} as arguments' do
198
193
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
199
- expect(items.first.method.first.name).to eq 'foo'
194
+ expect(items.first.method.first.name).to eq "foo"
200
195
  expect(items.first.method.first.arguments.first).to be_a(Hash)
201
- expect(items.first.method.first.arguments.first).to eq({ :bar => FlavourSaver::NumberNode.new('1') })
196
+ expect(items.first.method.first.arguments.first).to eq({bar: FlavourSaver::NumberNode.new("1")})
202
197
  end
203
198
  end
204
199
 
@@ -207,90 +202,98 @@ describe FlavourSaver::Parser do
207
202
 
208
203
  it 'calls the method "foo" with the hash {:bar => "baz", :fred => "wilma"} as arguments' do
209
204
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
210
- expect(items.first.method.first.name).to eq 'foo'
205
+ expect(items.first.method.first.name).to eq "foo"
211
206
  expect(items.first.method.first.arguments.first).to be_a(Hash)
212
- expect(items.first.method.first.arguments.first).to eq({ :bar => FlavourSaver::StringNode.new('baz'), :fred => FlavourSaver::StringNode.new('wilma') })
207
+ expect(items.first.method.first.arguments.first).to eq({bar: FlavourSaver::StringNode.new("baz"), fred: FlavourSaver::StringNode.new("wilma")})
213
208
  end
214
209
  end
215
210
 
216
211
  describe '{{foo bar="baz" fred "wilma"}}' do
217
212
  subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo bar="baz" fred "wilma"}}')) }
218
213
 
219
- it 'raises an exception' do
220
- expect { subject }.to raise_exception(RLTK::NotInLanguage)
214
+ it "raises an exception" do
215
+ expect { subject }.to raise_exception(FlavourSaver::Parser::NotInLanguage)
221
216
  end
222
217
  end
223
218
 
224
- describe '{{{foo}}}' do
225
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{{foo}}}')) }
219
+ describe "{{{foo}}}" do
220
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{{foo}}}")) }
226
221
 
227
- it 'returns a safe expression node' do
222
+ it "returns a safe expression node" do
228
223
  expect(items.first).to be_a(FlavourSaver::SafeExpressionNode)
229
224
  end
230
225
  end
231
226
 
232
- describe '{{../foo}}' do
233
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{../foo}}')) }
227
+ describe "{{../foo}}" do
228
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{../foo}}")) }
234
229
 
235
- it 'returns a parent call node' do
230
+ it "returns a parent call node" do
236
231
  expect(items.first.method.first).to be_a(FlavourSaver::ParentCallNode)
237
232
  end
238
233
  end
239
234
 
240
- describe '{{! comment}}' do
241
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{! comment}}')) }
235
+ describe "{{! comment}}" do
236
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{! comment}}")) }
242
237
 
243
- it 'returns a comment node' do
238
+ it "returns a comment node" do
244
239
  expect(items.first).to be_a(FlavourSaver::CommentNode)
245
240
  end
246
241
  end
247
242
 
248
- describe '{{#foo}}hello{{/foo}}' do
249
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{#foo}}hello{{/foo}}')) }
243
+ describe "{{#foo}}hello{{/foo}}" do
244
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{#foo}}hello{{/foo}}")) }
250
245
 
251
- it 'has a block start and end' do
252
- expect(items.map(&:class)).to eq [ FlavourSaver::BlockExpressionNode ]
246
+ it "has a block start and end" do
247
+ expect(items.map(&:class)).to eq [FlavourSaver::BlockExpressionNode]
253
248
  end
254
249
 
255
- describe '#contents' do
256
- it 'contains a single output node' do
250
+ describe "#contents" do
251
+ it "contains a single output node" do
257
252
  expect(items.first.contents.items.size).to eq 1
258
253
  expect(items.first.contents.items.first).to be_a(FlavourSaver::OutputNode)
259
- expect(items.first.contents.items.first.value).to eq 'hello'
254
+ expect(items.first.contents.items.first.value).to eq "hello"
260
255
  end
261
256
  end
262
257
  end
263
258
 
264
- describe '{{/foo}}' do
265
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{/foo}}')) }
259
+ describe "{{/foo}}" do
260
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{/foo}}")) }
261
+
262
+ it "raises a syntax error" do
263
+ expect { subject }.to raise_error(FlavourSaver::Parser::NotInLanguage)
264
+ end
266
265
 
267
- it 'raises a syntax error' do
268
- expect { subject }.to raise_error(RLTK::NotInLanguage)
266
+ it "is a FlavourSaver::Error" do
267
+ expect { subject }.to raise_error(FlavourSaver::Error)
269
268
  end
270
269
  end
271
270
 
272
- describe '{{#foo}}' do
273
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{#foo}}')) }
271
+ describe "{{#foo}}" do
272
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{#foo}}")) }
274
273
 
275
- it 'raises a syntax error' do
276
- expect { subject }.to raise_error(RLTK::NotInLanguage)
274
+ it "raises a syntax error" do
275
+ expect { subject }.to raise_error(FlavourSaver::Parser::NotInLanguage)
277
276
  end
278
277
  end
279
278
 
280
279
  describe "{{foo}}\n" do
281
280
  subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{foo}}\n")) }
282
281
 
283
- it 'has a block start and end' do
284
- expect(items.map(&:class)).to eq [ FlavourSaver::ExpressionNode, FlavourSaver::OutputNode ]
282
+ it "has a block start and end" do
283
+ expect(items.map(&:class)).to eq [FlavourSaver::ExpressionNode, FlavourSaver::OutputNode]
285
284
  end
286
285
  end
287
286
 
288
- describe '{{#foo}}{{#bar}}{{/foo}}' do
289
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{#foo}}{{#bar}}{{/foo}}')) }
287
+ describe "{{#foo}}{{#bar}}{{/foo}}" do
288
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{#foo}}{{#bar}}{{/foo}}")) }
290
289
 
291
- it 'raises a syntax error' do
290
+ it "raises a syntax error" do
292
291
  expect { subject }.to raise_error(FlavourSaver::Parser::UnbalancedBlockError)
293
292
  end
293
+
294
+ it "is a FlavourSaver::Error" do
295
+ expect { subject }.to raise_error(FlavourSaver::Error)
296
+ end
294
297
  end
295
298
 
296
299
  describe "{{#foo}}\n{{/foo}}" do
@@ -302,12 +305,12 @@ describe FlavourSaver::Parser do
302
305
  end
303
306
 
304
307
  describe "{{#foo}}{#foo}}{{/foo}}{{/foo}}" do
305
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{#foo}}{{#foo}}{{/foo}}{{/foo}}')) }
308
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{#foo}}{{#foo}}{{/foo}}{{/foo}}")) }
306
309
 
307
- describe 'the outer block' do
310
+ describe "the outer block" do
308
311
  let(:block) { subject.items.first }
309
312
 
310
- it 'should contain another block' do
313
+ it "should contain another block" do
311
314
  expect(block.contents.items.size).to eq 1
312
315
  expect(block.contents.items.first).to be_a(FlavourSaver::BlockExpressionNode)
313
316
  end
@@ -329,10 +332,10 @@ describe FlavourSaver::Parser do
329
332
  expect { subject }.to_not raise_error
330
333
  end
331
334
  end
332
- describe '' do
333
- subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('')) }
335
+ describe "" do
336
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("")) }
334
337
 
335
- it 'returns an empty template' do
338
+ it "returns an empty template" do
336
339
  expect(items).to be_empty
337
340
  end
338
341
  end
@@ -340,11 +343,68 @@ describe FlavourSaver::Parser do
340
343
  subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex('{{foo "bar" fred="wilma"}}')) }
341
344
  it 'calls the method "foo" with the "bar" and {:fred => "wilma"} as arguments' do
342
345
  expect(items.first.method.first).to be_a(FlavourSaver::CallNode)
343
- expect(items.first.method.first.name).to eq 'foo'
346
+ expect(items.first.method.first.name).to eq "foo"
344
347
  expect(items.first.method.first.arguments.first).to be_a(FlavourSaver::StringNode)
345
- expect(items.first.method.first.arguments.first.value).to eq 'bar'
348
+ expect(items.first.method.first.arguments.first.value).to eq "bar"
346
349
  expect(items.first.method.first.arguments.last).to be_a(Hash)
347
- expect(items.first.method.first.arguments.last).to eq({ :fred => FlavourSaver::StringNode.new('wilma') })
350
+ expect(items.first.method.first.arguments.last).to eq({fred: FlavourSaver::StringNode.new("wilma")})
351
+ end
352
+ end
353
+ describe "{{^foo}}falsy{{else}}truthy{{/foo}}" do
354
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{^foo}}falsy{{else}}truthy{{/foo}}")) }
355
+
356
+ it "renders the {{else}} section when foo is truthy and the body when it is falsy" do
357
+ expect(items.first).to be_a(FlavourSaver::BlockExpressionNodeWithElse)
358
+ expect(items.first.contents.items.map(&:value)).to eq ["truthy"]
359
+ expect(items.first.alternate.items.map(&:value)).to eq ["falsy"]
360
+ end
361
+ end
362
+
363
+ describe "{{#foo bar}}{{/foo}}" do
364
+ subject { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex("{{#foo bar}}{{/foo}}")) }
365
+
366
+ it "links each call to the node that contains it" do
367
+ expect(items.first.method.first.parent).to eq items.first
368
+ expect(items.first.method.first.arguments.first.first.parent).to be_nil
369
+ expect(items.first.parent).to eq subject
370
+ end
371
+ end
372
+
373
+ describe "nesting depth" do
374
+ let(:max) { FlavourSaver::Parser::MAX_DEPTH }
375
+ let(:parse) { ->(template) { FlavourSaver::Parser.parse(FlavourSaver::Lexer.lex(template)) } }
376
+
377
+ def blocks(depth)
378
+ ("{{#foo}}" * depth) + ("{{/foo}}" * depth)
379
+ end
380
+
381
+ def subexpressions(depth)
382
+ "{{foo #{"(foo " * depth}bar#{")" * depth}}}"
383
+ end
384
+
385
+ it "allows blocks nested up to the limit" do
386
+ expect { parse.call(blocks(max)) }.not_to raise_error
387
+ end
388
+
389
+ it "raises NotInLanguage for blocks nested past the limit" do
390
+ expect { parse.call(blocks(max + 1)) }.to raise_error(FlavourSaver::Parser::NotInLanguage, /more than #{max} levels/)
391
+ end
392
+
393
+ it "allows subexpressions nested up to the limit" do
394
+ expect { parse.call(subexpressions(max)) }.not_to raise_error
395
+ end
396
+
397
+ it "raises NotInLanguage for subexpressions nested past the limit" do
398
+ expect { parse.call(subexpressions(max + 1)) }.to raise_error(FlavourSaver::Parser::NotInLanguage, /more than #{max} levels/)
399
+ end
400
+
401
+ it "counts blocks and subexpressions together" do
402
+ template = ("{{#foo}}" * max) + "{{bar (baz)}}" + ("{{/foo}}" * max)
403
+ expect { parse.call(template) }.to raise_error(FlavourSaver::Parser::NotInLanguage, /more than #{max} levels/)
404
+ end
405
+
406
+ it "raises NotInLanguage rather than SystemStackError inside a Fiber" do
407
+ expect { Fiber.new { parse.call(blocks(10_000)) }.resume }.to raise_error(FlavourSaver::Parser::NotInLanguage)
348
408
  end
349
409
  end
350
410
  end