curlybars 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2be6d03409f5c575fb33c7ba4ed74117420fd22ddca8272005c5f23e459a0d8e
4
- data.tar.gz: ad11b01849c9d28a70b2ae2260615665cc2dc3ca3c380b9dfbc2ab9c91bf1de3
3
+ metadata.gz: ba4b2799fad2886edfd0bc1c4873d8da9ed80d8885a1868f4b0fa5797fb9e49c
4
+ data.tar.gz: ee1bfc5d500f2ceee63675c5188d915227f507de7895c6ed2f08e89ddcae122e
5
5
  SHA512:
6
- metadata.gz: ba6035a72fd5cf75120cc3adfc663192dae68d75132887b801bd29a53c4b655f6be09ed6b8e4a2ee497e708172c38db19258215b5f3b1a9b788371db20eeac3c
7
- data.tar.gz: 83961d2f0ef03848c1ffb3feb9db859c866ff90e74d173f116b16c3af5ff4eac710e934cd7b3bb5039749f05169bd4f95e3f19fc208163f3333a6d66ba7261df
6
+ metadata.gz: 4233c4cd5ed833892d17603776a6db2abae2061cf72dfebe1add84bb7464e99b563cacee499fbcd447f59dd54a1ea3bf3b4aa18010526249aba2ed39a2310b96
7
+ data.tar.gz: f78754a1adc25de66302116338dd2abf9b4e9f8c2ae3439d1f74beda9065bb634c8fae64c19f65767be4d7389f1362559c0537f36bb438969e151bb93a47612f
data/lib/curlybars.rb CHANGED
@@ -77,6 +77,13 @@ module Curlybars
77
77
  visitor.accept(tree)
78
78
  end
79
79
 
80
+ def global_helpers_dependency_tree
81
+ @global_helpers_dependency_tree ||= begin
82
+ classes = Curlybars.configuration.global_helpers_provider_classes
83
+ classes.map(&:dependency_tree).inject({}, :merge)
84
+ end
85
+ end
86
+
80
87
  def cache
81
88
  @cache ||= ActiveSupport::Cache::MemoryStore.new
82
89
  end
@@ -117,6 +124,7 @@ require 'curlybars/configuration'
117
124
  require 'curlybars/rendering_support'
118
125
  require 'curlybars/parser'
119
126
  require 'curlybars/position'
127
+ require 'curlybars/generic'
120
128
  require 'curlybars/lexer'
121
129
  require 'curlybars/processor/token_factory'
122
130
  require 'curlybars/processor/tilde'
@@ -0,0 +1,36 @@
1
+ require 'curlybars/method_whitelist'
2
+
3
+ module Curlybars
4
+ # A base class that can be used to signify that a helper's return type is a sort a generic.
5
+ #
6
+ # Examples
7
+ #
8
+ # class GlobalHelperProvider
9
+ # extend Curlybars::MethodWhitelist
10
+ #
11
+ # allow_methods slice: [:helper, [Curlybars::Generic]],
12
+ # translate: [:helper, Curlybars::Generic]
13
+ #
14
+ # def slice(collection, start, length, _)
15
+ # collection[start, length]
16
+ # end
17
+ #
18
+ # def translate(object, locale)
19
+ # object.translate(locale)
20
+ # end
21
+ # end
22
+ #
23
+ # {{#each (slice articles, 0, 5)}}
24
+ # Title: {{title}}
25
+ # Body: {{body}}
26
+ # {{/each}}
27
+ #
28
+ # {{#with (translate article "en-us")}}
29
+ # Title: {{title}}
30
+ # Body: {{body}}
31
+ # {{/with}}
32
+ #
33
+ class Generic
34
+ extend Curlybars::MethodWhitelist
35
+ end
36
+ end
@@ -1,10 +1,11 @@
1
1
  module Curlybars
2
2
  module MethodWhitelist
