curlybars 1.3.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/curlybars.rb +0 -1
  3. data/lib/curlybars/configuration.rb +1 -9
  4. data/lib/curlybars/error/base.rb +2 -0
  5. data/lib/curlybars/lexer.rb +3 -0
  6. data/lib/curlybars/method_whitelist.rb +16 -11
  7. data/lib/curlybars/node/block_helper_else.rb +1 -0
  8. data/lib/curlybars/node/if_else.rb +1 -1
  9. data/lib/curlybars/node/path.rb +6 -0
  10. data/lib/curlybars/node/sub_expression.rb +62 -0
  11. data/lib/curlybars/node/unless_else.rb +1 -1
  12. data/lib/curlybars/parser.rb +11 -0
  13. data/lib/curlybars/processor/tilde.rb +3 -0
  14. data/lib/curlybars/rendering_support.rb +9 -1
  15. data/lib/curlybars/template_handler.rb +18 -6
  16. data/lib/curlybars/version.rb +1 -1
  17. data/lib/curlybars/visitor.rb +6 -0
  18. data/spec/acceptance/application_layout_spec.rb +2 -2
  19. data/spec/acceptance/collection_blocks_spec.rb +1 -1
  20. data/spec/acceptance/global_helper_spec.rb +1 -1
  21. data/spec/curlybars/lexer_spec.rb +25 -2
  22. data/spec/curlybars/method_whitelist_spec.rb +15 -0
  23. data/spec/curlybars/rendering_support_spec.rb +4 -9
  24. data/spec/curlybars/template_handler_spec.rb +33 -30
  25. data/spec/integration/cache_spec.rb +20 -18
  26. data/spec/integration/node/block_helper_else_spec.rb +0 -2
  27. data/spec/integration/node/each_else_spec.rb +0 -2
  28. data/spec/integration/node/each_spec.rb +0 -2
  29. data/spec/integration/node/helper_spec.rb +12 -2
  30. data/spec/integration/node/if_else_spec.rb +0 -2
  31. data/spec/integration/node/if_spec.rb +2 -4
  32. data/spec/integration/node/output_spec.rb +0 -2
  33. data/spec/integration/node/partial_spec.rb +0 -2
  34. data/spec/integration/node/path_spec.rb +0 -2
  35. data/spec/integration/node/root_spec.rb +0 -2
  36. data/spec/integration/node/sub_expression_spec.rb +426 -0
  37. data/spec/integration/node/template_spec.rb +0 -2
  38. data/spec/integration/node/unless_else_spec.rb +2 -4
  39. data/spec/integration/node/unless_spec.rb +0 -2
  40. data/spec/integration/node/with_spec.rb +0 -2
  41. data/spec/integration/processors_spec.rb +0 -1
  42. data/spec/integration/visitor_spec.rb +13 -5
  43. metadata +47 -15
@@ -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
@@ -16,6 +16,8 @@ describe Curlybars::MethodWhitelist do
16
16
 
17
17
  let(:validation_context_class) do
18
18
  Class.new do
19
+ attr_accessor :invocation_count
20
+
19
21
  def foo?
20
22
  true
21
23
  end
@@ -130,10 +132,15 @@ describe Curlybars::MethodWhitelist do
130
132
 
131
133
  Class.new do
132
134
  extend Curlybars::MethodWhitelist
135
+ attr_accessor :invocation_count
136
+
133
137
  allow_methods :cook, link: LinkPresenter do |context, allow_method|
134
138
  if context.foo?
135
139
  allow_method.call(:bar)
136
140
  end
141
+
142
+ context.invocation_count ||= 0
143
+ context.invocation_count += 1
137
144
  end
138
145
 
139
146
  def foo?
@@ -173,6 +180,14 @@ describe Curlybars::MethodWhitelist do
173
180
  expect(post_presenter.new.allowed_methods).to eq([:cook, :link, :bar, :form, :foo_bar, :wave])
174
181
  end
175
182
 
183
+ it "only invokes the context block once" do
184
+ presenter = post_presenter.new
185
+
186
+ 10.times { presenter.allowed_methods }
187
+
188
+ expect(presenter.invocation_count).to eq(1)
189
+ end
190
+
176
191
  it "returns a dependency_tree with inheritance and composition with context" do
177
192
  expect(post_presenter.dependency_tree(validation_context_class.new)).
