curlybars 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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