3
- # rubocop:disable Style/SoleNestedConditional
4
3
  def allow_methods(*methods_without_type, **methods_with_type, &contextual_block)
5
4
  methods_with_type_validator = lambda do |methods_to_validate|
6
5
  methods_to_validate.each do |(method_name, type)|
7
6
  if type.is_a?(Array)
7
+ next if generic_or_collection_helper?(type)
8
+
8
9
  if type.size != 1 || !type.first.respond_to?(:dependency_tree)
9
10
  raise "Invalid allowed method syntax for `#{method_name}`. Collections must be of one presenter class"
10
11
  end
@@ -81,7 +82,15 @@ module Curlybars
81
82
  memo[method_name] = if type.respond_to?(:dependency_tree)
82
83
  type.dependency_tree(context)
83
84
  elsif type.is_a?(Array)
84
- [type.first.dependency_tree(context)]
85
+ if type.first == :helper
86
+ if type.last.is_a?(Array)
87
+ [:helper, [type.last.first.dependency_tree(context)]]
88
+ else
89
+ [:helper, type.last.dependency_tree(context)]
90
+ end
91
+ else
92
+ [type.first.dependency_tree(context)]
93
+ end
85
94
  else
86
95
  type
87
96
  end
@@ -92,11 +101,21 @@ module Curlybars
92
101
  allowed_methods.include?(method)
93
102
  end
94
103
  end
95
- # rubocop:enable Style/SoleNestedConditional
96
104
 
97
105
  def self.extended(base)
98
106
  # define a default of no method allowed
99
107
  base.allow_methods
100
108
  end
109
+
110
+ private
111
+
112
+ def generic_or_collection_helper?(type)
113
+ return false unless type.size == 2
114
+ return false unless type.first == :helper
115
+ return true if type.last.respond_to?(:dependency_tree)
116
+ return false unless type.last.is_a?(Array) && type.last.size == 1
117
+
118
+ type.last.first.respond_to?(:dependency_tree)
119
+ end
101
120
  end
102
121
  end
