curlybars 1.3.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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