curlybars 0.9.13

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 (77) hide show
  1. checksums.yaml +7 -0
  2. data/lib/curlybars.rb +108 -0
  3. data/lib/curlybars/configuration.rb +41 -0
  4. data/lib/curlybars/dependency_tracker.rb +8 -0
  5. data/lib/curlybars/error/base.rb +18 -0
  6. data/lib/curlybars/error/compile.rb +11 -0
  7. data/lib/curlybars/error/lex.rb +22 -0
  8. data/lib/curlybars/error/parse.rb +41 -0
  9. data/lib/curlybars/error/presenter/not_found.rb +23 -0
  10. data/lib/curlybars/error/render.rb +11 -0
  11. data/lib/curlybars/error/validate.rb +18 -0
  12. data/lib/curlybars/lexer.rb +60 -0
  13. data/lib/curlybars/method_whitelist.rb +69 -0
  14. data/lib/curlybars/node/block_helper_else.rb +108 -0
  15. data/lib/curlybars/node/boolean.rb +24 -0
  16. data/lib/curlybars/node/each_else.rb +69 -0
  17. data/lib/curlybars/node/if_else.rb +33 -0
  18. data/lib/curlybars/node/item.rb +31 -0
  19. data/lib/curlybars/node/literal.rb +28 -0
  20. data/lib/curlybars/node/option.rb +25 -0
  21. data/lib/curlybars/node/output.rb +24 -0
  22. data/lib/curlybars/node/partial.rb +24 -0
  23. data/lib/curlybars/node/path.rb +137 -0
  24. data/lib/curlybars/node/root.rb +29 -0
  25. data/lib/curlybars/node/string.rb +24 -0
  26. data/lib/curlybars/node/template.rb +32 -0
  27. data/lib/curlybars/node/text.rb +24 -0
  28. data/lib/curlybars/node/unless_else.rb +33 -0
  29. data/lib/curlybars/node/variable.rb +34 -0
  30. data/lib/curlybars/node/with_else.rb +54 -0
  31. data/lib/curlybars/parser.rb +183 -0
  32. data/lib/curlybars/position.rb +7 -0
  33. data/lib/curlybars/presenter.rb +288 -0
  34. data/lib/curlybars/processor/tilde.rb +31 -0
  35. data/lib/curlybars/processor/token_factory.rb +9 -0
  36. data/lib/curlybars/railtie.rb +18 -0
  37. data/lib/curlybars/rendering_support.rb +222 -0
  38. data/lib/curlybars/safe_buffer.rb +11 -0
  39. data/lib/curlybars/template_handler.rb +93 -0
  40. data/lib/curlybars/version.rb +3 -0
  41. data/spec/acceptance/application_layout_spec.rb +60 -0
  42. data/spec/acceptance/collection_blocks_spec.rb +28 -0
  43. data/spec/acceptance/global_helper_spec.rb +25 -0
  44. data/spec/curlybars/configuration_spec.rb +57 -0
  45. data/spec/curlybars/error/base_spec.rb +41 -0
  46. data/spec/curlybars/error/compile_spec.rb +19 -0
  47. data/spec/curlybars/error/lex_spec.rb +25 -0
  48. data/spec/curlybars/error/parse_spec.rb +74 -0
  49. data/spec/curlybars/error/render_spec.rb +19 -0
  50. data/spec/curlybars/error/validate_spec.rb +19 -0
  51. data/spec/curlybars/lexer_spec.rb +466 -0
  52. data/spec/curlybars/method_whitelist_spec.rb +168 -0
  53. data/spec/curlybars/processor/tilde_spec.rb +60 -0
  54. data/spec/curlybars/rendering_support_spec.rb +426 -0
  55. data/spec/curlybars/safe_buffer_spec.rb +46 -0
  56. data/spec/curlybars/template_handler_spec.rb +222 -0
  57. data/spec/integration/cache_spec.rb +124 -0
  58. data/spec/integration/comment_spec.rb +60 -0
  59. data/spec/integration/exception_spec.rb +31 -0
  60. data/spec/integration/node/block_helper_else_spec.rb +422 -0
  61. data/spec/integration/node/each_else_spec.rb +204 -0
  62. data/spec/integration/node/each_spec.rb +291 -0
  63. data/spec/integration/node/escape_spec.rb +27 -0
  64. data/spec/integration/node/helper_spec.rb +176 -0
  65. data/spec/integration/node/if_else_spec.rb +129 -0
  66. data/spec/integration/node/if_spec.rb +143 -0
  67. data/spec/integration/node/output_spec.rb +68 -0
  68. data/spec/integration/node/partial_spec.rb +66 -0
  69. data/spec/integration/node/path_spec.rb +286 -0
  70. data/spec/integration/node/root_spec.rb +15 -0
  71. data/spec/integration/node/template_spec.rb +86 -0
  72. data/spec/integration/node/unless_else_spec.rb +129 -0
  73. data/spec/integration/node/unless_spec.rb +130 -0
  74. data/spec/integration/node/with_spec.rb +116 -0
  75. data/spec/integration/processor/tilde_spec.rb +38 -0
  76. data/spec/integration/processors_spec.rb +30 -0
  77. metadata +358 -0
