curlybars 1.3.0 → 1.6.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 +4 -4
- data/lib/curlybars.rb +0 -1
- data/lib/curlybars/configuration.rb +1 -9
- data/lib/curlybars/error/base.rb +2 -0
- data/lib/curlybars/lexer.rb +3 -0
- data/lib/curlybars/method_whitelist.rb +16 -11
- data/lib/curlybars/node/block_helper_else.rb +1 -0
- data/lib/curlybars/node/if_else.rb +1 -1
- data/lib/curlybars/node/path.rb +6 -0
- data/lib/curlybars/node/sub_expression.rb +62 -0
- data/lib/curlybars/node/unless_else.rb +1 -1
- data/lib/curlybars/parser.rb +11 -0
- data/lib/curlybars/processor/tilde.rb +3 -0
- data/lib/curlybars/rendering_support.rb +9 -1
- data/lib/curlybars/template_handler.rb +18 -6
- data/lib/curlybars/version.rb +1 -1
- data/lib/curlybars/visitor.rb +6 -0
- data/spec/acceptance/application_layout_spec.rb +2 -2
- data/spec/acceptance/collection_blocks_spec.rb +1 -1
- data/spec/acceptance/global_helper_spec.rb +1 -1
- data/spec/curlybars/lexer_spec.rb +25 -2
- data/spec/curlybars/method_whitelist_spec.rb +15 -0
- data/spec/curlybars/rendering_support_spec.rb +4 -9
- data/spec/curlybars/template_handler_spec.rb +33 -30
- data/spec/integration/cache_spec.rb +20 -18
- data/spec/integration/node/block_helper_else_spec.rb +0 -2
- data/spec/integration/node/each_else_spec.rb +0 -2
- data/spec/integration/node/each_spec.rb +0 -2
- data/spec/integration/node/helper_spec.rb +12 -2
- data/spec/integration/node/if_else_spec.rb +0 -2
- data/spec/integration/node/if_spec.rb +2 -4
- data/spec/integration/node/output_spec.rb +0 -2
- data/spec/integration/node/partial_spec.rb +0 -2
- data/spec/integration/node/path_spec.rb +0 -2
- data/spec/integration/node/root_spec.rb +0 -2
- data/spec/integration/node/sub_expression_spec.rb +426 -0
- data/spec/integration/node/template_spec.rb +0 -2
- data/spec/integration/node/unless_else_spec.rb +2 -4
- data/spec/integration/node/unless_spec.rb +0 -2
- data/spec/integration/node/with_spec.rb +0 -2
- data/spec/integration/processors_spec.rb +0 -1
- data/spec/integration/visitor_spec.rb +13 -5
- metadata +47 -15
@@ -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/
|
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/
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
218
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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) {
|
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
|
|
@@ -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
|
|
@@ -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
|
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).
|
150
|
+
expect(errors).to be_empty
|
153
151
|
end
|
154
152
|
end
|
155
153
|
end
|