curlybars 0.9.13

Sign up to get free protection for your applications and to get access to all the features.
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,168 @@
1
+ describe Curlybars::MethodWhitelist do
2
+ let(:dummy_class) { Class.new { extend Curlybars::MethodWhitelist } }
3
+
4
+ describe "#allowed_methods" do
5
+ it "returns an empty array as default" do
6
+ expect(dummy_class.new.allowed_methods).to eq([])
7
+ end
8
+ end
9
+
10
+ describe ".allow_methods" do
11
+ before do
12
+ link_presenter = Class.new
13
+ article_presenter = Class.new
14
+
15
+ dummy_class.class_eval do
16
+ allow_methods :cook, link: link_presenter, article: article_presenter
17
+ end
18
+ end
19
+
20
+ it "sets the allowed methods" do
21
+ expect(dummy_class.new.allowed_methods).to eq([:cook, :link, :article])
22
+ end
23
+
24
+ it "raises when collection is not of presenters" do
25
+ expect do
26
+ dummy_class.class_eval { allow_methods :cook, links: ["foobar"] }
27
+ end.to raise_error(RuntimeError)
28
+ end
29
+
30
+ it "raises when collection cardinality is greater than one" do
31
+ stub_const("OnePresenter", Class.new { extend Curlybars::MethodWhitelist })
32
+ stub_const("OtherPresenter", Class.new { extend Curlybars::MethodWhitelist })
33
+
34
+ expect do
35
+ dummy_class.class_eval { allow_methods :cook, links: [OnePresenter, OtherPresenter] }
36
+ end.to raise_error(RuntimeError)
37
+ end
38
+ end
39
+
40
+ describe "inheritance and composition" do
41
+ let(:base_presenter) do
42
+ stub_const("LinkPresenter", Class.new)
43
+
44
+ Class.new do
45
+ extend Curlybars::MethodWhitelist
46
+ allow_methods :cook, link: LinkPresenter
47
+ end
48
+ end
49
+
50
+ let(:helpers) do
51
+ Module.new do
52
+ extend Curlybars::MethodWhitelist
53
+ allow_methods :form
54
+ end
55
+ end
56
+
57
+ let(:post_presenter) do
58
+ Class.new(base_presenter) do
59
+ extend Curlybars::MethodWhitelist
60
+ include Helpers
61
+ allow_methods :wave
62
+ end
63
+ end
64
+
65
+ before do
66
+ stub_const("Helpers", helpers)
67
+ end
68
+
69
+ it "allows methods from inheritance and composition" do
70
+ expect(post_presenter.new.allowed_methods).to eq([:cook, :link, :form, :wave])
71
+ end
72
+
73
+ it "returns a dependency_tree with inheritance and composition" do
74
+ expect(post_presenter.dependency_tree).
75
+ to eq(
76
+ cook: nil,
77
+ link: LinkPresenter,
78
+ form: nil,
79
+ wave: nil
80
+ )
81
+ end
82
+ end
83
+
84
+ describe ".methods_schema" do
85
+ it "setups a schema propagating nil" do
86
+ stub_const("LinkPresenter", Class.new { extend Curlybars::MethodWhitelist })
87
+ dummy_class.class_eval { allow_methods :cook }
88
+
89
+ expect(dummy_class.methods_schema).to eq(cook: nil)
90
+ end
91
+
92
+ it "setups a schema any random type" do
93
+ stub_const("LinkPresenter", Class.new { extend Curlybars::MethodWhitelist })
94
+ dummy_class.class_eval { allow_methods something: :foobar }
95
+
96
+ expect(dummy_class.methods_schema).to eq(something: :foobar)
97
+ end
98
+
99
+ it "setups a schema propagating the return type of the method" do
100
+ stub_const("ArticlePresenter", Class.new { extend Curlybars::MethodWhitelist })
101
+ dummy_class.class_eval { allow_methods article: ArticlePresenter }
102
+
103
+ expect(dummy_class.methods_schema).to eq(article: ArticlePresenter)
104
+ end
105
+
106
+ it "setups a schema propagating a collection" do
107
+ stub_const("LinkPresenter", Class.new { extend Curlybars::MethodWhitelist })
108
+ dummy_class.class_eval { allow_methods links: [LinkPresenter] }
109
+
110
+ expect(dummy_class.methods_schema).to eq(links: [LinkPresenter])
111
+ end
112
+
113
+ it "supports procs in schema" do
114
+ dummy_class.class_eval { allow_methods settings: ->() { { color_1: nil } } }
115
+
116
+ expect(dummy_class.methods_schema).to eq(settings: { color_1: nil })
117
+ end
118
+
119
+ it "supports procs with arguments in schema" do
120
+ dummy_class.class_eval { allow_methods settings: ->(name) { Hash[name, nil] } }
121
+
122
+ expect(dummy_class.methods_schema(:background_color)).to eq(settings: { background_color: nil })
123
+ end
124
+ end
125
+
126
+ describe ".dependency_tree" do
127
+ it "returns a dependencies tree" do
128
+ link_presenter = Class.new do
129
+ extend Curlybars::MethodWhitelist
130
+ allow_methods :url
131
+ end
132
+
133
+ article_presenter = Class.new do
134
+ extend Curlybars::MethodWhitelist
135
+ allow_methods :title, :body
136
+ end
137
+
138
+ stub_const("ArticlePresenter", article_presenter)
139
+ stub_const("LinkPresenter", link_presenter)
140
+
141
+ dummy_class.class_eval do
142
+ allow_methods links: [LinkPresenter], article: ArticlePresenter
143
+ end
144
+
145
+ expect(dummy_class.dependency_tree).
146
+ to eq(
147
+ links: [{ url: nil }],
148
+ article: {
149
+ title: nil,
150
+ body: nil
151
+ }
152
+ )
153
+ end
154
+
155
+ it "propagates arguments" do
156
+ dummy_class.class_eval do
157
+ allow_methods label: ->(label) { Hash[label, nil] }
158
+ end
159
+
160
+ expect(dummy_class.dependency_tree(:some_label)).
161
+ to eq(
162
+ label: {
163
+ some_label: nil
164
+ }
165
+ )
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,60 @@
1
+ describe Curlybars::Processor::Tilde do
2
+ let(:tilde_start_token) { double(:tilde_start, type: :TILDE_START, value: nil, position: nil) }
3
+ let(:tilde_end_token) { double(:tilde_end, type: :TILDE_END, value: nil, position: nil) }
4
+ let(:start_token) { double(:start, type: :START, value: nil, position: nil) }
5
+ let(:end_token) { double(:end, type: :END, value: nil, position: nil) }
6
+
7
+ describe ":TILDE_START" do
8
+ it "trims the previous text token" do
9
+ tokens = [
10
+ text_token("text \t\r\n"),
11
+ tilde_start_token
12
+ ]
13
+ Curlybars::Processor::Tilde.process!(tokens, 'identifier')
14
+
15
+ expect(tokens.first.value).to eq 'text'
16
+ end
17
+
18
+ it "doesn't trim the previous text token when not right before" do
19
+ tokens = [
20
+ text_token("text \t\r\n"),
21
+ start_token,
22
+ end_token,
23
+ tilde_start_token
24
+ ]
25
+ Curlybars::Processor::Tilde.process!(tokens, 'identifier')
26
+
27
+ expect(tokens.first.value).to eq "text \t\r\n"
28
+ end
29
+ end
30
+
31
+ describe ":TILDE_END" do
32
+ it "trims the following text token" do
33
+ tokens = [
34
+ tilde_end_token,
35
+ text_token("\t\r\n text")
36
+ ]
37
+ Curlybars::Processor::Tilde.process!(tokens, 'identifier')
38
+
39
+ expect(tokens.last.value).to eq 'text'
40
+ end
41
+
42
+ it "doesn't trim the following text token when not right after" do
43
+ tokens = [
44
+ tilde_end_token,
45
+ start_token,
46
+ end_token,
47
+ text_token("\t\r\n text")
48
+ ]
49
+ Curlybars::Processor::Tilde.process!(tokens, 'identifier')
50
+
51
+ expect(tokens.last.value).to eq "\t\r\n text"
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def text_token(value)
58
+ double(:text, type: :TEXT, value: value, position: nil)
59
+ end
60
+ end
@@ -0,0 +1,426 @@
1
+ describe Curlybars::RenderingSupport do
2
+ let(:file_name) { '/app/views/template.hbs' }
3
+ let(:presenter) { double(:presenter, allows_method?: true, meth: :value) }
4
+ let(:contexts) { [presenter] }
5
+ let(:variables) { [{}] }
6
+ let(:rendering) { Curlybars::RenderingSupport.new(1.second, contexts, variables, file_name) }
7
+ let(:position) do
8
+ double(:position, file_name: 'template.hbs', line_number: 1, line_offset: 0)
9
+ end
10
+ let(:block) { -> {} }
11
+
12
+ describe "#check_timeout!" do
13
+ it "skips checking if timeout is nil" do
14
+ rendering = Curlybars::RenderingSupport.new(nil, contexts, variables, file_name)
15
+
16
+ sleep 0.1.seconds
17
+ expect { rendering.check_timeout! }.not_to raise_error
18
+ end
19
+
20
+ it "doesn't happen when rendering is < rendering_timeout" do
21
+ rendering = Curlybars::RenderingSupport.new(10.seconds, contexts, variables, file_name)
22
+ expect { rendering.check_timeout! }.not_to raise_error
23
+ end
24
+
25
+ it "happens and raises when rendering >= rendering_timeout" do
26
+ rendering = Curlybars::RenderingSupport.new(0.01.seconds, contexts, variables, file_name)
27
+
28
+ sleep 0.1.seconds
29
+ expect { rendering.check_timeout! }.to raise_error(Curlybars::Error::Render)
30
+ end
31
+ end
32
+
33
+ describe "#to_bool" do
34
+ describe "returns true" do
35
+ it "with `true`" do
36
+ expect(rendering.to_bool(true)).to be_truthy
37
+ end
38
+
39
+ it "with `[:non_empty]`" do
40
+ expect(rendering.to_bool([:non_empty])).to be_truthy
41
+ end
42
+
43
+ it "with `1`" do
44
+ expect(rendering.to_bool(1)).to be_truthy
45
+ end
46
+ end
47
+
48
+ describe "returns false" do
49
+ it "with `false`" do
50
+ expect(rendering.to_bool(false)).to be_falsey
51
+ end
52
+
53
+ it "with `[]`" do
54
+ expect(rendering.to_bool([])).to be_falsey
55
+ end
56
+
57
+ it "with `{}`" do
58
+ expect(rendering.to_bool({})).to be_falsey
59
+ end
60
+
61
+ it "with `0`" do
62
+ expect(rendering.to_bool(0)).to be_falsey
63
+ end
64
+
65
+ it "with `nil`" do
66
+ expect(rendering.to_bool(nil)).to be_falsey
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#path" do
72
+ it "returns the method in the current context" do
73
+ allow_all_methods(presenter)
74
+ allow(presenter).to receive(:method) { :method }
75
+
76
+ expect(rendering.path('method', rendering.position(0, 1))).to eq :method
77
+ end
78
+
79
+ it "returns the method in the current context" do
80
+ sub = double(:sub_presenter)
81
+ allow_all_methods(sub)
82
+ allow(sub).to receive(:method) { :method }
83
+
84
+ allow_all_methods(presenter)
85
+ allow(presenter).to receive(:sub) { sub }
86
+
87
+ expect(rendering.path('sub.method', rendering.position(0, 1))).to eq :method
88
+ end
89
+
90
+ it "returns the length of a collection, when `lenght` is the last step" do
91
+ allow_all_methods(presenter)
92
+ single_element_presenter = double(:single_element_presenter)
93
+ allow_all_methods(single_element_presenter)
94
+ collection = [single_element_presenter]
95
+ allow(presenter).to receive(:collection) { collection }
96
+
97
+ returned_method = rendering.path('collection.length', rendering.position(0, 1))
98
+ expect(returned_method.call).to eq collection.length
99
+ end
100
+
101
+ it "raises an exception when the method is not allowed" do
102
+ disallow_all_methods(presenter)
103
+ allow(presenter).to receive(:forbidden_method) { :forbidden_method }
104
+
105
+ expect do
106
+ rendering.path('forbidden_method', rendering.position(0, 1))
107
+ end.to raise_error(Curlybars::Error::Render)
108
+ end
109
+
110
+ it "exposes the unallowed method in the exception payload" do
111
+ disallow_all_methods(presenter)
112
+ allow(presenter).to receive(:forbidden_method) { :forbidden_method }
113
+
114
+ begin
115
+ rendering.path('forbidden_method', rendering.position(0, 1))
116
+ rescue Curlybars::Error::Render => e
117
+ expect(e.metadata).to eq(meth: :forbidden_method)
118
+ end
119
+ end
120
+
121
+ it "raises an exception when the context is not a presenter" do
122
+ sub = double(:not_presenter)
123
+ allow(presenter).to receive(:sub) { sub }
124
+
125
+ expect do
126
+ rendering.path('sub.method', rendering.position(0, 1))
127
+ end.to raise_error(Curlybars::Error::Render)
128
+ end
129
+
130
+ it "refers to the second to last presenter in the stack when using `../`" do
131
+ sub = double(:sub_presenter)
132
+ allow_all_methods(sub)
133
+ allow(sub).to receive(:method) { :sub_method }
134
+
135
+ allow_all_methods(presenter)
136
+ allow(presenter).to receive(:method) { :root_method }
137
+
138
+ contexts.push(sub)
139
+
140
+ expect(rendering.path('../method', rendering.position(0, 1))).to eq :root_method
141
+ end
142
+
143
+ it "refers to the third to last presenter in the stack when using `../../`" do
144
+ sub_sub = double(:sub_presenter)
145
+ allow_all_methods(sub_sub)
146
+ allow(sub_sub).to receive(:method) { :sub_sub_method }
147
+
148
+ sub = double(:sub_presenter)
149
+ allow_all_methods(sub)
150
+ allow(sub).to receive(:method) { :sub_method }
151
+
152
+ allow_all_methods(presenter)
153
+ allow(presenter).to receive(:method) { :root_method }
154
+
155
+ contexts.push(sub)
156
+ contexts.push(sub_sub)
157
+
158
+ expect(rendering.path('../../method', rendering.position(0, 1))).to eq :root_method
159
+ end
160
+
161
+ it "returns a method that returns nil, if nil is returned from any method in the chain (except the latter)" do
162
+ allow_all_methods(presenter)
163
+ allow(presenter).to receive(:returns_nil) { nil }
164
+
165
+ outcome = rendering.path('returns_nil.another_method', rendering.position(0, 1)).call
166
+ expect(outcome).to be_nil
167
+ end
168
+
169
+ it "returns a method that returns nil, if `../`` goes too deep in the stack" do
170
+ outcome = rendering.path('../too_deep', rendering.position(0, 1)).call
171
+ expect(outcome).to be_nil
172
+ end
173
+
174
+ it "raises an exception if tha path is too deep (> 10)" do
175
+ expect do
176
+ rendering.path('a.b.c.d.e.f.g.h.i.l', rendering.position(0, 1))
177
+ end.to raise_error(Curlybars::Error::Render)
178
+ end
179
+ end
180
+
181
+ describe "#cached_call" do
182
+ before do
183
+ class APresenter
184
+ def meth
185
+ :value
186
+ end
187
+ end
188
+ end
189
+
190
+ it "(cache miss) calls the method if not cached already" do
191
+ meth = presenter.method(:meth)
192
+ allow(meth).to receive(:call)
193
+
194
+ rendering.cached_call(meth)
195
+
196
+ expect(meth).to have_received(:call).once
197
+ end
198
+
199
+ it "(cache hit) avoids to call a method for more than one time" do
200
+ meth = presenter.method(:meth)
201
+ allow(meth).to receive(:call)
202
+
203
+ rendering.cached_call(meth)
204
+ rendering.cached_call(meth)
205
+
206
+ expect(meth).to have_received(:call).once
207
+ end
208
+
209
+ it "the returned cached value is the same as the uncached one" do
210
+ meth = presenter.method(:meth)
211
+
212
+ first_outcome = rendering.cached_call(meth)
213
+ second_outcome = rendering.cached_call(meth)
214
+
215
+ expect(second_outcome).to eq first_outcome
216
+ end
217
+ end
218
+
219
+ describe "#call" do
220
+ it "calls with no arguments a method with no parameters" do
221
+ method = ->() { :return }
222
+ arguments = []
223
+
224
+ output = rendering.call(method, "method", position, arguments, :options, &block)
225
+ expect(output).to eq :return
226
+ end
227
+
228
+ it "calls with one argument a method with no parameters, discarding the parameter" do
229
+ method = ->() { :return }
230
+ arguments = [:argument]
231
+
232
+ output = rendering.call(method, "method", position, arguments, :options, &block)
233
+ expect(output).to eq :return
234
+ end
235
+
236
+ it "calls a method with only one parameter can only receive the options" do
237
+ method = ->(parameter) { parameter }
238
+ arguments = []
239
+
240
+ output = rendering.call(method, "method", position, arguments, :options, &block)
241
+ expect(output).to eq :options
242
+ end
243
+
244
+ it "calls a method with only one parameter can only receive the options, even with some arguments" do
245
+ method = ->(parameter) { parameter }
246
+ arguments = [:argument]
247
+
248
+ output = rendering.call(method, "method", position, arguments, :options, &block)
249
+ expect(output).to eq :options
250
+ end
251
+
252
+ it "calls a method with two parameter can receive nil as first argument and the options" do
253
+ method = ->(parameter, options) { [parameter, options] }
254
+ arguments = []
255
+
256
+ output = rendering.call(method, "method", position, arguments, :options, &block)
257
+ expect(output).to eq [nil, :options]
258
+ end
259
+
260
+ it "calls a method with two parameter can receive an argument and the options" do
261
+ method = ->(parameter, options) { [parameter, options] }
262
+ arguments = [:argument]
263
+
264
+ output = rendering.call(method, "method", position, arguments, :options, &block)
265
+ expect(output).to eq [:argument, :options]
266
+ end
267
+
268
+ it "calls a method with three parameter can receive two arguments and the options" do
269
+ method = ->(first, second, options) { [first, second, options] }
270
+ arguments = [:first, :second]
271
+
272
+ output = rendering.call(method, "method", position, arguments, :options, &block)
273
+ expect(output).to eq [:first, :second, :options]
274
+ end
275
+
276
+ it "calls a method with three parameter can receive one argument, nil and the options" do
277
+ method = ->(first, second, options) { [first, second, options] }
278
+ arguments = [:first]
279
+
280
+ output = rendering.call(method, "method", position, arguments, :options, &block)
281
+ expect(output).to eq [:first, nil, :options]
282
+ end
283
+
284
+ it "calls a method with three parameter can receive nil, nil and the options" do
285
+ method = ->(first, second, options) { [first, second, options] }
286
+ arguments = []
287
+
288
+ output = rendering.call(method, "method", position, arguments, :options, &block)
289
+ expect(output).to eq [nil, nil, :options]
290
+ end
291
+
292
+ it "calls a method passing an array as argument" do
293
+ method = ->(parameter, _) { parameter }
294
+ array = [1, 2, 3]
295
+ arguments = [array]
296
+
297
+ output = rendering.call(method, "method", position, arguments, :options, &block)
298
+ expect(output).to eq arguments.first
299
+ end
300
+
301
+ it "raises Curlybars::Error::Render if the helper has at least an optional parameter" do
302
+ method = ->(one, two = :optional) {}
303
+ arguments = [:arg1]
304
+ options = { key: :value }
305
+
306
+ expect do
307
+ rendering.call(method, "method", position, arguments, options, &block)
308
+ end.to raise_error(Curlybars::Error::Render)
309
+ end
310
+
311
+ it "raises Curlybars::Error::Render if the helper has at least a keyword parameter" do
312
+ method = ->(keyword:) {}
313
+ arguments = [:arg1]
314
+ options = { key: :value }
315
+
316
+ expect do
317
+ rendering.call(method, "method", position, arguments, options, &block)
318
+ end.to raise_error(Curlybars::Error::Render)
319
+ end
320
+
321
+ it "raises Curlybars::Error::Render if the helper has at least an optional keyword parameter" do
322
+ method = ->(keyword: :optional) {}
323
+ arguments = [:arg1]
324
+ options = { key: :value }
325
+
326
+ expect do
327
+ rendering.call(method, "meth", position, arguments, options, &block)
328
+ end.to raise_error(Curlybars::Error::Render)
329
+ end
330
+ end
331
+
332
+ describe "#position" do
333
+ it "returns a position with file_name" do
334
+ position = rendering.position(0, 0)
335
+ expect(position.file_name).to eq file_name
336
+ end
337
+
338
+ it "returns a position with line_number" do
339
+ position = rendering.position(1, 0)
340
+ expect(position.line_number).to eq 1
341
+ end
342
+
343
+ it "returns a position with line_offset" do
344
+ position = rendering.position(0, 1)
345
+ expect(position.line_offset).to eq 1
346
+ end
347
+ end
348
+
349
+ describe "#check_context_is_hash_or_enum_of_presenters" do
350
+ it "doesn't raise an exception when argument is an empty enumerable" do
351
+ collection = []
352
+ rendering.check_context_is_hash_or_enum_of_presenters(collection, 'path', position)
353
+ end
354
+
355
+ it "doesn't raise an exception when argument is an empty hash" do
356
+ collection = {}
357
+ rendering.check_context_is_hash_or_enum_of_presenters(collection, nil, position)
358
+ end
359
+
360
+ it "doesn't raise an exception when argument is an enumerable of presenters" do
361
+ collection = [presenter]
362
+ rendering.check_context_is_hash_or_enum_of_presenters(collection, 'path', position)
363
+ end
364
+
365
+ it "doesn't raise an exception when argument is a hash of presenters" do
366
+ collection = { presenter: presenter }
367
+ rendering.check_context_is_hash_or_enum_of_presenters(collection, 'path', position)
368
+ end
369
+
370
+ it "raises when it is not an hash or an enumerable" do
371
+ expect do
372
+ rendering.check_context_is_hash_or_enum_of_presenters(:not_a_presenter, 'path', position)
373
+ end.to raise_error(Curlybars::Error::Render)
374
+ end
375
+
376
+ it "raises when it is not an hash or an enumerable of presenters" do
377
+ expect do
378
+ rendering.check_context_is_hash_or_enum_of_presenters([:not_a_presenter], 'path', position)
379
+ end.to raise_error(Curlybars::Error::Render)
380
+ end
381
+ end
382
+
383
+ describe "#check_context_is_presenter" do
384
+ it "doesn't raise an exception when argument is a presenter" do
385
+ rendering.check_context_is_presenter(presenter, 'path', position)
386
+ end
387
+
388
+ it "raises when it is not a presenter" do
389
+ expect do
390
+ rendering.check_context_is_presenter(:not_a_presenter, 'path', position)
391
+ end.to raise_error(Curlybars::Error::Render)
392
+ end
393
+ end
394
+
395
+ describe "#coerce_to_hash!" do
396
+ let(:a_presenter) { double(:a_presenter, allows_method?: true, meth: :value) }
397
+ let(:another_presenter) { double(:another_presenter, allows_method?: true, meth: :value) }
398
+
399
+ it "leaves hashes intacted" do
400
+ hash = { first: a_presenter }
401
+ expect(rendering.coerce_to_hash!(hash, 'path', position)).to be hash
402
+ end
403
+
404
+ it "transform an Array to a Hash" do
405
+ array = [a_presenter, another_presenter]
406
+ expected_hash = { 0 => a_presenter, 1 => another_presenter }
407
+ expect(rendering.coerce_to_hash!(array, 'path', position)).to eq expected_hash
408
+ end
409
+
410
+ it "raises when it is not a hash or an enumerable" do
411
+ expect do
412
+ rendering.coerce_to_hash!(:not_a_presenter, 'path', position)
413
+ end.to raise_error(Curlybars::Error::Render)
414
+ end
415
+ end
416
+
417
+ private
418
+
419
+ def allow_all_methods(presenter)
420
+ allow(presenter).to receive(:allows_method?) { true }
421
+ end
422
+
423
+ def disallow_all_methods(presenter)
424
+ allow(presenter).to receive(:allows_method?) { false }
425
+ end
426
+ end