178
193
  to eq(
@@ -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
@@ -1,29 +1,31 @@
1
1
  describe "caching" do
2
- class DummyCache
3
- attr_reader :reads, :hits
4
-
5
- def initialize
6
- @store = {}
7
- @reads = 0
8
- @hits = 0
9
- end
2
+ let(:dummy_cache) do
3
+ Class.new do
4
+ attr_reader :reads, :hits
5
+
6
+ def initialize
7
+ @store = {}
8
+ @reads = 0
9
+ @hits = 0
10
+ end
10
11
 
11
- def fetch(key)
12
- @reads += 1
13
- if @store.key?(key)
14
- @hits += 1
15
- @store[key]
16
- else
17
- value = yield
18
- @store[key] = value
19
- value
12
+ def fetch(key)
13
+ @reads += 1
14
+ if @store.key?(key)
15
+ @hits += 1
16
+ @store[key]
17
+ else
18
+ value = yield
19
+ @store[key] = value
20
+ value
21
+ end
20
22
  end
21
23
  end
22
24
  end
23
25
 
24
26
  let(:global_helpers_providers) { [] }
25
27
  let(:presenter) { IntegrationTest::Presenter.new(double("view_context")) }
26
- let(:cache) { DummyCache.new }
28
+ let(:cache) { dummy_cache.new }
27
29
 
28
30
  before do
29
31
  Curlybars.configure do |config|
@@ -230,8 +230,6 @@ describe "{{#helper arg1 arg2 ... key=value ...}}...<{{else}}>...{{/helper}}" do
230
230
  end
231
231
 
232
232
  describe "#validate" do
233
- let(:presenter_class) { double(:presenter_class) }
234
-
235
233
  it "without errors when global helper" do
236
234
  allow(Curlybars.configuration).to receive(:global_helpers_provider_classes).and_return([IntegrationTest::GlobalHelperProvider])
237
235
 
@@ -151,8 +151,6 @@ describe "{{#each collection}}...{{else}}...{{/each}}" do
151
151
  end
152
152
 
153
153
  describe "#validate" do
154
- let(:presenter_class) { double(:presenter_class) }
155
-
156
154
  it "without errors" do
157
155
  dependency_tree = { a_presenter_collection: [{}] }
158
156
 
@@ -238,8 +238,6 @@ describe "{{#each collection}}...{{/each}}" do
238
238
  end
239
239
 
240
240
  describe "#validate" do
241
- let(:presenter_class) { double(:presenter_class) }
242
-
243
241
  it "without errors" do
244
242
  dependency_tree = { a_presenter_collection: [{}] }
245
243
 
@@ -15,6 +15,18 @@ describe "{{helper context key=value}}" do
15
15
  HTML
16
16
  end
17
17
 
18
+ it "calls a helper without arguments in an if statement" do
19
+ template = Curlybars.compile(<<-HBS)
20
+ {{#if print_args_and_options}}
21
+ {{print_args_and_options 'first' 'second'}}
22
+ {{/if}}
23
+ HBS
24
+
25
+ expect(eval(template)).to resemble(<<-HTML)
26
+ first, second, key=
27
+ HTML
28
+ end
29
+
18
30
  it "passes two arguments and options" do
19
31
  template = Curlybars.compile(<<-HBS)
20
32
  {{print_args_and_options 'first' 'second' key='value'}}
@@ -109,8 +121,6 @@ describe "{{helper context key=value}}" do
109
121
  end
110
122
 
111
123
  describe "#validate" do
112
- let(:presenter_class) { double(:presenter_class) }
113
-
114
124
  it "with errors" do
115
125
  dependency_tree = {}
116
126
 
@@ -82,8 +82,6 @@ describe "{{#if}}...{{else}}...{{/if}}" do
82
82
  end
83
83
 
84
84
  describe "#validate" do
85
- let(:presenter_class) { double(:presenter_class) }
86
-
87
85
  it "validates without errors the literal condition" do
88
86
  dependency_tree = {}
89
87
 
@@ -112,8 +112,6 @@ describe "{{#if}}...{{/if}}" do
112
112
  end
113
113
 
114
114
  describe "#validate" do
115
- let(:presenter_class) { double(:presenter_class) }
116
-
117
115
  it "validates with errors the condition" do
118
116
  dependency_tree = {}
119
117
 
@@ -140,7 +138,7 @@ describe "{{#if}}...{{/if}}" do
140
138
  expect(errors).not_to be_empty
141
139
  end
142
140
 
143
- it "validates with errors the helper as condition" do
141
+ it "validates without errors the helper as condition" do
144
142
  dependency_tree = { helper: :helper }
145
143
 
146
144
  source = <<-HBS
@@ -149,7 +147,7 @@ describe "{{#if}}...{{/if}}" do
149
147
 
150
148
  errors = Curlybars.validate(dependency_tree, source)
151
149
 
152
- expect(errors).not_to be_empty
150
+ expect(errors).to be_empty
153
151
  end
154
152
  end
155
153
  end
@@ -51,8 +51,6 @@ describe '{{value}}' do
51
51
  end
52
52
 
53
53
  describe "#validate" do
54
- let(:presenter_class) { double(:presenter_class) }
55
-
56
54
  it "validates the path with errors" do
57
55
  dependency_tree = {}
58
56