curlybars 1.3.1 → 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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/curlybars.rb +8 -1
  3. data/lib/curlybars/configuration.rb +1 -9
  4. data/lib/curlybars/error/base.rb +2 -0
  5. data/lib/curlybars/generic.rb +36 -0
  6. data/lib/curlybars/lexer.rb +3 -0
  7. data/lib/curlybars/method_whitelist.rb +25 -3
  8. data/lib/curlybars/node/block_helper_else.rb +1 -0
  9. data/lib/curlybars/node/each_else.rb +6 -2
  10. data/lib/curlybars/node/if_else.rb +1 -1
  11. data/lib/curlybars/node/path.rb +58 -11
  12. data/lib/curlybars/node/sub_expression.rb +108 -0
  13. data/lib/curlybars/node/unless_else.rb +1 -1
  14. data/lib/curlybars/node/with_else.rb +6 -2
  15. data/lib/curlybars/parser.rb +39 -0
  16. data/lib/curlybars/processor/tilde.rb +3 -0
  17. data/lib/curlybars/rendering_support.rb +9 -1
  18. data/lib/curlybars/template_handler.rb +18 -6
  19. data/lib/curlybars/version.rb +1 -1
  20. data/lib/curlybars/visitor.rb +6 -0
  21. data/spec/acceptance/application_layout_spec.rb +2 -2
  22. data/spec/acceptance/collection_blocks_spec.rb +1 -1
  23. data/spec/acceptance/global_helper_spec.rb +1 -1
  24. data/spec/curlybars/lexer_spec.rb +25 -2
  25. data/spec/curlybars/method_whitelist_spec.rb +8 -4
  26. data/spec/curlybars/rendering_support_spec.rb +4 -9
  27. data/spec/curlybars/template_handler_spec.rb +33 -30
  28. data/spec/integration/cache_spec.rb +20 -18
  29. data/spec/integration/node/block_helper_else_spec.rb +0 -2
  30. data/spec/integration/node/each_else_spec.rb +208 -4
  31. data/spec/integration/node/each_spec.rb +0 -2
  32. data/spec/integration/node/helper_spec.rb +12 -2
  33. data/spec/integration/node/if_else_spec.rb +0 -2
  34. data/spec/integration/node/if_spec.rb +2 -4
  35. data/spec/integration/node/output_spec.rb +0 -2
  36. data/spec/integration/node/partial_spec.rb +0 -2
  37. data/spec/integration/node/path_spec.rb +0 -2
  38. data/spec/integration/node/root_spec.rb +0 -2
  39. data/spec/integration/node/sub_expression_spec.rb +426 -0
  40. data/spec/integration/node/template_spec.rb +0 -2
  41. data/spec/integration/node/unless_else_spec.rb +2 -4
  42. data/spec/integration/node/unless_spec.rb +0 -2
  43. data/spec/integration/node/with_spec.rb +66 -4
  44. data/spec/integration/processor/tilde_spec.rb +1 -1
  45. data/spec/integration/processors_spec.rb +4 -5
  46. data/spec/integration/visitor_spec.rb +13 -5
  47. metadata +49 -15
@@ -15,7 +15,7 @@ module Curlybars
15
15
 
16
16
  def validate(branches)
17
17
  [
18
- expression.validate(branches, check_type: :not_helper),
18
+ expression.validate(branches),
19
19
  unless_template.validate(branches),
20
20
  else_template.validate(branches)
21
21
  ]
@@ -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,
@@ -15,6 +15,7 @@ require 'curlybars/node/block_helper_else'
15
15
  require 'curlybars/node/option'
16
16
  require 'curlybars/node/partial'
17
17
  require 'curlybars/node/output'
18
+ require 'curlybars/node/sub_expression'
18
19
 
19
20
  module Curlybars
20
21
  class Parser < RLTK::Parser
@@ -111,6 +112,12 @@ module Curlybars
111
112
  Node::EachElse.new(path, each_template || VOID, VOID, pos(0))
112
113
  end