@@ -11,7 +11,7 @@ module Curlybars
11
11
  position = rendering.position(#{position.line_number}, #{position.line_offset})
12
12
  template_cache_key = '#{each_template.cache_key}'
13
13
 
14
- collection = rendering.coerce_to_hash!(collection, #{path.path.inspect}, position)
14
+ collection = rendering.coerce_to_hash!(collection, #{collection_path.path.inspect}, position)
15
15
  collection.each.with_index.map do |key_and_presenter, index|
16
16
  rendering.check_timeout!
17
17
  rendering.optional_presenter_cache(key_and_presenter[1], template_cache_key, buffer) do |buffer|
@@ -37,7 +37,7 @@ module Curlybars
37
37
  end
38
38
 
39
39
  def validate(branches)
40
- resolved = path.resolve_and_check!(branches, check_type: :presenter_collection)
40
+ resolved = path.resolve_and_check!(branches, check_type: :collectionlike)
41
41
  sub_tree = resolved.first
42
42
 
43
43
  each_template_errors = begin
@@ -57,6 +57,10 @@ module Curlybars
57
57
  path_error
58
58
  end
59
59
 
60
+ def collection_path
61
+ path.subexpression? ? path.helper : path
62
+ end
63
+
60
64
  def cache_key
61
65
  [
62
66
  path,
@@ -35,11 +35,18 @@ module Curlybars
35
35
  resolve(branches).is_a?(Hash)
36
36
  end
37
37
 
38
- def presenter_collection?(branches)
39
- value = resolve(branches)
38
+ def presenter_value?(value)
39
+ value.is_a?(Hash)
40
+ end
41
+
42
+ def collection_value?(value)
40
43
  value.is_a?(Array) && value.first.is_a?(Hash)
41
44
  end
42
45
 
46
+ def presenter_collection?(branches)
47
+ collection_value?(resolve(branches))
48
+ end
49
+
43
50
  def leaf?(branches)
44
51
  value = resolve(branches)
45
52
  value.nil?
@@ -53,9 +60,41 @@ module Curlybars
53
60
  resolve(branches) == :helper
54
61
  end
55
62
 
63
+ def generic_helper?(branches)
64
+ value = resolve(branches)
65
+ value.is_a?(Array) &&
66
+ value.first == :helper &&
67
+ presenter_value?(value.last)
68
+ end
69
+
70
+ def generic_collection_helper?(branches)
71
+ value = resolve(branches)
72
+ value.is_a?(Array) &&
73
+ value.first == :helper &&
74
+ collection_value?(value.last)
75
+ end
76
+
77
+ def collectionlike?(branches)
78
+ presenter_collection?(branches) || generic_collection_helper?(branches)
79
+ end
80
+
81
+ def presenterlike?(branches)
82
+ presenter?(branches) || generic_helper?(branches)
83
+ end
84
+
85
+ def subexpression?
86
+ false
87
+ end
88
+
56
89
  def resolve(branches)
57
90
  @value ||= begin
58
- return :helper if global_helpers_dependency_tree.key?(path.to_sym)
91
+ if Curlybars.global_helpers_dependency_tree.key?(path.to_sym)
92
+ dep_node = Curlybars.global_helpers_dependency_tree[path.to_sym]
93
+
94
+ return :helper if dep_node.nil?
95
+
96
+ return [:helper, dep_node]
97
+ end
59
98
 
60
99
  path_split_by_slashes = path.split('/')
61
100
  backward_steps_on_branches = path_split_by_slashes.count - 1
@@ -97,14 +136,6 @@ module Curlybars
97
136
 
98
137
  private
99
138
 
100
- # TODO: extract me away
101
- def global_helpers_dependency_tree
102
- @global_helpers_dependency_tree ||= begin
103
- classes = Curlybars.configuration.global_helpers_provider_classes
104
- classes.map(&:dependency_tree).inject({}, :merge)
105
- end
106
- end
107
-
108
139
  def check_type_of(branches, check_type)
109
140
  case check_type
110
141
  when :presenter
@@ -112,6 +143,16 @@ module Curlybars
112
143
 
113
144
  message = "`#{path}` must resolve to a presenter"
114
145
  raise Curlybars::Error::Validate.new('not_a_presenter', message, position)
146
+ when :presenterlike
147
+ return if presenterlike?(branches)
148
+
149
+ message = "`#{path}` must resolve to a presenter"
150
+ raise Curlybars::Error::Validate.new('not_presenterlike', message, position)
151
+ when :collectionlike
152
+ return if collectionlike?(branches)
153
+
154
+ message = "`#{path}` must resolve to a collection of presenters"
155
+ raise Curlybars::Error::Validate.new('not_collectionlike', message, position)
115
156
  when :presenter_collection
116
157
  return if presenter_collection?(branches)
117
158
 
@@ -1,6 +1,10 @@
1
1
  module Curlybars
2
2
  module Node
3
3
  SubExpression = Struct.new(:helper, :arguments, :options, :position) do
4
+ def subexpression?
5
+ true
6
+ end
7
+
4
8
  def compile
5
9
  compiled_arguments = arguments.map do |argument|
6
10
  "arguments.push(rendering.cached_call(#{argument.compile}))"
@@ -50,6 +54,48 @@ module Curlybars
50
54
  ]
51
55
  end
52
56
 
57
+ def resolve_and_check!(branches, check_type: :collectionlike)
58
+ node = if arguments.first.is_a?(Curlybars::Node::SubExpression)
59
+ arguments.first
60
+ else
61
+ helper
62
+ end
63
+
64
+ type = node.resolve_and_check!(branches, check_type: check_type)
65
+
66
+ if helper?(type)
67
+ if generic_helper?(type)
68
+ is_collection = type.last.is_a?(Array)
69
+ return infer_generic_helper_type!(branches, is_collection: is_collection)
70
+ end
71
+
72
+ return type.last
73
+ end
74
+
75
+ type
76
+ end
77
+
78
+ def generic_helper?(type)
79
+ return false unless type.is_a?(Array)
80
+ return false unless type.size == 2
81
+ return false unless type.first == :helper
82
+
83
+ type.last == [{}] || type.last == {}
84
+ end
85
+
86
+ def helper?(type)
87
+ type.first == :helper
88
+ end
89
+
90
+ def infer_generic_helper_type!(branches, is_collection:)
91
+ if arguments.empty?
92
+ raise Curlybars::Error::Validate.new('missing_path', "'#{helper.path}' requires a collection as its first argument", helper.position)
93
+ end
94
+
95
+ check_type = is_collection ? :presenter_collection : :presenter
96
+ arguments.first.resolve_and_check!(branches, check_type: check_type)
97
+ end
98
+
53
99
  def cache_key
54
100
  [
55
101
  helper,
@@ -9,7 +9,7 @@ module Curlybars
9
9
 
10
10
  if rendering.to_bool(compiled_path)
11
11
  position = rendering.position(#{position.line_number}, #{position.line_offset})
12
- rendering.check_context_is_presenter(compiled_path, #{path.path.inspect}, position)
12
+ rendering.check_context_is_presenter(compiled_path, #{presenter_path.path.inspect}, position)
13
13
 
14
14
  contexts.push(compiled_path)
15
15
  begin
@@ -24,7 +24,7 @@ module Curlybars
24
24
  end
25
25
 
26
26
  def validate(branches)
27
- sub_tree = path.resolve_and_check!(branches, check_type: :presenter)
27
+ sub_tree = path.resolve_and_check!(branches, check_type: :presenterlike)
28
28
  with_template_errors = begin
29
29
  branches.push(sub_tree)
30
30
  with_template.validate(branches)
@@ -42,6 +42,10 @@ module Curlybars
42
42
  path_error
43
43
  end
44
44
 
45
+ def presenter_path
46
+ path.subexpression? ? path.helper : path
47
+ end
48
+
45
49
  def cache_key
46
50
  [
47
51
  path,
@@ -112,6 +112,12 @@ module Curlybars
112
112
  Node::EachElse.new(path, each_template || VOID, VOID, pos(0))
113
113
  end
114
114
 
115
+ clause('START HASH EACH .subexpression END
116
+ .template?
117
+ START SLASH EACH END') do |subexpression, each_template|
118
+ Node::EachElse.new(subexpression, each_template || VOID, VOID, pos(0))
119
+ end
120
+
115
121
  clause('START HASH EACH .path END
116
122
  .template?
117
123
  START ELSE END
@@ -120,12 +126,26 @@ module Curlybars
120
126
  Node::EachElse.new(path, each_template || VOID, else_template || VOID, pos(0))
121
127
  end
122
128
 
129
+ clause('START HASH EACH .subexpression END
130
+ .template?
131
+ START ELSE END
132
+ .template?
133
+ START SLASH EACH END') do |subexpression, each_template, else_template|
134
+ Node::EachElse.new(subexpression, each_template || VOID, else_template || VOID, pos(0))
135
+ end
136
+
123
137
  clause('START HASH WITH .path END
124
138
  .template?
125
139
  START SLASH WITH END') do |path, with_template|
126
140
  Node::WithElse.new(path, with_template || VOID, VOID, pos(0))
127
141
  end
128
142
 
143
+ clause('START HASH WITH .subexpression END
144
+ .template?
145
+ START SLASH WITH END') do |subexpression, with_template|
146
+ Node::WithElse.new(subexpression, with_template || VOID, VOID, pos(0))
147
+ end
148
+
129
149
  clause('START HASH WITH .path END
130
150
  .template?
131
151
  START ELSE END
@@ -134,6 +154,14 @@ module Curlybars
134
154
  Node::WithElse.new(path, with_template || VOID, else_template || VOID, pos(0))
135
155
  end
136
156
 
157
+ clause('START HASH WITH .subexpression END
158
+ .template?
159
+ START ELSE END
160
+ .template?
161
+ START SLASH WITH END') do |subexpression, with_template, else_template|
162
+ Node::WithElse.new(subexpression, with_template || VOID, else_template || VOID, pos(0))
163
+ end
164
+
137
165
  clause('START GT .path END') do |path|
138
166
  Node::Partial.new(path)
139
167
  end
@@ -1,3 +1,3 @@
1
1
  module Curlybars
2
- VERSION = '1.6.0'.freeze
2
+ VERSION = '1.7.0'.freeze
3
3
  end
@@ -36,16 +36,20 @@ describe Curlybars::MethodWhitelist do
36
36
 
37
37
  describe ".allow_methods" do
38
38
  before do
39
- link_presenter = Class.new
40
- article_presenter = Class.new
39
+ link_presenter = Class.new { extend Curlybars::MethodWhitelist }
40
+ article_presenter = Class.new { extend Curlybars::MethodWhitelist }
41
41
 
42
42
  dummy_class.class_eval do
43
- allow_methods :cook, link: link_presenter, article: article_presenter
43
+ allow_methods :cook, link: link_presenter, article: article_presenter,
44
+ translate_article: [:helper, article_presenter],
45
+ reverse_articles: [:helper, [article_presenter]],
46
+ translate: [:helper, Curlybars::Generic],
47
+ slice: [:helper, [Curlybars::Generic]]
44
48
  end
45
49
  end
46
50
 
47
51
  it "sets the allowed methods" do
48
- expect(dummy_class.new.allowed_methods).to eq([:cook, :link, :article])
52
+ expect(dummy_class.new.allowed_methods).to eq([:cook, :link, :article, :translate_article, :reverse_articles, :translate, :slice])
49
53
  end
50
54
 
51
55
  it "supports adding more methods for validation" do
@@ -1,9 +1,10 @@
1
1
  describe "{{#each collection}}...{{else}}...{{/each}}" do
2
- let(:global_helpers_providers) { [] }
2
+ let(:global_helpers_providers) { [IntegrationTest::GlobalHelperProvider.new] }
3
3
 
4
4
  describe "#compile" do
5
5
  let(:post) { double("post") }
6
- let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
6
+ let(:presenter_class) { IntegrationTest::Presenter }
7
+ let(:presenter) { presenter_class.new(double("view_context"), post: post) }
7
8
 
8
9
  it "uses each_template when collection is not empty" do
9
10
  allow(presenter).to receive(:allows_method?).with(:non_empty_collection).and_return(true)
@@ -123,6 +124,41 @@ describe "{{#each collection}}...{{else}}...{{/each}}" do
123
124
  HTML
124
125
  end
125
126
 
127
+ it "allows subexpressions" do
128
+ template = Curlybars.compile(<<-HBS)
129
+ {{#each (articles)}}left{{else}}right{{/each}}
130
+ HBS
131
+
132
+ expected = "left" * presenter.articles.size
133
+ expect(eval(template)).to resemble(expected)
134
+ end
135
+
136
+ it "allows subexpressions with collection helpers" do
137
+ template = Curlybars.compile(<<-HBS)
138
+ {{#each (reverse_articles)}}
139
+ {{title}}
140
+ {{else}}
141
+ right
142
+ {{/each}}
143
+ HBS
144
+
145
+ expected = presenter.reverse_articles.inject("") { |res, a| res + a.title }
146
+ expect(eval(template)).to resemble(expected)
147
+ end
148
+
149
+ it "allows subexpressions with generic collection helpers" do
150
+ template = Curlybars.compile(<<-HBS)
151
+ {{#each (refl articles)}}
152
+ {{title}}
153
+ {{else}}
154
+ right
155
+ {{/each}}
156
+ HBS
157
+
158
+ expected = presenter.articles.inject("") { |res, a| res + a.title }
159
+ expect(eval(template)).to resemble(expected)
160
+ end
161
+
126
162
  it "raises an error if the context is not an array-like object" do
127
163
  allow(IntegrationTest::Presenter).to receive(:allows_method?).with(:not_a_collection).and_return(true)
128
164
  allow(presenter).to receive(:not_a_collection).and_return("string")
@@ -198,5 +234,175 @@ describe "{{#each collection}}...{{else}}...{{/each}}" do
198
234
 
199
235
  expect(errors).not_to be_empty
200
236
  end
237
+
238
+ describe "with subexpressions" do
239
+ before do
240
+ Curlybars.instance_variable_set(:@global_helpers_dependency_tree, nil)
241
+ end
242
+
243
+ it "without errors when called with a presenter collection" do
244
+ dependency_tree = { a_presenter_collection: [{}] }
245
+
246
+ source = <<-HBS
247
+ {{#each (a_presenter_collection)}} {{else}} {{/each}}
248
+ HBS
249
+
250
+ errors = Curlybars.validate(dependency_tree, source)
251
+
252
+ expect(errors).to be_empty
253
+ end
254
+
255
+ it "without errors when called with a collection helper" do
256
+ dependency_tree = { a_collection_helper: [:helper, [{ url: nil }]] }
257
+
258
+ source = <<-HBS
259
+ {{#each (a_collection_helper)}}
260
+ {{url}}
261
+ {{else}}
262
+ right
263
+ {{/each}}
264
+ HBS
265
+
266
+ errors = Curlybars.validate(dependency_tree, source)
267
+
268
+ expect(errors).to be_empty
269
+ end
270
+
271
+ context "with a generic collection helper" do
272
+ it "without errors when arguments are valid" do
273
+ dependency_tree = {
274
+ refl: [:helper, [{}]],
275
+ articles: [{ url: nil }]
276
+ }
277
+
278
+ source = <<-HBS
279
+ {{#each (refl articles)}}
280
+ {{url}}
281
+ {{else}}
282
+ right
283
+ {{/each}}
284
+ HBS
285
+
286
+ errors = Curlybars.validate(dependency_tree, source)
287
+
288
+ expect(errors).to be_empty
289
+ end
290
+
291
+ it "with errors when the first argument is not a collection" do
292
+ dependency_tree = {
293
+ refl: [:helper, [{}]],
294
+ articles: { url: nil }
295
+ }
296
+
297
+ source = <<-HBS
298
+ {{#each (refl articles)}}
299
+ {{url}}
300
+ {{else}}
301
+ right
302
+ {{/each}}
303
+ HBS
304
+
305
+ errors = Curlybars.validate(dependency_tree, source)
306
+
307
+ expect(errors).not_to be_empty
308
+ end
309
+
310
+ it "with errors when the first argument is not defined" do
311
+ dependency_tree = {
312
+ refl: [:helper, [{}]]
313
+ }
314
+
315
+ source = <<-HBS
316
+ {{#each (refl articles)}}
317
+ {{url}}
318
+ {{else}}
319
+ right
320
+ {{/each}}
321
+ HBS
322
+
323
+ errors = Curlybars.validate(dependency_tree, source)
324
+
325
+ expect(errors).not_to be_empty
326
+ end
327
+
328
+ it "with errors when no argument is given" do
329
+ dependency_tree = {
330
+ refl: [:helper, [{}]]
331
+ }
332
+
333
+ source = <<-HBS
334
+ {{#each (refl)}}
335
+ {{url}}
336
+ {{else}}
337
+ right
338
+ {{/each}}
339
+ HBS
340
+
341
+ errors = Curlybars.validate(dependency_tree, source)
342
+
343
+ expect(errors).not_to be_empty
344
+ end
345
+
346
+ describe "as a global helper" do
347
+ let(:global_helpers_provider_classes) { [IntegrationTest::GlobalHelperProvider] }
348
+
349
+ before do
350
+ allow(Curlybars.configuration).to receive(:global_helpers_provider_classes).and_return(global_helpers_provider_classes)
351
+ end
352
+
353
+ it "without errors when the first argument is a collection" do
354
+ dependency_tree = {
355
+ articles: [{ url: nil }]
356
+ }
357
+
358
+ source = <<-HBS
359
+ {{#each (slice articles 0 4)}}
360
+ {{url}}
361
+ {{else}}
362
+ right
363
+ {{/each}}
364
+ HBS
365
+
366
+ errors = Curlybars.validate(dependency_tree, source)
367
+
368
+ expect(errors).to be_empty
369
+ end
370
+
371
+ it "with errors when the first argument is not a collection" do
372
+ dependency_tree = {
373
+ articles: { url: nil }
374
+ }
375
+
376
+ source = <<-HBS
377
+ {{#each (slice articles 0 4)}}
378
+ {{url}}
379
+ {{else}}
380
+ right
381
+ {{/each}}
382
+ HBS
383
+
384
+ errors = Curlybars.validate(dependency_tree, source)
385
+
386
+ expect(errors).not_to be_empty
387
+ end
388
+
389
+ it "with errors when no argument is given" do
390
+ dependency_tree = {}
391
+
392
+ source = <<-HBS
393
+ {{#each (slice)}}
394
+ {{url}}
395
+ {{else}}
396
+ right
397
+ {{/each}}
398
+ HBS
399
+
400
+ errors = Curlybars.validate(dependency_tree, source)
401
+
402
+ expect(errors).not_to be_empty
403
+ end
404
+ end
405
+ end
406
+ end
201
407
  end
202
408
  end
@@ -1,5 +1,5 @@
1
1
  describe "{{#with presenter}}...{{/with}}" do
2
- let(:global_helpers_providers) { [] }
2
+ let(:global_helpers_providers) { [IntegrationTest::GlobalHelperProvider.new] }
3
3
 
4
4
  describe "#compile" do
5
5
  let(:post) { double("post") }
@@ -31,6 +31,18 @@ describe "{{#with presenter}}...{{/with}}" do
31
31
  HTML
32
32
  end
33
33
 
34
+ it "allows subexpressions" do
35
+ template = Curlybars.compile(<<-HBS)
36
+ {{#with (translate user "sk-SK")}}
37
+ {{avatar.url}}
38
+ {{/with}}
39
+ HBS
40
+
41
+ expect(eval(template)).to resemble(<<-HTML)
42
+ http://example.com/foo.png?locale=sk-SK
43
+ HTML
44
+ end
45
+
34
46
  it "allows empty with_template" do
35
47
  template = Curlybars.compile(<<-HBS)
36
48
  {{#with user}}{{/with}}
@@ -39,7 +51,7 @@ describe "{{#with presenter}}...{{/with}}" do
39
51
  expect(eval(template)).to resemble("")
40
52
  end
41
53
 
42
- it "renders the else templte if the context is nil" do
54
+ it "renders the else template if the context is nil" do
43
55
  template = Curlybars.compile(<<-HBS)
44
56
  {{#with return_nil}}
45
57
  text
@@ -87,6 +99,34 @@ describe "{{#with presenter}}...{{/with}}" do
87
99
  expect(errors).to be_empty
88
100
  end
89
101
 
102
+ it "without errors when a presenter helper is used" do
103
+ dependency_tree = { translate_article: [:helper, { title: nil }] }
104
+
105
+ source = <<-HBS
106
+ {{#with (translate_article 12345 "en-US")}}
107
+ {{title}}
108
+ {{/with}}
109
+ HBS
110
+
111
+ errors = Curlybars.validate(dependency_tree, source)
112
+
113
+ expect(errors).to be_empty
114
+ end
115
+
116
+ it "without errors when a generic presenter helper is used" do
117
+ dependency_tree = { article: { title: nil }, translate: [:helper, {}] }
118
+
119
+ source = <<-HBS
120
+ {{#with (translate article "en-US")}}
121
+ {{title}}
122
+ {{/with}}
123
+ HBS
124
+
125
+ errors = Curlybars.validate(dependency_tree, source)
126
+
127
+ expect(errors).to be_empty
128
+ end
129
+
90
130
  it "with errors due to a leaf" do
91
131
  dependency_tree = { not_a_presenter: nil }
92
132
 
@@ -110,5 +150,29 @@ describe "{{#with presenter}}...{{/with}}" do
110
150
 
111
151
  expect(errors).not_to be_empty
112
152
  end
153
+
154
+ it "with errors due collection helpers" do
155
+ dependency_tree = { reverse_articles: [:helper, [{ title: nil }]] }
156
+
157
+ source = <<-HBS
158
+ {{#with (reverse_articles)}}{{/with}}
159
+ HBS
160
+
161
+ errors = Curlybars.validate(dependency_tree, source)
162
+
163
+ expect(errors).not_to be_empty
164
+ end
165
+
166
+ it "with errors due generic collection helpers" do
167
+ dependency_tree = { articles: [{ title: nil }], slice: [:helper, [{}]] }
168
+
169
+ source = <<-HBS
170
+ {{#with (slice articles 0 4)}}{{/with}}
171
+ HBS
172
+
173
+ errors = Curlybars.validate(dependency_tree, source)
174
+
175
+ expect(errors).not_to be_empty
176
+ end
113
177
  end
114
178
  end
@@ -30,7 +30,7 @@ describe "tilde operator" do
30
30
  it "runs even when 'run_processors' flag is set to false" do
31
31
  allow(Curlybars::Processor::Tilde).to receive(:process!)
32
32
 
33
- Curlybars.validate(presenter, "source", run_processors: false)
33
+ Curlybars.validate(presenter.dependency_tree, "source", run_processors: false)
34
34
 
35
35
  expect(Curlybars::Processor::Tilde).to have_received(:process!)
36
36
  end
@@ -9,19 +9,19 @@ describe "processors" do
9
9
 
10
10
  describe "validation" do
11
11
  it "are run by default" do
12
- Curlybars.validate(presenter, "source")
12
+ Curlybars.validate(presenter.dependency_tree, "source")
13
13
 
14
14
  expect(processor).to have_received(:process!)
15
15
  end
16
16
 
17
- it "are not run when run_processors is true" do
18
- Curlybars.validate(presenter, "source", run_processors: true)
17
+ it "are run when run_processors is true" do
18
+ Curlybars.validate(presenter.dependency_tree, "source", run_processors: true)
19
19
 
20
20
  expect(processor).to have_received(:process!)
21
21
  end
22
22
 
23
23
  it "are not run when run_processors is false" do
24
- Curlybars.validate(presenter, "source", run_processors: false)
24
+ Curlybars.validate(presenter.dependency_tree, "source", run_processors: false)
25
25
 
26
26
  expect(processor).not_to have_received(:process!)
27
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curlybars
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Libo Cannici
@@ -11,10 +11,11 @@ authors:
11
11
  - Luís Almeida
12
12
  - Andreas Garnæs
13
13
  - Augusto Silva
14
+ - Attila Večerek
14
15
  autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
- date: 2021-01-05 00:00:00.000000000 Z
18
+ date: 2021-02-17 00:00:00.000000000 Z
18
19
  dependencies:
19
20
  - !ruby/object:Gem::Dependency
20
21
  name: actionpack
@@ -234,6 +235,7 @@ files:
234
235
  - lib/curlybars/error/presenter/not_found.rb
235
236
  - lib/curlybars/error/render.rb
236
237
  - lib/curlybars/error/validate.rb
238
+ - lib/curlybars/generic.rb
237
239
  - lib/curlybars/lexer.rb
238
240
  - lib/curlybars/method_whitelist.rb
239
241
  - lib/curlybars/node/block_helper_else.rb