@@ -0,0 +1,204 @@
1
+ describe "{{#each collection}}...{{else}}...{{/each}}" do
2
+ let(:global_helpers_providers) { [] }
3
+
4
+ describe "#compile" do
5
+ let(:post) { double("post") }
6
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
7
+
8
+ it "uses each_template when collection is not empty" do
9
+ allow(presenter).to receive(:allows_method?).with(:non_empty_collection) { true }
10
+ allow(presenter).to receive(:non_empty_collection) { [presenter] }
11
+
12
+ template = Curlybars.compile(<<-HBS)
13
+ {{#each non_empty_collection}}
14
+ each_template
15
+ {{else}}
16
+ else_template
17
+ {{/each}}
18
+ HBS
19
+
20
+ expect(eval(template)).to resemble(<<-HTML)
21
+ each_template
22
+ HTML
23
+ end
24
+
25
+ it "uses else_template when collection is not empty" do
26
+ allow(presenter).to receive(:allows_method?).with(:empty_collection) { true }
27
+ allow(presenter).to receive(:empty_collection) { [] }
28
+
29
+ template = Curlybars.compile(<<-HBS)
30
+ {{#each empty_collection}}
31
+ each_template
32
+ {{else}}
33
+ else_template
34
+ {{/each}}
35
+ HBS
36
+
37
+ expect(eval(template)).to resemble(<<-HTML)
38
+ else_template
39
+ HTML
40
+ end
41
+
42
+ it "uses each_template when collection is not empty" do
43
+ path_presenter_class = Class.new(Curlybars::Presenter) do
44
+ presents :path
45
+ allow_methods :path
46
+ def path
47
+ @path
48
+ end
49
+ end
50
+
51
+ a_path_presenter = path_presenter_class.new(nil, path: 'a_path')
52
+ another_path_presenter = path_presenter_class.new(nil, path: 'another_path')
53
+
54
+ allow(presenter).to receive(:allows_method?).with(:non_empty_collection) { true }
55
+ allow(presenter).to receive(:non_empty_collection) { [a_path_presenter, another_path_presenter] }
56
+
57
+ template = Curlybars.compile(<<-HBS)
58
+ {{#each non_empty_collection}}
59
+ {{path}}
60
+ {{else}}
61
+ else_template
62
+ {{/each}}
63
+ HBS
64
+
65
+ expect(eval(template)).to resemble(<<-HTML)
66
+ a_path
67
+ another_path
68
+ HTML
69
+ end
70
+
71
+ it "allows empty each_template" do
72
+ allow(presenter).to receive(:allows_method?).with(:empty_collection) { true }
73
+ allow(presenter).to receive(:empty_collection) { [] }
74
+
75
+ template = Curlybars.compile(<<-HBS)
76
+ {{#each empty_collection}}{{else}}
77
+ else_template
78
+ {{/each}}
79
+ HBS
80
+
81
+ expect(eval(template)).to resemble(<<-HTML)
82
+ else_template
83
+ HTML
84
+ end
85
+
86
+ it "allows empty else_template" do
87
+ allow(presenter).to receive(:allows_method?).with(:non_empty_collection) { true }
88
+ allow(presenter).to receive(:non_empty_collection) { [presenter] }
89
+
90
+ template = Curlybars.compile(<<-HBS)
91
+ {{#each non_empty_collection}}
92
+ each_template
93
+ {{else}}{{/each}}
94
+ HBS
95
+
96
+ expect(eval(template)).to resemble(<<-HTML)
97
+ each_template
98
+ HTML
99
+ end
100
+
101
+ it "allows empty each_template and else_template" do
102
+ allow(presenter).to receive(:allows_method?).with(:non_empty_collection) { true }
103
+ allow(presenter).to receive(:non_empty_collection) { [presenter] }
104
+
105
+ template = Curlybars.compile(<<-HBS)
106
+ {{#each non_empty_collection}}{{else}}{{/each}}
107
+ HBS
108
+
109
+ expect(eval(template)).to resemble("")
110
+ end
111
+
112
+ it "renders nothing if the context is nil" do
113
+ template = Curlybars.compile(<<-HBS)
114
+ {{#each return_nil}}
115
+ each_template
116
+ {{else}}
117
+ else_template
118
+ {{/each}}
119
+ HBS
120
+
121
+ expect(eval(template)).to resemble(<<-HTML)
122
+ else_template
123
+ HTML
124
+ end
125
+
126
+ it "raises an error if the context is not an array-like object" do
127
+ allow(IntegrationTest::Presenter).to receive(:allows_method?).with(:not_a_collection) { true }
128
+ allow(presenter).to receive(:not_a_collection) { "string" }
129
+
130
+ template = Curlybars.compile(<<-HBS)
131
+ {{#each not_a_collection}}{{else}}{{/each}}
132
+ HBS
133
+
134
+ expect do
135
+ eval(template)
136
+ end.to raise_error(Curlybars::Error::Render)
137
+ end
138
+
139
+ it "raises an error if the objects inside of the context array are not presenters" do
140
+ allow(IntegrationTest::Presenter).to receive(:allows_method?).with(:not_a_presenter_collection) { true }
141
+ allow(presenter).to receive(:not_a_presenter_collection) { [:an_element] }
142
+
143
+ template = Curlybars.compile(<<-HBS)
144
+ {{#each not_a_presenter_collection}}{{else}}{{/each}}
145
+ HBS
146
+
147
+ expect do
148
+ eval(template)
149
+ end.to raise_error(Curlybars::Error::Render)
150
+ end
151
+ end
152
+
153
+ describe "#validate" do
154
+ let(:presenter_class) { double(:presenter_class) }
155
+
156
+ it "without errors" do
157
+ dependency_tree = { a_presenter_collection: [{}] }
158
+
159
+ source = <<-HBS
160
+ {{#each a_presenter_collection}} {{else}} {{/each}}
161
+ HBS
162
+
163
+ errors = Curlybars.validate(dependency_tree, source)
164
+
165
+ expect(errors).to be_empty
166
+ end
167
+
168
+ it "with errors due to a presenter path" do
169
+ dependency_tree = { a_presenter: {} }
170
+
171
+ source = <<-HBS
172
+ {{#each a_presenter}} {{else}} {{/each}}
173
+ HBS
174
+
175
+ errors = Curlybars.validate(dependency_tree, source)
176
+
177
+ expect(errors).not_to be_empty
178
+ end
179
+
180
+ it "with errors due to a leaf path" do
181
+ dependency_tree = { a_leaf: nil }
182
+
183
+ source = <<-HBS
184
+ {{#each a_leaf}} {{else}} {{/each}}
185
+ HBS
186
+
187
+ errors = Curlybars.validate(dependency_tree, source)
188
+
189
+ expect(errors).not_to be_empty
190
+ end
191
+
192
+ it "with errors due unallowed method" do
193
+ dependency_tree = {}
194
+
195
+ source = <<-HBS
196
+ {{#each unallowed}} {{else}} {{/each}}
197
+ HBS
198
+
199
+ errors = Curlybars.validate(dependency_tree, source)
200
+
201
+ expect(errors).not_to be_empty
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,291 @@
1
+ describe "{{#each collection}}...{{/each}}" do
2
+ let(:global_helpers_providers) { [] }
3
+
4
+ describe "#compile" do
5
+ let(:post) { double("post") }
6
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
7
+
8
+ it "uses each_template when collection is not empty" do
9
+ allow(presenter).to receive(:allows_method?).with(:non_empty_collection) { true }
10
+ allow(presenter).to receive(:non_empty_collection) { [presenter] }
11
+
12
+ template = Curlybars.compile(<<-HBS)
13
+ {{#each non_empty_collection}}
14
+ each_template
15
+ {{/each}}
16
+ HBS
17
+
18
+ expect(eval(template)).to resemble(<<-HTML)
19
+ each_template
20
+ HTML
21
+ end
22
+
23
+ it "doesn't use each_template when collection is empty" do
24
+ allow(presenter).to receive(:allows_method?).with(:empty_collection) { true }
25
+ allow(presenter).to receive(:empty_collection) { [] }
26
+
27
+ template = Curlybars.compile(<<-HBS)
28
+ {{#each empty_collection}}
29
+ each_template
30
+ {{/each}}
31
+ HBS
32
+
33
+ expect(eval(template)).to resemble("")
34
+ end
35
+
36
+ it "allows empty each_template" do
37
+ allow(presenter).to receive(:allows_method?).with(:non_empty_collection) { true }
38
+ allow(presenter).to receive(:non_empty_collection) { [presenter] }
39
+
40
+ template = Curlybars.compile(<<-HBS)
41
+ {{#each non_empty_collection}}{{/each}}
42
+ HBS
43
+
44
+ expect(eval(template)).to resemble("")
45
+ end
46
+
47
+ it "uses each_template when collection is a not empty enumerable" do
48
+ path_presenter_class = Class.new(Curlybars::Presenter) do
49
+ presents :path
50
+ allow_methods :path
51
+ def path
52
+ @path
53
+ end
54
+ end
55
+
56
+ a_path_presenter = path_presenter_class.new(nil, path: 'a_path')
57
+ another_path_presenter = path_presenter_class.new(nil, path: 'another_path')
58
+
59
+ allow(presenter).to receive(:allows_method?).with(:non_empty_collection) { true }
60
+ allow(presenter).to receive(:non_empty_collection) { [a_path_presenter, another_path_presenter] }
61
+
62
+ template = Curlybars.compile(<<-HBS)
63
+ {{#each non_empty_collection}}
64
+ {{path}}
65
+ {{/each}}
66
+ HBS
67
+
68
+ expect(eval(template)).to resemble(<<-HTML)
69
+ a_path
70
+ another_path
71
+ HTML
72
+ end
73
+
74
+ it "uses each_template when collection is a not empty hash" do
75
+ path_presenter_class = Class.new(Curlybars::Presenter) do
76
+ presents :path
77
+ allow_methods :path
78
+ def path
79
+ @path
80
+ end
81
+ end
82
+
83
+ a_path_presenter = path_presenter_class.new(nil, path: 'a_path')
84
+ another_path_presenter = path_presenter_class.new(nil, path: 'another_path')
85
+
86
+ allow(presenter).to receive(:allows_method?).with(:non_empty_hash) { true }
87
+ allow(presenter).to receive(:non_empty_hash) do
88
+ { first: a_path_presenter, second: another_path_presenter }
89
+ end
90
+
91
+ template = Curlybars.compile(<<-HBS)
92
+ {{#each non_empty_hash}}
93
+ {{path}}
94
+ {{/each}}
95
+ HBS
96
+
97
+ expect(eval(template)).to resemble(<<-HTML)
98
+ a_path
99
+ another_path
100
+ HTML
101
+ end
102
+
103
+ it "raises an error if the context is not an array-like object" do
104
+ allow(presenter).to receive(:allows_method?).with(:not_a_collection) { true }
105
+ allow(presenter).to receive(:not_a_collection) { "string" }
106
+
107
+ template = Curlybars.compile(<<-HBS)
108
+ {{#each not_a_collection}}{{/each}}
109
+ HBS
110
+
111
+ expect do
112
+ eval(template)
113
+ end.to raise_error(Curlybars::Error::Render)
114
+ end
115
+
116
+ it "renders nothing if the context is nil" do
117
+ template = Curlybars.compile(<<-HBS)
118
+ {{#each return_nil}}
119
+ text
120
+ {{/each}}
121
+ HBS
122
+
123
+ expect(eval(template)).to resemble("")
124
+ end
125
+
126
+ it "gives access to `@index` variable" do
127
+ template = Curlybars.compile(<<-HBS)
128
+ {{#each two_elements}}
129
+ {{@index}}
130
+ {{/each}}
131
+ HBS
132
+
133
+ expect(eval(template)).to resemble(<<-HTML)
134
+ 0
135
+ 1
136
+ HTML
137
+ end
138
+
139
+ it "gives access to `@first` variable" do
140
+ template = Curlybars.compile(<<-HBS)
141
+ {{#each two_elements}}
142
+ {{@first}}
143
+ {{/each}}
144
+ HBS
145
+
146
+ expect(eval(template)).to resemble(<<-HTML)
147
+ true
148
+ false
149
+ HTML
150
+ end
151
+
152
+ it "gives access to `@last` variable" do
153
+ template = Curlybars.compile(<<-HBS)
154
+ {{#each two_elements}}
155
+ {{@last}}
156
+ {{/each}}
157
+ HBS
158
+
159
+ expect(eval(template)).to resemble(<<-HTML)
160
+ false
161
+ true
162
+ HTML
163
+ end
164
+
165
+ it "gives access to variables from nested {{#with}}" do
166
+ template = Curlybars.compile(<<-HBS)
167
+ {{#each two_elements}}
168
+ {{#with me}}
169
+ {{@index}}
170
+ {{/with}}
171
+ {{/each}}
172
+ HBS
173
+
174
+ expect(eval(template)).to resemble(<<-HTML)
175
+ 0
176
+ 1
177
+ HTML
178
+ end
179
+
180
+ it "gives access to variables from nested {{#each}}" do
181
+ template = Curlybars.compile(<<-HBS)
182
+ {{#each two_elements}}
183
+ {{#each ../two_elements}}
184
+ {{@../index}}
185
+ {{/each}}
186
+ {{/each}}
187
+ HBS
188
+
189
+ expect(eval(template)).to resemble(<<-HTML)
190
+ 0
191
+ 0
192
+ 1
193
+ 1
194
+ HTML
195
+ end
196
+
197
+ it "gives access to variables when collection is a not empty hash" do
198
+ path_presenter_class = Class.new(Curlybars::Presenter) do
199
+ presents :path
200
+ allow_methods :path
201
+ def path
202
+ @path
203
+ end
204
+ end
205
+
206
+ a_path_presenter = path_presenter_class.new(nil, path: 'a_path')
207
+ another_path_presenter = path_presenter_class.new(nil, path: 'another_path')
208
+
209
+ allow(presenter).to receive(:allows_method?).with(:non_empty_hash) { true }
210
+ allow(presenter).to receive(:non_empty_hash) do
211
+ { first: a_path_presenter, second: another_path_presenter }
212
+ end
213
+
214
+ template = Curlybars.compile(<<-HBS)
215
+ {{#each non_empty_hash}}
216
+ {{@index}}) {{@key}}: {{path}}
217
+ {{/each}}
218
+ HBS
219
+
220
+ expect(eval(template)).to resemble(<<-HTML)
221
+ 0) first: a_path
222
+ 1) second: another_path
223
+ HTML
224
+ end
225
+
226
+ it "raises an error if the objects inside of the context array are not presenters" do
227
+ allow(presenter).to receive(:allows_method?).with(:not_a_presenter_collection) { true }
228
+ allow(presenter).to receive(:not_a_presenter_collection) { [:an_element] }
229
+
230
+ template = Curlybars.compile(<<-HBS)
231
+ {{#each not_a_presenter_collection}}{{/each}}
232
+ HBS
233
+
234
+ expect do
235
+ eval(template)
236
+ end.to raise_error(Curlybars::Error::Render)
237
+ end
238
+ end
239
+
240
+ describe "#validate" do
241
+ let(:presenter_class) { double(:presenter_class) }
242
+
243
+ it "without errors" do
244
+ dependency_tree = { a_presenter_collection: [{}] }
245
+
246
+ source = <<-HBS
247
+ {{#each a_presenter_collection}}{{/each}}
248
+ HBS
249
+
250
+ errors = Curlybars.validate(dependency_tree, source)
251
+
252
+ expect(errors).to be_empty
253
+ end
254
+
255
+ it "with errors due to a presenter path" do
256
+ dependency_tree = { a_presenter: {} }
257
+
258
+ source = <<-HBS
259
+ {{#each a_presenter}}{{/each}}
260
+ HBS
261
+
262
+ errors = Curlybars.validate(dependency_tree, source)
263
+
264
+ expect(errors).not_to be_empty
265
+ end
266
+
267
+ it "with errors due to a leaf path" do
268
+ dependency_tree = { a_leaf: nil }
269
+
270
+ source = <<-HBS
271
+ {{#each a_leaf}}{{/each}}
272
+ HBS
273
+
274
+ errors = Curlybars.validate(dependency_tree, source)
275
+
276
+ expect(errors).not_to be_empty
277
+ end
278
+
279
+ it "with errors due unallowed method" do
280
+ dependency_tree = {}
281
+
282
+ source = <<-HBS
283
+ {{#each unallowed}}{{/each}}
284
+ HBS
285
+
286
+ errors = Curlybars.validate(dependency_tree, source)
287
+
288
+ expect(errors).not_to be_empty
289
+ end
290
+ end
291
+ end