113
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
+
114
121
  clause('START HASH EACH .path END
115
122
  .template?
116
123
  START ELSE END
@@ -119,12 +126,26 @@ module Curlybars
119
126
  Node::EachElse.new(path, each_template || VOID, else_template || VOID, pos(0))
120
127
  end
121
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
+
122
137
  clause('START HASH WITH .path END
123
138
  .template?
124
139
  START SLASH WITH END') do |path, with_template|
125
140
  Node::WithElse.new(path, with_template || VOID, VOID, pos(0))
126
141
  end
127
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
+
128
149
  clause('START HASH WITH .path END
129
150
  .template?
130
151
  START ELSE END
@@ -133,6 +154,14 @@ module Curlybars
133
154
  Node::WithElse.new(path, with_template || VOID, else_template || VOID, pos(0))
134
155
  end
135
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
+
136
165
  clause('START GT .path END') do |path|
137
166
  Node::Partial.new(path)
138
167
  end
@@ -155,6 +184,7 @@ module Curlybars
155
184
  production(:expression) do
156
185
  clause('value') { |value| value }
157
186
  clause('path') { |path| path }
187
+ clause('subexpression') { |subexpression| subexpression }
158
188
  end
159
189
 
160
190
  production(:value) do
@@ -164,6 +194,15 @@ module Curlybars
164
194
 
165
195
  production(:path, 'PATH') { |path| Node::Path.new(path, pos(0)) }
166
196
 
197
+ production(:subexpression, 'LPAREN .path .expressions? .options? RPAREN') do |helper, arguments, options|
198
+ Node::SubExpression.new(
199
+ helper,
200
+ arguments || [],
201
+ options || [],
202
+ pos(0)
203
+ )
204
+ end
205
+
167
206
  finalize
168
207
 
169
208
  VOID = Class.new do
@@ -10,10 +10,12 @@ module Curlybars
10
10
  when :TILDE_START
11
11
  tokens[index] = create_token(:START, token.value, token.position)
12
12
  next if index == 0
13
+
13
14
  strip_token_if_text(tokens, index - 1, :rstrip)
14
15
  when :TILDE_END
15
16
  tokens[index] = create_token(:END, token.value, token.position)
16
17
  next if index == (tokens.length - 1)
18
+
17
19
  strip_token_if_text(tokens, index + 1, :lstrip)
18
20
  end
19
21
  end
@@ -22,6 +24,7 @@ module Curlybars
22
24
  def strip_token_if_text(tokens, index, strip_method)
23
25
  token = tokens[index]
24
26
  return if token.type != :TEXT
27
+
25
28
  stripped_value = token.value.public_send(strip_method)
26
29
  tokens[index] = create_token(token.type, stripped_value, token.position)
27
30
  end
@@ -23,12 +23,14 @@ module Curlybars
23
23
  def check_timeout!
24
24
  return unless timeout.present?
25
25
  return unless (Time.now - start_time) > timeout
26
+
26
27
  message = "Rendering took too long (> #{timeout} seconds)"
27
28
  raise ::Curlybars::Error::Render.new('timeout', message, nil)
28
29
  end
29
30
 
30
31
  def check_context_is_presenter(context, path, position)
31
32
  return if presenter?(context)
33
+
32
34
  message = "`#{path}` is not a context type object"
33
35
  raise Curlybars::Error::Render.new('context_is_not_a_presenter', message, position)
34
36
  end
@@ -83,9 +85,11 @@ module Curlybars
83
85
  resolved = chain.inject(base_context) do |context, meth|
84
86
  next context if meth == 'this'
85
87
  next context.count if meth == 'length' && presenter_collection?(context)
88
+
86
89
  raise_if_not_traversable(context, meth, position)
87
90
  outcome = instrument(context.method(meth)) { context.public_send(meth) }
88
91
  return -> {} if outcome.nil?
92
+
89
93
  outcome
90
94
  end
91
95
 
@@ -101,7 +105,8 @@ module Curlybars
101
105
 
102
106
  def cached_call(meth)
103
107
  return cached_calls[meth] if cached_calls.key? meth
104
- instrument(meth) { cached_calls[meth] = meth.call }
108
+
109
+ instrument(meth) { cached_calls[meth] = meth.call(*arguments_for_signature(meth, [], {})) }
105
110
  end
106
111
 
107
112
  def call(helper, helper_path, helper_position, arguments, options, &block)
@@ -202,6 +207,7 @@ module Curlybars
202
207
 
203
208
  def check_context_allows_method(context, meth, position)
204
209
  return if context.allows_method?(meth.to_sym)
210
+
205
211
  message = "`#{meth}` is not available - "
206
212
  message += "add `allow_methods :#{meth}` to #{context.class} to allow this path"
207
213
  raise Curlybars::Error::Render.new('unallowed_path', message, position, meth: meth.to_sym)
@@ -209,12 +215,14 @@ module Curlybars
209
215
 
210
216
  def check_context_has_method(context, meth, position)
211
217
  return if context.respond_to?(meth.to_sym)
218
+
212
219
  message = "`#{meth}` is not available in #{context.class}"
213
220
  raise Curlybars::Error::Render.new('unallowed_path', message, position)
214
221
  end
215
222
 
216
223
  def check_traverse_not_too_deep(traverse, position)
217
224
  return unless traverse.count('.') > Curlybars.configuration.traversing_limit
225
+
218
226
  message = "`#{traverse}` too deep"
219
227
  raise Curlybars::Error::Render.new('traverse_too_deep', message, position)
220
228
  end
@@ -13,9 +13,17 @@ module Curlybars
13
13
  # template - The ActionView::Template template that should be compiled.
14
14
  #
15
15
  # Returns a String containing the Ruby code representing the template.
16
- def call(template)
17
- instrument(template) do
18
- compile(template)
16
+ if ActionView::VERSION::MAJOR < 6
17
+ def call(template)
18
+ instrument(template) do
19
+ compile_for_actionview5(template)
20
+ end
21
+ end
22
+ else
23
+ def call(template, source)
24
+ instrument(template) do
25
+ compile(template, source)
26
+ end
19
27
  end
20
28
  end
21
29
 
@@ -43,9 +51,13 @@ module Curlybars
43
51
 
44
52
  private
45
53
 
46
- def compile(template)
54
+ def compile_for_actionview5(template)
55
+ compile(template, template.source)
56
+ end
57
+
58
+ def compile(template, source)
47
59
  # Template is empty, so there's no need to initialize a presenter.
48
- return %("") if template.source.empty?
60
+ return %("") if source.empty?
49
61
 
50
62
  path = template.virtual_path
51
63
  presenter_class = Curlybars::Presenter.presenter_for_path(path)
@@ -55,7 +67,7 @@ module Curlybars
55
67
  # For security reason, we strip the encoding directive in order to avoid
56
68
  # potential issues when rendering the template in another character
57
69
  # encoding.
58
- safe_source = template.source.gsub(/\A#{ActionView::ENCODING_FLAG}/, '')
70
+ safe_source = source.gsub(/\A#{ActionView::ENCODING_FLAG}/, '')
59
71
 
60
72
  source = Curlybars.compile(safe_source, template.identifier)
61
73
 
@@ -1,3 +1,3 @@
1
1
  module Curlybars
2
- VERSION = '1.3.1'.freeze
2
+ VERSION = '1.7.0'.freeze
3
3
  end
@@ -75,6 +75,12 @@ module Curlybars
75
75
  def visit_string(_node)
76
76
  end
77
77
 
78
+ def visit_sub_expression(node)
79
+ visit(node.helper)
80
+ node.arguments.each { |argument| visit(argument) }
81
+ node.options.each { |option| visit(option) }
82
+ end
83
+
78
84
  def visit_template(node)
79
85
  node.items.each { |item| visit(item) }
80
86
  end
@@ -2,7 +2,7 @@ describe "Using Curlybars for the application layout", type: :request do
2
2
  example "A simple layout view in Curlybars" do
3
3
  get '/'
4
4
 
5
- expect(body).to eq(<<-HTML.strip_heredoc)
5
+ expect(body).to eq(<<~HTML)
6
6
  <html>
7
7
  <head>
8
8
  <title>Dummy app</title>
@@ -31,7 +31,7 @@ describe "Using Curlybars for the application layout", type: :request do
31
31
  example "A simple layout view in Curlybars with html safe logic" do
32
32
  get '/articles/1'
33
33
 
34
- expect(body).to eq(<<-HTML.strip_heredoc)
34
+ expect(body).to eq(<<~HTML)
35
35
  <html>
36
36
  <head>
37
37
  <title>Dummy app</title>
@@ -8,7 +8,7 @@ describe "Collection blocks", type: :request do
8
8
  example "Rendering collections" do
9
9
  get '/categories'
10
10
 
11
- expect(body).to eq(<<-HTML.strip_heredoc)
11
+ expect(body).to eq(<<~HTML)
12
12
  <html>
13
13
  <head>
14
14
  <title>Dummy app</title>
@@ -9,7 +9,7 @@ describe "Collection blocks", type: :request do
9
9
  example "Render a global helper" do
10
10
  get '/welcome'
11
11
 
12
- expect(body).to eq(<<-HTML.strip_heredoc)
12
+ expect(body).to eq(<<~HTML)
13
13
  <html>
14
14
  <head>
15
15
  <title>Dummy app</title>
@@ -37,6 +37,7 @@ describe Curlybars::Lexer do
37
37
  it "is resilient to whitespaces" do
38
38
  expect(lex('{{! }}')).to produce []
39
39
  end
40
+
40
41
  it "is resilient to newlines" do
41
42
  expect(lex("{{!\n}}")).to produce []
42
43
  end
@@ -160,6 +161,28 @@ describe Curlybars::Lexer do
160
161
  end
161
162
  end
162
163
 
164
+ describe "{{path (path context options)}}" do
165
+ it "is lexed with path, context and options" do
166
+ expect(lex('{{path (path context key=value)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :PATH, :KEY, :PATH, :RPAREN, :END]
167
+ end
168
+
169
+ it "is lexed without options" do
170
+ expect(lex('{{path (path context)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :PATH, :RPAREN, :END]
171
+ end
172
+
173
+ it "is lexed without context" do
174
+ expect(lex('{{path (path key=value)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :KEY, :PATH, :RPAREN, :END]
175
+ end
176
+
177
+ it "is lexed without context and options" do
178
+ expect(lex('{{path (path)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :RPAREN, :END]
179
+ end
180
+
181
+ it "is lexed with a nested subexpression" do
182
+ expect(lex('{{path (path (path context key=value) key=value)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :LPAREN, :PATH, :PATH, :KEY, :PATH, :RPAREN, :KEY, :PATH, :RPAREN, :END]
183
+ end
184
+ end
185
+
163
186
  describe "{{#if path}}...{{/if}}" do
164
187
  it "is lexed" do
165
188
  expect(lex('{{#if path}} text {{/if}}')).to produce(
@@ -180,7 +203,7 @@ describe Curlybars::Lexer do
180
203
  end
181
204
  end
182
205
 
183
- # rubocop:disable Layout/AlignArray
206
+ # rubocop:disable Layout/ArrayAlignment
184
207
  describe "{{#if path}}...{{else}}...{{/if}}" do
185
208
  it "is lexed" do
186
209
  expect(lex('{{#if path}} text {{else}} text {{/if}}')).to produce(
@@ -298,7 +321,7 @@ describe Curlybars::Lexer do
298
321
  )
299
322
  end
300
323
  end
301
- # rubocop:enable Layout/AlignArray
324
+ # rubocop:enable Layout/ArrayAlignment
302
325
 
303
326
  describe "{{#with path}}...{{/with}}" do
304
327
  it "is lexed" do
@@ -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,3 +1,4 @@
1
+ # rubocop:disable RSpec/MultipleMemoizedHelpers
1
2
  describe Curlybars::RenderingSupport do
2
3
  let(:file_name) { '/app/views/template.hbs' }
3
4
  let(:presenter) { double(:presenter, allows_method?: true, meth: :value) }
@@ -7,7 +8,6 @@ describe Curlybars::RenderingSupport do
7
8
  let(:position) do
8
9
  double(:position, file_name: 'template.hbs', line_number: 1, line_offset: 0)
9
10
  end
10
- let(:block) { -> {} }
11
11
 
12
12
  describe "#check_timeout!" do
13
13
  it "skips checking if timeout is nil" do
@@ -179,14 +179,6 @@ describe Curlybars::RenderingSupport do
179
179
  end
180
180
 
181
181
  describe "#cached_call" do
182
- before do
183
- class APresenter
184
- def meth
185
- :value
186
- end
187
- end
188
- end
189
-
190
182
  it "(cache miss) calls the method if not cached already" do
191
183
  meth = presenter.method(:meth)
192
184
  allow(meth).to receive(:call)
@@ -217,6 +209,8 @@ describe Curlybars::RenderingSupport do
217
209
  end
218
210
 
219
211
  describe "#call" do
212
+ let(:block) { -> {} }
213
+
220
214
  it "calls with no arguments a method with no parameters" do
221
215
  method = -> { :return }
222
216
  arguments = []
@@ -424,3 +418,4 @@ describe Curlybars::RenderingSupport do
424
418
  allow(presenter).to receive(:allows_method?).and_return(false)
425
419
  end
426
420
  end
421
+ # rubocop:enable RSpec/MultipleMemoizedHelpers
@@ -100,13 +100,11 @@ describe Curlybars::TemplateHandler do
100
100
  end
101
101
 
102
102
  it "strips the `# encoding: *` directive away from the template" do
103
- allow(template).to receive(:source) do
104
- <<-TEMPLATE.strip_heredoc
105
- # encoding: utf-8"
106
- first line
107
- TEMPLATE
108
- end
109
- expect(output).to eq(<<-TEMPLATE.strip_heredoc)
103
+ output = render(<<~TEMPLATE)
104
+ # encoding: utf-8"
105
+ first line
106
+ TEMPLATE
107
+ expect(output).to eq(<<~TEMPLATE)
110
108
 
111
109
  first line
112
110
  TEMPLATE
@@ -114,55 +112,54 @@ describe Curlybars::TemplateHandler do
114
112
 
115
113
  it "passes in the presenter context to the presenter class" do
116
114
  allow(context).to receive(:bar).and_return("BAR")
117
- allow(template).to receive(:source).and_return("{{bar}}")
115
+ output = render("{{bar}}")
118
116
  expect(output).to eq("BAR")
119
117
  end
120
118
 
121
119
  it "fails if there's no matching presenter class" do
122
120
  allow(template).to receive(:virtual_path).and_return("missing")
123
- allow(template).to receive(:source).and_return(" FOO ")
124
- expect { output }.to raise_exception(Curlybars::Error::Presenter::NotFound)
121
+ expect { render(" FOO ") }.to raise_exception(Curlybars::Error::Presenter::NotFound)
125
122
  end
126
123
 
127
124
  it "allows calling public methods on the presenter" do
128
- allow(template).to receive(:source).and_return("{{foo}}")
125
+ output = render("{{foo}}")
129
126
  expect(output).to eq("FOO")
130
127
  end
131
128
 
132
129
  it "marks its output as HTML safe" do
133
- allow(template).to receive(:source).and_return("{{foo}}")
130
+ output = render("{{foo}}")
134
131
  expect(output).to be_html_safe
135
132
  end
136
133
 
137
134
  it "calls the #setup! method before rendering the view" do
138
- allow(template).to receive(:source).and_return("{{foo}}")
139
- output
135
+ render("{{foo}}")
140
136
  expect(context.content_for(:foo)).to eq("bar")
141
137
  end
142
138
 
143
139
  describe "caching" do
144
140
  before do
145
- allow(template).to receive(:source).and_return("{{bar}}")
146
141
  allow(context).to receive(:bar).and_return("BAR")
147
142
  end
148
143
 
144
+ let(:output) { -> { render("{{bar}}") } }
145
+
149
146
  it "caches the result with the #cache_key from the presenter" do
150
147
  context.assigns[:cache_key] = "x"
151
- expect(output).to eq("BAR")
148
+ expect(output.call).to eq("BAR")
152
149
 
153
150
  allow(context).to receive(:bar).and_return("BAZ")
154
- expect(output).to eq("BAR")
151
+ expect(output.call).to eq("BAR")
155
152
 
156
153
  context.assigns[:cache_key] = "y"
157
- expect(output).to eq("BAZ")
154
+ expect(output.call).to eq("BAZ")
158
155
  end
159
156
 
160
157
  it "doesn't cache when the cache key is nil" do
161
158
  context.assigns[:cache_key] = nil
162
- expect(output).to eq("BAR")
159
+ expect(output.call).to eq("BAR")
163
160
 
164
161
  allow(context).to receive(:bar).and_return("BAZ")
165
- expect(output).to eq("BAZ")
162
+ expect(output.call).to eq("BAZ")
166
163
  end
167
164
 
168
165
  it "adds the presenter class' cache key to the instance's cache key" do
@@ -171,51 +168,57 @@ describe Curlybars::TemplateHandler do
171
168
 
172
169
  allow(presenter_class).to receive(:cache_key).and_return("foo")
173
170
 
174
- expect(output).to eq("BAR")
171
+ expect(output.call).to eq("BAR")
175
172
 
176
173
  allow(presenter_class).to receive(:cache_key).and_return("bar")
177
174
 
178
175
  allow(context).to receive(:bar).and_return("FOOBAR")
179
- expect(output).to eq("FOOBAR")
176
+ expect(output.call).to eq("FOOBAR")
180
177
  end
181
178
 
182
179
  it "expires the cache keys after #cache_duration" do
183
180
  context.assigns[:cache_key] = "x"
184
181
  context.assigns[:cache_duration] = 42
185
182
 
186
- expect(output).to eq("BAR")
183
+ expect(output.call).to eq("BAR")
187
184
 
188
185
  allow(context).to receive(:bar).and_return("FOO")
189
186
 
190
187
  # Cached fragment has not yet expired.
191
188
  context.advance_clock(41)
192
- expect(output).to eq("BAR")
189
+ expect(output.call).to eq("BAR")
193
190
 
194
191
  # Now it has! Huzzah!
195
192
  context.advance_clock(1)
196
- expect(output).to eq("FOO")
193
+ expect(output.call).to eq("FOO")
197
194
  end
198
195
 
199
196
  it "passes #cache_options to the cache backend" do
200
197
  context.assigns[:cache_key] = "x"
201
198
  context.assigns[:cache_options] = { expires_in: 42 }
202
199
 
203
- expect(output).to eq("BAR")
200
+ expect(output.call).to eq("BAR")
204
201
 
205
202
  allow(context).to receive(:bar).and_return("FOO")
206
203
 
207
204
  # Cached fragment has not yet expired.
208
205
  context.advance_clock(41)
209
- expect(output).to eq("BAR")
206
+ expect(output.call).to eq("BAR")
210
207
 
211
208
  # Now it has! Huzzah!
212
209
  context.advance_clock(1)
213
- expect(output).to eq("FOO")
210
+ expect(output.call).to eq("FOO")
214
211
  end
215
212
  end
216
213
 
217
- def output
218
- code = Curlybars::TemplateHandler.call(template)
214
+ def render(source)
215
+ if ActionView::VERSION::MAJOR < 6
216
+ allow(template).to receive(:source).and_return(source)
217
+ code = Curlybars::TemplateHandler.call(template)
218
+ else
219
+ code = Curlybars::TemplateHandler.call(template, source)
220
+ end
221
+
219
222
  context.reset!
220
223
  context.instance_eval(code)
221
224
  end