curlybars 0.9.13
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 +7 -0
- data/lib/curlybars.rb +108 -0
- data/lib/curlybars/configuration.rb +41 -0
- data/lib/curlybars/dependency_tracker.rb +8 -0
- data/lib/curlybars/error/base.rb +18 -0
- data/lib/curlybars/error/compile.rb +11 -0
- data/lib/curlybars/error/lex.rb +22 -0
- data/lib/curlybars/error/parse.rb +41 -0
- data/lib/curlybars/error/presenter/not_found.rb +23 -0
- data/lib/curlybars/error/render.rb +11 -0
- data/lib/curlybars/error/validate.rb +18 -0
- data/lib/curlybars/lexer.rb +60 -0
- data/lib/curlybars/method_whitelist.rb +69 -0
- data/lib/curlybars/node/block_helper_else.rb +108 -0
- data/lib/curlybars/node/boolean.rb +24 -0
- data/lib/curlybars/node/each_else.rb +69 -0
- data/lib/curlybars/node/if_else.rb +33 -0
- data/lib/curlybars/node/item.rb +31 -0
- data/lib/curlybars/node/literal.rb +28 -0
- data/lib/curlybars/node/option.rb +25 -0
- data/lib/curlybars/node/output.rb +24 -0
- data/lib/curlybars/node/partial.rb +24 -0
- data/lib/curlybars/node/path.rb +137 -0
- data/lib/curlybars/node/root.rb +29 -0
- data/lib/curlybars/node/string.rb +24 -0
- data/lib/curlybars/node/template.rb +32 -0
- data/lib/curlybars/node/text.rb +24 -0
- data/lib/curlybars/node/unless_else.rb +33 -0
- data/lib/curlybars/node/variable.rb +34 -0
- data/lib/curlybars/node/with_else.rb +54 -0
- data/lib/curlybars/parser.rb +183 -0
- data/lib/curlybars/position.rb +7 -0
- data/lib/curlybars/presenter.rb +288 -0
- data/lib/curlybars/processor/tilde.rb +31 -0
- data/lib/curlybars/processor/token_factory.rb +9 -0
- data/lib/curlybars/railtie.rb +18 -0
- data/lib/curlybars/rendering_support.rb +222 -0
- data/lib/curlybars/safe_buffer.rb +11 -0
- data/lib/curlybars/template_handler.rb +93 -0
- data/lib/curlybars/version.rb +3 -0
- data/spec/acceptance/application_layout_spec.rb +60 -0
- data/spec/acceptance/collection_blocks_spec.rb +28 -0
- data/spec/acceptance/global_helper_spec.rb +25 -0
- data/spec/curlybars/configuration_spec.rb +57 -0
- data/spec/curlybars/error/base_spec.rb +41 -0
- data/spec/curlybars/error/compile_spec.rb +19 -0
- data/spec/curlybars/error/lex_spec.rb +25 -0
- data/spec/curlybars/error/parse_spec.rb +74 -0
- data/spec/curlybars/error/render_spec.rb +19 -0
- data/spec/curlybars/error/validate_spec.rb +19 -0
- data/spec/curlybars/lexer_spec.rb +466 -0
- data/spec/curlybars/method_whitelist_spec.rb +168 -0
- data/spec/curlybars/processor/tilde_spec.rb +60 -0
- data/spec/curlybars/rendering_support_spec.rb +426 -0
- data/spec/curlybars/safe_buffer_spec.rb +46 -0
- data/spec/curlybars/template_handler_spec.rb +222 -0
- data/spec/integration/cache_spec.rb +124 -0
- data/spec/integration/comment_spec.rb +60 -0
- data/spec/integration/exception_spec.rb +31 -0
- data/spec/integration/node/block_helper_else_spec.rb +422 -0
- data/spec/integration/node/each_else_spec.rb +204 -0
- data/spec/integration/node/each_spec.rb +291 -0
- data/spec/integration/node/escape_spec.rb +27 -0
- data/spec/integration/node/helper_spec.rb +176 -0
- data/spec/integration/node/if_else_spec.rb +129 -0
- data/spec/integration/node/if_spec.rb +143 -0
- data/spec/integration/node/output_spec.rb +68 -0
- data/spec/integration/node/partial_spec.rb +66 -0
- data/spec/integration/node/path_spec.rb +286 -0
- data/spec/integration/node/root_spec.rb +15 -0
- data/spec/integration/node/template_spec.rb +86 -0
- data/spec/integration/node/unless_else_spec.rb +129 -0
- data/spec/integration/node/unless_spec.rb +130 -0
- data/spec/integration/node/with_spec.rb +116 -0
- data/spec/integration/processor/tilde_spec.rb +38 -0
- data/spec/integration/processors_spec.rb +30 -0
- metadata +358 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
describe Curlybars::MethodWhitelist do
|
2
|
+
let(:dummy_class) { Class.new { extend Curlybars::MethodWhitelist } }
|
3
|
+
|
4
|
+
describe "#allowed_methods" do
|
5
|
+
it "returns an empty array as default" do
|
6
|
+
expect(dummy_class.new.allowed_methods).to eq([])
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".allow_methods" do
|
11
|
+
before do
|
12
|
+
link_presenter = Class.new
|
13
|
+
article_presenter = Class.new
|
14
|
+
|
15
|
+
dummy_class.class_eval do
|
16
|
+
allow_methods :cook, link: link_presenter, article: article_presenter
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets the allowed methods" do
|
21
|
+
expect(dummy_class.new.allowed_methods).to eq([:cook, :link, :article])
|
22
|
+
end
|
23
|
+
|
24
|
+
it "raises when collection is not of presenters" do
|
25
|
+
expect do
|
26
|
+
dummy_class.class_eval { allow_methods :cook, links: ["foobar"] }
|
27
|
+
end.to raise_error(RuntimeError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises when collection cardinality is greater than one" do
|
31
|
+
stub_const("OnePresenter", Class.new { extend Curlybars::MethodWhitelist })
|
32
|
+
stub_const("OtherPresenter", Class.new { extend Curlybars::MethodWhitelist })
|
33
|
+
|
34
|
+
expect do
|
35
|
+
dummy_class.class_eval { allow_methods :cook, links: [OnePresenter, OtherPresenter] }
|
36
|
+
end.to raise_error(RuntimeError)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "inheritance and composition" do
|
41
|
+
let(:base_presenter) do
|
42
|
+
stub_const("LinkPresenter", Class.new)
|
43
|
+
|
44
|
+
Class.new do
|
45
|
+
extend Curlybars::MethodWhitelist
|
46
|
+
allow_methods :cook, link: LinkPresenter
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
let(:helpers) do
|
51
|
+
Module.new do
|
52
|
+
extend Curlybars::MethodWhitelist
|
53
|
+
allow_methods :form
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
let(:post_presenter) do
|
58
|
+
Class.new(base_presenter) do
|
59
|
+
extend Curlybars::MethodWhitelist
|
60
|
+
include Helpers
|
61
|
+
allow_methods :wave
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
before do
|
66
|
+
stub_const("Helpers", helpers)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "allows methods from inheritance and composition" do
|
70
|
+
expect(post_presenter.new.allowed_methods).to eq([:cook, :link, :form, :wave])
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns a dependency_tree with inheritance and composition" do
|
74
|
+
expect(post_presenter.dependency_tree).
|
75
|
+
to eq(
|
76
|
+
cook: nil,
|
77
|
+
link: LinkPresenter,
|
78
|
+
form: nil,
|
79
|
+
wave: nil
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe ".methods_schema" do
|
85
|
+
it "setups a schema propagating nil" do
|
86
|
+
stub_const("LinkPresenter", Class.new { extend Curlybars::MethodWhitelist })
|
87
|
+
dummy_class.class_eval { allow_methods :cook }
|
88
|
+
|
89
|
+
expect(dummy_class.methods_schema).to eq(cook: nil)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "setups a schema any random type" do
|
93
|
+
stub_const("LinkPresenter", Class.new { extend Curlybars::MethodWhitelist })
|
94
|
+
dummy_class.class_eval { allow_methods something: :foobar }
|
95
|
+
|
96
|
+
expect(dummy_class.methods_schema).to eq(something: :foobar)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "setups a schema propagating the return type of the method" do
|
100
|
+
stub_const("ArticlePresenter", Class.new { extend Curlybars::MethodWhitelist })
|
101
|
+
dummy_class.class_eval { allow_methods article: ArticlePresenter }
|
102
|
+
|
103
|
+
expect(dummy_class.methods_schema).to eq(article: ArticlePresenter)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "setups a schema propagating a collection" do
|
107
|
+
stub_const("LinkPresenter", Class.new { extend Curlybars::MethodWhitelist })
|
108
|
+
dummy_class.class_eval { allow_methods links: [LinkPresenter] }
|
109
|
+
|
110
|
+
expect(dummy_class.methods_schema).to eq(links: [LinkPresenter])
|
111
|
+
end
|
112
|
+
|
113
|
+
it "supports procs in schema" do
|
114
|
+
dummy_class.class_eval { allow_methods settings: ->() { { color_1: nil } } }
|
115
|
+
|
116
|
+
expect(dummy_class.methods_schema).to eq(settings: { color_1: nil })
|
117
|
+
end
|
118
|
+
|
119
|
+
it "supports procs with arguments in schema" do
|
120
|
+
dummy_class.class_eval { allow_methods settings: ->(name) { Hash[name, nil] } }
|
121
|
+
|
122
|
+
expect(dummy_class.methods_schema(:background_color)).to eq(settings: { background_color: nil })
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe ".dependency_tree" do
|
127
|
+
it "returns a dependencies tree" do
|
128
|
+
link_presenter = Class.new do
|
129
|
+
extend Curlybars::MethodWhitelist
|
130
|
+
allow_methods :url
|
131
|
+
end
|
132
|
+
|
133
|
+
article_presenter = Class.new do
|
134
|
+
extend Curlybars::MethodWhitelist
|
135
|
+
allow_methods :title, :body
|
136
|
+
end
|
137
|
+
|
138
|
+
stub_const("ArticlePresenter", article_presenter)
|
139
|
+
stub_const("LinkPresenter", link_presenter)
|
140
|
+
|
141
|
+
dummy_class.class_eval do
|
142
|
+
allow_methods links: [LinkPresenter], article: ArticlePresenter
|
143
|
+
end
|
144
|
+
|
145
|
+
expect(dummy_class.dependency_tree).
|
146
|
+
to eq(
|
147
|
+
links: [{ url: nil }],
|
148
|
+
article: {
|
149
|
+
title: nil,
|
150
|
+
body: nil
|
151
|
+
}
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "propagates arguments" do
|
156
|
+
dummy_class.class_eval do
|
157
|
+
allow_methods label: ->(label) { Hash[label, nil] }
|
158
|
+
end
|
159
|
+
|
160
|
+
expect(dummy_class.dependency_tree(:some_label)).
|
161
|
+
to eq(
|
162
|
+
label: {
|
163
|
+
some_label: nil
|
164
|
+
}
|
165
|
+
)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
describe Curlybars::Processor::Tilde do
|
2
|
+
let(:tilde_start_token) { double(:tilde_start, type: :TILDE_START, value: nil, position: nil) }
|
3
|
+
let(:tilde_end_token) { double(:tilde_end, type: :TILDE_END, value: nil, position: nil) }
|
4
|
+
let(:start_token) { double(:start, type: :START, value: nil, position: nil) }
|
5
|
+
let(:end_token) { double(:end, type: :END, value: nil, position: nil) }
|
6
|
+
|
7
|
+
describe ":TILDE_START" do
|
8
|
+
it "trims the previous text token" do
|
9
|
+
tokens = [
|
10
|
+
text_token("text \t\r\n"),
|
11
|
+
tilde_start_token
|
12
|
+
]
|
13
|
+
Curlybars::Processor::Tilde.process!(tokens, 'identifier')
|
14
|
+
|
15
|
+
expect(tokens.first.value).to eq 'text'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't trim the previous text token when not right before" do
|
19
|
+
tokens = [
|
20
|
+
text_token("text \t\r\n"),
|
21
|
+
start_token,
|
22
|
+
end_token,
|
23
|
+
tilde_start_token
|
24
|
+
]
|
25
|
+
Curlybars::Processor::Tilde.process!(tokens, 'identifier')
|
26
|
+
|
27
|
+
expect(tokens.first.value).to eq "text \t\r\n"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe ":TILDE_END" do
|
32
|
+
it "trims the following text token" do
|
33
|
+
tokens = [
|
34
|
+
tilde_end_token,
|
35
|
+
text_token("\t\r\n text")
|
36
|
+
]
|
37
|
+
Curlybars::Processor::Tilde.process!(tokens, 'identifier')
|
38
|
+
|
39
|
+
expect(tokens.last.value).to eq 'text'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "doesn't trim the following text token when not right after" do
|
43
|
+
tokens = [
|
44
|
+
tilde_end_token,
|
45
|
+
start_token,
|
46
|
+
end_token,
|
47
|
+
text_token("\t\r\n text")
|
48
|
+
]
|
49
|
+
Curlybars::Processor::Tilde.process!(tokens, 'identifier')
|
50
|
+
|
51
|
+
expect(tokens.last.value).to eq "\t\r\n text"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def text_token(value)
|
58
|
+
double(:text, type: :TEXT, value: value, position: nil)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,426 @@
|
|
1
|
+
describe Curlybars::RenderingSupport do
|
2
|
+
let(:file_name) { '/app/views/template.hbs' }
|
3
|
+
let(:presenter) { double(:presenter, allows_method?: true, meth: :value) }
|
4
|
+
let(:contexts) { [presenter] }
|
5
|
+
let(:variables) { [{}] }
|
6
|
+
let(:rendering) { Curlybars::RenderingSupport.new(1.second, contexts, variables, file_name) }
|
7
|
+
let(:position) do
|
8
|
+
double(:position, file_name: 'template.hbs', line_number: 1, line_offset: 0)
|
9
|
+
end
|
10
|
+
let(:block) { -> {} }
|
11
|
+
|
12
|
+
describe "#check_timeout!" do
|
13
|
+
it "skips checking if timeout is nil" do
|
14
|
+
rendering = Curlybars::RenderingSupport.new(nil, contexts, variables, file_name)
|
15
|
+
|
16
|
+
sleep 0.1.seconds
|
17
|
+
expect { rendering.check_timeout! }.not_to raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
it "doesn't happen when rendering is < rendering_timeout" do
|
21
|
+
rendering = Curlybars::RenderingSupport.new(10.seconds, contexts, variables, file_name)
|
22
|
+
expect { rendering.check_timeout! }.not_to raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it "happens and raises when rendering >= rendering_timeout" do
|
26
|
+
rendering = Curlybars::RenderingSupport.new(0.01.seconds, contexts, variables, file_name)
|
27
|
+
|
28
|
+
sleep 0.1.seconds
|
29
|
+
expect { rendering.check_timeout! }.to raise_error(Curlybars::Error::Render)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#to_bool" do
|
34
|
+
describe "returns true" do
|
35
|
+
it "with `true`" do
|
36
|
+
expect(rendering.to_bool(true)).to be_truthy
|
37
|
+
end
|
38
|
+
|
39
|
+
it "with `[:non_empty]`" do
|
40
|
+
expect(rendering.to_bool([:non_empty])).to be_truthy
|
41
|
+
end
|
42
|
+
|
43
|
+
it "with `1`" do
|
44
|
+
expect(rendering.to_bool(1)).to be_truthy
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "returns false" do
|
49
|
+
it "with `false`" do
|
50
|
+
expect(rendering.to_bool(false)).to be_falsey
|
51
|
+
end
|
52
|
+
|
53
|
+
it "with `[]`" do
|
54
|
+
expect(rendering.to_bool([])).to be_falsey
|
55
|
+
end
|
56
|
+
|
57
|
+
it "with `{}`" do
|
58
|
+
expect(rendering.to_bool({})).to be_falsey
|
59
|
+
end
|
60
|
+
|
61
|
+
it "with `0`" do
|
62
|
+
expect(rendering.to_bool(0)).to be_falsey
|
63
|
+
end
|
64
|
+
|
65
|
+
it "with `nil`" do
|
66
|
+
expect(rendering.to_bool(nil)).to be_falsey
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#path" do
|
72
|
+
it "returns the method in the current context" do
|
73
|
+
allow_all_methods(presenter)
|
74
|
+
allow(presenter).to receive(:method) { :method }
|
75
|
+
|
76
|
+
expect(rendering.path('method', rendering.position(0, 1))).to eq :method
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns the method in the current context" do
|
80
|
+
sub = double(:sub_presenter)
|
81
|
+
allow_all_methods(sub)
|
82
|
+
allow(sub).to receive(:method) { :method }
|
83
|
+
|
84
|
+
allow_all_methods(presenter)
|
85
|
+
allow(presenter).to receive(:sub) { sub }
|
86
|
+
|
87
|
+
expect(rendering.path('sub.method', rendering.position(0, 1))).to eq :method
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns the length of a collection, when `lenght` is the last step" do
|
91
|
+
allow_all_methods(presenter)
|
92
|
+
single_element_presenter = double(:single_element_presenter)
|
93
|
+
allow_all_methods(single_element_presenter)
|
94
|
+
collection = [single_element_presenter]
|
95
|
+
allow(presenter).to receive(:collection) { collection }
|
96
|
+
|
97
|
+
returned_method = rendering.path('collection.length', rendering.position(0, 1))
|
98
|
+
expect(returned_method.call).to eq collection.length
|
99
|
+
end
|
100
|
+
|
101
|
+
it "raises an exception when the method is not allowed" do
|
102
|
+
disallow_all_methods(presenter)
|
103
|
+
allow(presenter).to receive(:forbidden_method) { :forbidden_method }
|
104
|
+
|
105
|
+
expect do
|
106
|
+
rendering.path('forbidden_method', rendering.position(0, 1))
|
107
|
+
end.to raise_error(Curlybars::Error::Render)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "exposes the unallowed method in the exception payload" do
|
111
|
+
disallow_all_methods(presenter)
|
112
|
+
allow(presenter).to receive(:forbidden_method) { :forbidden_method }
|
113
|
+
|
114
|
+
begin
|
115
|
+
rendering.path('forbidden_method', rendering.position(0, 1))
|
116
|
+
rescue Curlybars::Error::Render => e
|
117
|
+
expect(e.metadata).to eq(meth: :forbidden_method)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "raises an exception when the context is not a presenter" do
|
122
|
+
sub = double(:not_presenter)
|
123
|
+
allow(presenter).to receive(:sub) { sub }
|
124
|
+
|
125
|
+
expect do
|
126
|
+
rendering.path('sub.method', rendering.position(0, 1))
|
127
|
+
end.to raise_error(Curlybars::Error::Render)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "refers to the second to last presenter in the stack when using `../`" do
|
131
|
+
sub = double(:sub_presenter)
|
132
|
+
allow_all_methods(sub)
|
133
|
+
allow(sub).to receive(:method) { :sub_method }
|
134
|
+
|
135
|
+
allow_all_methods(presenter)
|
136
|
+
allow(presenter).to receive(:method) { :root_method }
|
137
|
+
|
138
|
+
contexts.push(sub)
|
139
|
+
|
140
|
+
expect(rendering.path('../method', rendering.position(0, 1))).to eq :root_method
|
141
|
+
end
|
142
|
+
|
143
|
+
it "refers to the third to last presenter in the stack when using `../../`" do
|
144
|
+
sub_sub = double(:sub_presenter)
|
145
|
+
allow_all_methods(sub_sub)
|
146
|
+
allow(sub_sub).to receive(:method) { :sub_sub_method }
|
147
|
+
|
148
|
+
sub = double(:sub_presenter)
|
149
|
+
allow_all_methods(sub)
|
150
|
+
allow(sub).to receive(:method) { :sub_method }
|
151
|
+
|
152
|
+
allow_all_methods(presenter)
|
153
|
+
allow(presenter).to receive(:method) { :root_method }
|
154
|
+
|
155
|
+
contexts.push(sub)
|
156
|
+
contexts.push(sub_sub)
|
157
|
+
|
158
|
+
expect(rendering.path('../../method', rendering.position(0, 1))).to eq :root_method
|
159
|
+
end
|
160
|
+
|
161
|
+
it "returns a method that returns nil, if nil is returned from any method in the chain (except the latter)" do
|
162
|
+
allow_all_methods(presenter)
|
163
|
+
allow(presenter).to receive(:returns_nil) { nil }
|
164
|
+
|
165
|
+
outcome = rendering.path('returns_nil.another_method', rendering.position(0, 1)).call
|
166
|
+
expect(outcome).to be_nil
|
167
|
+
end
|
168
|
+
|
169
|
+
it "returns a method that returns nil, if `../`` goes too deep in the stack" do
|
170
|
+
outcome = rendering.path('../too_deep', rendering.position(0, 1)).call
|
171
|
+
expect(outcome).to be_nil
|
172
|
+
end
|
173
|
+
|
174
|
+
it "raises an exception if tha path is too deep (> 10)" do
|
175
|
+
expect do
|
176
|
+
rendering.path('a.b.c.d.e.f.g.h.i.l', rendering.position(0, 1))
|
177
|
+
end.to raise_error(Curlybars::Error::Render)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "#cached_call" do
|
182
|
+
before do
|
183
|
+
class APresenter
|
184
|
+
def meth
|
185
|
+
:value
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
it "(cache miss) calls the method if not cached already" do
|
191
|
+
meth = presenter.method(:meth)
|
192
|
+
allow(meth).to receive(:call)
|
193
|
+
|
194
|
+
rendering.cached_call(meth)
|
195
|
+
|
196
|
+
expect(meth).to have_received(:call).once
|
197
|
+
end
|
198
|
+
|
199
|
+
it "(cache hit) avoids to call a method for more than one time" do
|
200
|
+
meth = presenter.method(:meth)
|
201
|
+
allow(meth).to receive(:call)
|
202
|
+
|
203
|
+
rendering.cached_call(meth)
|
204
|
+
rendering.cached_call(meth)
|
205
|
+
|
206
|
+
expect(meth).to have_received(:call).once
|
207
|
+
end
|
208
|
+
|
209
|
+
it "the returned cached value is the same as the uncached one" do
|
210
|
+
meth = presenter.method(:meth)
|
211
|
+
|
212
|
+
first_outcome = rendering.cached_call(meth)
|
213
|
+
second_outcome = rendering.cached_call(meth)
|
214
|
+
|
215
|
+
expect(second_outcome).to eq first_outcome
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe "#call" do
|
220
|
+
it "calls with no arguments a method with no parameters" do
|
221
|
+
method = ->() { :return }
|
222
|
+
arguments = []
|
223
|
+
|
224
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
225
|
+
expect(output).to eq :return
|
226
|
+
end
|
227
|
+
|
228
|
+
it "calls with one argument a method with no parameters, discarding the parameter" do
|
229
|
+
method = ->() { :return }
|
230
|
+
arguments = [:argument]
|
231
|
+
|
232
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
233
|
+
expect(output).to eq :return
|
234
|
+
end
|
235
|
+
|
236
|
+
it "calls a method with only one parameter can only receive the options" do
|
237
|
+
method = ->(parameter) { parameter }
|
238
|
+
arguments = []
|
239
|
+
|
240
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
241
|
+
expect(output).to eq :options
|
242
|
+
end
|
243
|
+
|
244
|
+
it "calls a method with only one parameter can only receive the options, even with some arguments" do
|
245
|
+
method = ->(parameter) { parameter }
|
246
|
+
arguments = [:argument]
|
247
|
+
|
248
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
249
|
+
expect(output).to eq :options
|
250
|
+
end
|
251
|
+
|
252
|
+
it "calls a method with two parameter can receive nil as first argument and the options" do
|
253
|
+
method = ->(parameter, options) { [parameter, options] }
|
254
|
+
arguments = []
|
255
|
+
|
256
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
257
|
+
expect(output).to eq [nil, :options]
|
258
|
+
end
|
259
|
+
|
260
|
+
it "calls a method with two parameter can receive an argument and the options" do
|
261
|
+
method = ->(parameter, options) { [parameter, options] }
|
262
|
+
arguments = [:argument]
|
263
|
+
|
264
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
265
|
+
expect(output).to eq [:argument, :options]
|
266
|
+
end
|
267
|
+
|
268
|
+
it "calls a method with three parameter can receive two arguments and the options" do
|
269
|
+
method = ->(first, second, options) { [first, second, options] }
|
270
|
+
arguments = [:first, :second]
|
271
|
+
|
272
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
273
|
+
expect(output).to eq [:first, :second, :options]
|
274
|
+
end
|
275
|
+
|
276
|
+
it "calls a method with three parameter can receive one argument, nil and the options" do
|
277
|
+
method = ->(first, second, options) { [first, second, options] }
|
278
|
+
arguments = [:first]
|
279
|
+
|
280
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
281
|
+
expect(output).to eq [:first, nil, :options]
|
282
|
+
end
|
283
|
+
|
284
|
+
it "calls a method with three parameter can receive nil, nil and the options" do
|
285
|
+
method = ->(first, second, options) { [first, second, options] }
|
286
|
+
arguments = []
|
287
|
+
|
288
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
289
|
+
expect(output).to eq [nil, nil, :options]
|
290
|
+
end
|
291
|
+
|
292
|
+
it "calls a method passing an array as argument" do
|
293
|
+
method = ->(parameter, _) { parameter }
|
294
|
+
array = [1, 2, 3]
|
295
|
+
arguments = [array]
|
296
|
+
|
297
|
+
output = rendering.call(method, "method", position, arguments, :options, &block)
|
298
|
+
expect(output).to eq arguments.first
|
299
|
+
end
|
300
|
+
|
301
|
+
it "raises Curlybars::Error::Render if the helper has at least an optional parameter" do
|
302
|
+
method = ->(one, two = :optional) {}
|
303
|
+
arguments = [:arg1]
|
304
|
+
options = { key: :value }
|
305
|
+
|
306
|
+
expect do
|
307
|
+
rendering.call(method, "method", position, arguments, options, &block)
|
308
|
+
end.to raise_error(Curlybars::Error::Render)
|
309
|
+
end
|
310
|
+
|
311
|
+
it "raises Curlybars::Error::Render if the helper has at least a keyword parameter" do
|
312
|
+
method = ->(keyword:) {}
|
313
|
+
arguments = [:arg1]
|
314
|
+
options = { key: :value }
|
315
|
+
|
316
|
+
expect do
|
317
|
+
rendering.call(method, "method", position, arguments, options, &block)
|
318
|
+
end.to raise_error(Curlybars::Error::Render)
|
319
|
+
end
|
320
|
+
|
321
|
+
it "raises Curlybars::Error::Render if the helper has at least an optional keyword parameter" do
|
322
|
+
method = ->(keyword: :optional) {}
|
323
|
+
arguments = [:arg1]
|
324
|
+
options = { key: :value }
|
325
|
+
|
326
|
+
expect do
|
327
|
+
rendering.call(method, "meth", position, arguments, options, &block)
|
328
|
+
end.to raise_error(Curlybars::Error::Render)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
describe "#position" do
|
333
|
+
it "returns a position with file_name" do
|
334
|
+
position = rendering.position(0, 0)
|
335
|
+
expect(position.file_name).to eq file_name
|
336
|
+
end
|
337
|
+
|
338
|
+
it "returns a position with line_number" do
|
339
|
+
position = rendering.position(1, 0)
|
340
|
+
expect(position.line_number).to eq 1
|
341
|
+
end
|
342
|
+
|
343
|
+
it "returns a position with line_offset" do
|
344
|
+
position = rendering.position(0, 1)
|
345
|
+
expect(position.line_offset).to eq 1
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe "#check_context_is_hash_or_enum_of_presenters" do
|
350
|
+
it "doesn't raise an exception when argument is an empty enumerable" do
|
351
|
+
collection = []
|
352
|
+
rendering.check_context_is_hash_or_enum_of_presenters(collection, 'path', position)
|
353
|
+
end
|
354
|
+
|
355
|
+
it "doesn't raise an exception when argument is an empty hash" do
|
356
|
+
collection = {}
|
357
|
+
rendering.check_context_is_hash_or_enum_of_presenters(collection, nil, position)
|
358
|
+
end
|
359
|
+
|
360
|
+
it "doesn't raise an exception when argument is an enumerable of presenters" do
|
361
|
+
collection = [presenter]
|
362
|
+
rendering.check_context_is_hash_or_enum_of_presenters(collection, 'path', position)
|
363
|
+
end
|
364
|
+
|
365
|
+
it "doesn't raise an exception when argument is a hash of presenters" do
|
366
|
+
collection = { presenter: presenter }
|
367
|
+
rendering.check_context_is_hash_or_enum_of_presenters(collection, 'path', position)
|
368
|
+
end
|
369
|
+
|
370
|
+
it "raises when it is not an hash or an enumerable" do
|
371
|
+
expect do
|
372
|
+
rendering.check_context_is_hash_or_enum_of_presenters(:not_a_presenter, 'path', position)
|
373
|
+
end.to raise_error(Curlybars::Error::Render)
|
374
|
+
end
|
375
|
+
|
376
|
+
it "raises when it is not an hash or an enumerable of presenters" do
|
377
|
+
expect do
|
378
|
+
rendering.check_context_is_hash_or_enum_of_presenters([:not_a_presenter], 'path', position)
|
379
|
+
end.to raise_error(Curlybars::Error::Render)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
describe "#check_context_is_presenter" do
|
384
|
+
it "doesn't raise an exception when argument is a presenter" do
|
385
|
+
rendering.check_context_is_presenter(presenter, 'path', position)
|
386
|
+
end
|
387
|
+
|
388
|
+
it "raises when it is not a presenter" do
|
389
|
+
expect do
|
390
|
+
rendering.check_context_is_presenter(:not_a_presenter, 'path', position)
|
391
|
+
end.to raise_error(Curlybars::Error::Render)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe "#coerce_to_hash!" do
|
396
|
+
let(:a_presenter) { double(:a_presenter, allows_method?: true, meth: :value) }
|
397
|
+
let(:another_presenter) { double(:another_presenter, allows_method?: true, meth: :value) }
|
398
|
+
|
399
|
+
it "leaves hashes intacted" do
|
400
|
+
hash = { first: a_presenter }
|
401
|
+
expect(rendering.coerce_to_hash!(hash, 'path', position)).to be hash
|
402
|
+
end
|
403
|
+
|
404
|
+
it "transform an Array to a Hash" do
|
405
|
+
array = [a_presenter, another_presenter]
|
406
|
+
expected_hash = { 0 => a_presenter, 1 => another_presenter }
|
407
|
+
expect(rendering.coerce_to_hash!(array, 'path', position)).to eq expected_hash
|
408
|
+
end
|
409
|
+
|
410
|
+
it "raises when it is not a hash or an enumerable" do
|
411
|
+
expect do
|
412
|
+
rendering.coerce_to_hash!(:not_a_presenter, 'path', position)
|
413
|
+
end.to raise_error(Curlybars::Error::Render)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
private
|
418
|
+
|
419
|
+
def allow_all_methods(presenter)
|
420
|
+
allow(presenter).to receive(:allows_method?) { true }
|
421
|
+
end
|
422
|
+
|
423
|
+
def disallow_all_methods(presenter)
|
424
|
+
allow(presenter).to receive(:allows_method?) { false }
|
425
|
+
end
|
426
|
+
end
|