flavour_saver 0.3.3
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/.gitignore +16 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +81 -0
- data/Guardfile +12 -0
- data/LICENSE +22 -0
- data/README.md +339 -0
- data/Rakefile +2 -0
- data/flavour_saver.gemspec +28 -0
- data/lib/flavour_saver/helpers.rb +118 -0
- data/lib/flavour_saver/lexer.rb +127 -0
- data/lib/flavour_saver/nodes.rb +177 -0
- data/lib/flavour_saver/parser.rb +183 -0
- data/lib/flavour_saver/partial.rb +29 -0
- data/lib/flavour_saver/rails_partial.rb +10 -0
- data/lib/flavour_saver/runtime.rb +269 -0
- data/lib/flavour_saver/template.rb +19 -0
- data/lib/flavour_saver/version.rb +3 -0
- data/lib/flavour_saver.rb +78 -0
- data/spec/acceptance/backtrack_spec.rb +14 -0
- data/spec/acceptance/comment_spec.rb +12 -0
- data/spec/acceptance/custom_block_helper_spec.rb +35 -0
- data/spec/acceptance/custom_helper_spec.rb +15 -0
- data/spec/acceptance/ensure_no_rce_spec.rb +26 -0
- data/spec/acceptance/handlebars_qunit_spec.rb +911 -0
- data/spec/acceptance/if_else_spec.rb +17 -0
- data/spec/acceptance/multi_level_with_spec.rb +15 -0
- data/spec/acceptance/one_character_identifier_spec.rb +13 -0
- data/spec/acceptance/runtime_run_spec.rb +27 -0
- data/spec/acceptance/sections_spec.rb +25 -0
- data/spec/acceptance/segment_literals_spec.rb +26 -0
- data/spec/acceptance/simple_expression_spec.rb +13 -0
- data/spec/fixtures/backtrack.hbs +4 -0
- data/spec/fixtures/comment.hbs +1 -0
- data/spec/fixtures/custom_block_helper.hbs +3 -0
- data/spec/fixtures/custom_helper.hbs +1 -0
- data/spec/fixtures/if_else.hbs +5 -0
- data/spec/fixtures/multi_level_if.hbs +12 -0
- data/spec/fixtures/multi_level_with.hbs +11 -0
- data/spec/fixtures/one_character_identifier.hbs +1 -0
- data/spec/fixtures/sections.hbs +9 -0
- data/spec/fixtures/simple_expression.hbs +1 -0
- data/spec/lib/flavour_saver/lexer_spec.rb +187 -0
- data/spec/lib/flavour_saver/parser_spec.rb +277 -0
- data/spec/lib/flavour_saver/runtime_spec.rb +190 -0
- data/spec/lib/flavour_saver/template_spec.rb +5 -0
- metadata +243 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'tilt/template'
|
2
|
+
|
3
|
+
module FlavourSaver
|
4
|
+
class Template < Tilt::Template
|
5
|
+
|
6
|
+
def self.engine_initialized?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def prepare
|
11
|
+
@ast = Parser.parse(Lexer.lex(data))
|
12
|
+
end
|
13
|
+
|
14
|
+
def evaluate(scope=Object.new,locals={},&block)
|
15
|
+
Runtime.run(@ast,scope,locals)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "flavour_saver/version"
|
2
|
+
require 'tilt'
|
3
|
+
|
4
|
+
module FlavourSaver
|
5
|
+
|
6
|
+
autoload :Lexer, 'flavour_saver/lexer'
|
7
|
+
autoload :Parser, 'flavour_saver/parser'
|
8
|
+
autoload :Runtime, 'flavour_saver/runtime'
|
9
|
+
autoload :Helpers, 'flavour_saver/helpers'
|
10
|
+
autoload :Partial, 'flavour_saver/partial'
|
11
|
+
autoload :RailsPartial, 'flavour_saver/rails_partial'
|
12
|
+
autoload :Template, 'flavour_saver/template'
|
13
|
+
autoload :NodeCollection, 'flavour_saver/node_collection'
|
14
|
+
|
15
|
+
if defined? Rails
|
16
|
+
class Engine < Rails::Engine
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveSupport.on_load(:action_view) do
|
20
|
+
handler = proc do |template|
|
21
|
+
# I'd rather be caching the Runtime object ready to fire, but apparently I don't get that luxury.
|
22
|
+
<<-SOURCE
|
23
|
+
FlavourSaver.evaluate((begin;#{template.source.inspect};end),self)
|
24
|
+
SOURCE
|
25
|
+
end
|
26
|
+
ActionView::Template.register_template_handler(:hbs, handler)
|
27
|
+
ActionView::Template.register_template_handler(:handlebars, handler)
|
28
|
+
end
|
29
|
+
|
30
|
+
@default_logger = proc { Rails.logger }
|
31
|
+
@partial_handler = RailsPartial
|
32
|
+
else
|
33
|
+
@default_logger = proc { Logger.new }
|
34
|
+
@partial_handler = Partial
|
35
|
+
end
|
36
|
+
|
37
|
+
module_function
|
38
|
+
|
39
|
+
def lex(template)
|
40
|
+
Lexer.lex(template)
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse(tokens)
|
44
|
+
Parser.parse(tokens)
|
45
|
+
end
|
46
|
+
|
47
|
+
def evaluate(template,context)
|
48
|
+
Runtime.run(parse(lex(template)), context)
|
49
|
+
end
|
50
|
+
|
51
|
+
def register_helper(*args,&b)
|
52
|
+
Helpers.register_helper(*args,&b)
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset_helpers
|
56
|
+
Helpers.reset_helpers
|
57
|
+
end
|
58
|
+
|
59
|
+
def register_partial(name,content=nil,&block)
|
60
|
+
Partial.register_partial(name,content,&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def reset_partials
|
64
|
+
Partial.reset_partials
|
65
|
+
end
|
66
|
+
|
67
|
+
def logger
|
68
|
+
@logger || @default_logger.call
|
69
|
+
end
|
70
|
+
|
71
|
+
def logger=(logger)
|
72
|
+
@logger=logger
|
73
|
+
end
|
74
|
+
|
75
|
+
Tilt.register(Template, 'handlebars', 'hbs')
|
76
|
+
end
|
77
|
+
|
78
|
+
FS = FlavourSaver
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'flavour_saver'
|
3
|
+
|
4
|
+
describe 'Fixture: backtrack.hbs' do
|
5
|
+
subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
|
6
|
+
let(:template) { File.expand_path('../../fixtures/backtrack.hbs', __FILE__) }
|
7
|
+
let(:context) { Struct.new(:person,:company).new }
|
8
|
+
|
9
|
+
it 'renders correctly' do
|
10
|
+
context.person = Struct.new(:name).new('Alan')
|
11
|
+
context.company = Struct.new(:name).new('Rad, Inc.')
|
12
|
+
subject.should == "Alan - Rad, Inc."
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'flavour_saver'
|
3
|
+
|
4
|
+
describe 'Fixture: comment.hbs' do
|
5
|
+
subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
|
6
|
+
let(:template) { File.expand_path('../../fixtures/comment.hbs', __FILE__) }
|
7
|
+
let(:context) { double(:context) }
|
8
|
+
|
9
|
+
it 'renders correctly' do
|
10
|
+
subject.should == "I am a very nice person!"
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'flavour_saver'
|
3
|
+
|
4
|
+
describe 'Fixture: custom_block_helper.hbs' do
|
5
|
+
subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
|
6
|
+
let(:template) { File.expand_path('../../fixtures/custom_block_helper.hbs', __FILE__) }
|
7
|
+
let(:context) { double(:context) }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
FlavourSaver::Helpers.reset_helpers
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'method helper' do
|
14
|
+
it 'renders correctly' do
|
15
|
+
def three_times
|
16
|
+
(1..3).map do |i|
|
17
|
+
yield.contents i
|
18
|
+
end.join ''
|
19
|
+
end
|
20
|
+
FlavourSaver.register_helper(method(:three_times))
|
21
|
+
subject.should == "1 time. 2 time. 3 time."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'proc helper' do
|
26
|
+
it 'renders correctly' do
|
27
|
+
FlavourSaver.register_helper(:three_times) { |&b|
|
28
|
+
(1..3).map do |i|
|
29
|
+
b.call.contents i
|
30
|
+
end.join ''
|
31
|
+
}
|
32
|
+
subject.should == "1 time. 2 time. 3 time."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'flavour_saver'
|
3
|
+
|
4
|
+
describe 'Fixture: custom_helper.hbs' do
|
5
|
+
subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
|
6
|
+
let(:template) { File.expand_path('../../fixtures/custom_helper.hbs', __FILE__) }
|
7
|
+
let(:context) { double(:context) }
|
8
|
+
|
9
|
+
it 'renders correctly' do
|
10
|
+
FlavourSaver.register_helper(:say_what_again) do
|
11
|
+
'What?'
|
12
|
+
end
|
13
|
+
subject.should == "What?"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'flavour_saver'
|
3
|
+
|
4
|
+
describe "Can't call methods that the context doesn't respond to" do
|
5
|
+
subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
|
6
|
+
let(:template) { '{{system "ls"}}' }
|
7
|
+
let(:context) { double(:context) }
|
8
|
+
|
9
|
+
it 'renders correctly' do
|
10
|
+
expect(Kernel).not_to receive(:system)
|
11
|
+
expect { subject }.to raise_error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "Can't eval arbitrary Ruby code" do
|
16
|
+
subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
|
17
|
+
let(:template) { '{{eval "puts 1 + 1"}}' }
|
18
|
+
let(:context) { double(:context) }
|
19
|
+
|
20
|
+
it 'renders correctly' do
|
21
|
+
expect(Kernel).not_to receive(:eval)
|
22
|
+
expect { subject }.to raise_error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,911 @@
|
|
1
|
+
# These are the original Handlebars.js qunit acceptance tests, ported
|
2
|
+
# to run against FlavourSaver. Yes, this is a more brittle way of
|
3
|
+
# doing it.
|
4
|
+
|
5
|
+
require 'active_support'
|
6
|
+
ActiveSupport::SafeBuffer
|
7
|
+
|
8
|
+
require 'flavour_saver'
|
9
|
+
|
10
|
+
describe FlavourSaver do
|
11
|
+
let(:context) { double(:context) }
|
12
|
+
subject { FS.evaluate(template, context) }
|
13
|
+
after do
|
14
|
+
FS.reset_helpers
|
15
|
+
FS.reset_partials
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "basic context" do
|
19
|
+
before { FS.register_helper(:link_to) { "<a>#{context}</a>" } }
|
20
|
+
|
21
|
+
describe 'most basic' do
|
22
|
+
let(:template) { "{{foo}}" }
|
23
|
+
|
24
|
+
it 'returns "foo"' do
|
25
|
+
context.stub(:foo).and_return('foo')
|
26
|
+
subject.should == 'foo'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'compiling with a basic context' do
|
31
|
+
let(:template) { "Goodbye\n{{cruel}}\n{{world}}!" }
|
32
|
+
|
33
|
+
it 'it works if all the required keys are provided' do
|
34
|
+
context.should_receive(:cruel).and_return('cruel')
|
35
|
+
context.should_receive(:world).and_return('world')
|
36
|
+
subject.should == "Goodbye\ncruel\nworld!"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'comments' do
|
41
|
+
let(:template) {"{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!"}
|
42
|
+
|
43
|
+
it 'comments are ignored' do
|
44
|
+
context.should_receive(:cruel).and_return('cruel')
|
45
|
+
context.should_receive(:world).and_return('world')
|
46
|
+
subject.should == "Goodbye\ncruel\nworld!"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'boolean' do
|
51
|
+
let(:template) { "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!" }
|
52
|
+
|
53
|
+
it 'booleans show the contents when true' do
|
54
|
+
context.stub(:goodbye).and_return(true)
|
55
|
+
context.stub(:world).and_return('world')
|
56
|
+
subject.should == "GOODBYE cruel world!"
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'booleans do not show the contents when false' do
|
60
|
+
context.stub(:goodbye).and_return(false)
|
61
|
+
context.stub(:world).and_return('world')
|
62
|
+
subject.should == "cruel world!"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'zeros' do
|
67
|
+
describe '{num1: 42, num2: 0}' do
|
68
|
+
let (:template) { "num1: {{num1}}, num2: {{num2}}" }
|
69
|
+
|
70
|
+
it 'should compile to "num1: 42, num2: 0"' do
|
71
|
+
context.stub(:num1).and_return(42)
|
72
|
+
context.stub(:num2).and_return(0)
|
73
|
+
subject.should == 'num1: 42, num2: 0'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 0 do
|
78
|
+
let (:template) { 'num: {{.}}' }
|
79
|
+
|
80
|
+
it 'should compile to "num: 0"' do
|
81
|
+
FlavourSaver.evaluate(template,0).should == 'num: 0'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '{num1: {num2: 0}}' do
|
86
|
+
let(:template) { 'num: {{num1/num2}}' }
|
87
|
+
|
88
|
+
it 'should compile to "num: 0"' do
|
89
|
+
context.stub_chain(:num1, :num2).and_return(0)
|
90
|
+
subject.should == 'num: 0'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'newlines' do
|
96
|
+
describe '\n' do
|
97
|
+
let(:template) { "Alan's\nTest" }
|
98
|
+
|
99
|
+
it 'works' do
|
100
|
+
subject.should == "Alan's\nTest"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '\r' do
|
105
|
+
let(:template) { "Alan's\rTest" }
|
106
|
+
|
107
|
+
it 'works' do
|
108
|
+
subject.should == "Alan's\rTest"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'esaping text' do
|
114
|
+
describe 'apostrophes' do
|
115
|
+
let(:template) {"Awesome's"}
|
116
|
+
|
117
|
+
it "text is escapes so that it doesn't get caught in single quites" do
|
118
|
+
subject.should == "Awesome's"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'backslashes' do
|
123
|
+
let(:template) { "Awesome \\" }
|
124
|
+
|
125
|
+
it "text is escaped so that the closing quote can't be ignored" do
|
126
|
+
subject.should == "Awesome \\"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'more backslashes' do
|
131
|
+
let(:template) { "Awesome\\\\ foo" }
|
132
|
+
|
133
|
+
it "text is escapes so that it doesn't mess up the backslashes" do
|
134
|
+
subject.should == "Awesome\\\\ foo"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'helper output containing backslashes' do
|
139
|
+
let(:template) { "Awesome {{foo}}" }
|
140
|
+
|
141
|
+
it "text is escaped so that it doesn't mess up backslashes" do
|
142
|
+
context.stub(:foo).and_return('\\')
|
143
|
+
subject.should == "Awesome \\"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe 'doubled quotes' do
|
148
|
+
let(:template) { ' " " ' }
|
149
|
+
|
150
|
+
it "double quotes never produce invalid javascript" do
|
151
|
+
subject.should == ' " " '
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe 'escaping expressions' do
|
157
|
+
describe 'expressions with 3 handlebars' do
|
158
|
+
let(:template) { "{{{awesome}}}" }
|
159
|
+
|
160
|
+
it "shouldn't be escaped" do
|
161
|
+
context.stub(:awesome).and_return("&\"\\<>")
|
162
|
+
subject.should == "&\"\\<>"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe 'expressions with {{& handlebars' do
|
167
|
+
let(:template) { "{{&awesome}}" }
|
168
|
+
|
169
|
+
it "shouldn't be escaped" do
|
170
|
+
context.stub(:awesome).and_return("&\"\\<>")
|
171
|
+
subject.should == "&\"\\<>"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe 'expressions' do
|
176
|
+
let(:template) { "{{awesome}}" }
|
177
|
+
|
178
|
+
it "should be escaped" do
|
179
|
+
context.stub(:awesome).and_return("&\"'`\\<>")
|
180
|
+
if RUBY_VERSION == '2.0.0'
|
181
|
+
subject.should == "&"'`\\<>"
|
182
|
+
else
|
183
|
+
subject.should == "&"'`\\<>"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe 'ampersands' do
|
189
|
+
let(:template) { "{{awesome}}" }
|
190
|
+
|
191
|
+
it "should be escaped" do
|
192
|
+
context.stub(:awesome).and_return("Escaped, <b> looks like: <b>")
|
193
|
+
subject.should == "Escaped, <b> looks like: &lt;b&gt;"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "functions returning safe strings" do
|
199
|
+
let(:template) { "{{awesome}}" }
|
200
|
+
|
201
|
+
it "shouldn't be escaped" do
|
202
|
+
context.stub(:awesome).and_return("&\"\\<>".html_safe)
|
203
|
+
subject.should == "&\"\\<>"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe 'functions' do
|
208
|
+
let(:template) { "{{awesome}}" }
|
209
|
+
|
210
|
+
it "are called and render their output" do
|
211
|
+
context.stub(:awesome).and_return("Awesome")
|
212
|
+
subject.should == "Awesome"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe 'paths with hyphens' do
|
217
|
+
describe '{{foo-bar}}' do
|
218
|
+
let(:template) { "{{foo-bar}}" }
|
219
|
+
|
220
|
+
it 'paths can contain hyphens (-)' do
|
221
|
+
context.should_receive(:[]).with('foo-bar').and_return('baz')
|
222
|
+
subject.should == 'baz'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe '{{foo.foo-bar}}' do
|
227
|
+
let(:template) { "{{foo.foo-bar}}" }
|
228
|
+
|
229
|
+
it 'paths can contain hyphens (-)' do
|
230
|
+
context.stub_chain(:foo, :[]).with('foo-bar').and_return(proc { 'baz' })
|
231
|
+
subject.should == 'baz'
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe '{{foo/foo-bar}}' do
|
236
|
+
let(:template) { "{{foo/foo-bar}}" }
|
237
|
+
|
238
|
+
it 'paths can contain hyphens (-)' do
|
239
|
+
context.stub_chain(:foo, :[]).with('foo-bar').and_return('baz')
|
240
|
+
subject.should == 'baz'
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'nested paths' do
|
245
|
+
let(:template) {"Goodbye {{alan/expression}} world!"}
|
246
|
+
|
247
|
+
it 'access nested object' do
|
248
|
+
context.stub_chain(:alan, :expression).and_return('beautiful')
|
249
|
+
subject.should == 'Goodbye beautiful world!'
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
describe 'nested path with empty string value' do
|
254
|
+
let(:template) {"Goodbye {{alan/expression}} world!"}
|
255
|
+
|
256
|
+
it 'access nested object' do
|
257
|
+
context.stub_chain(:alan, :expression).and_return('')
|
258
|
+
subject.should == 'Goodbye world!'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe 'literal paths' do
|
263
|
+
let(:template) { "Goodbye {{[@alan]/expression}} world!" }
|
264
|
+
|
265
|
+
it 'literal paths can be used' do
|
266
|
+
alan = double(:alan)
|
267
|
+
context.should_receive(:[]).with('@alan').and_return(alan)
|
268
|
+
alan.should_receive(:expression).and_return('beautiful')
|
269
|
+
subject.should == 'Goodbye beautiful world!'
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe 'complex but empty paths' do
|
274
|
+
let(:template) { '{{person/name}}' }
|
275
|
+
|
276
|
+
it 'returns empty string from nested paths' do
|
277
|
+
context.stub_chain(:person,:name).and_return('')
|
278
|
+
subject.should == ''
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'returns empty string from nil objects' do
|
282
|
+
context.stub_chain(:person,:name)
|
283
|
+
subject.should == ''
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe '"this" keyword' do
|
288
|
+
describe 'in a block' do
|
289
|
+
let(:template) { "{{#goodbyes}}{{this}}{{/goodbyes}}" }
|
290
|
+
|
291
|
+
it 'evaluates to the current context' do
|
292
|
+
context.stub(:goodbyes).and_return(["goodbye", "Goodbye", "GOODBYE"])
|
293
|
+
subject.should == "goodbyeGoodbyeGOODBYE"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
describe 'in a block in a path' do
|
298
|
+
let(:template) { "{{#hellos}}{{this/text}}{{/hellos}}" }
|
299
|
+
|
300
|
+
it 'evaluates in more complex paths' do
|
301
|
+
hellos = []
|
302
|
+
hellos << double(:hello)
|
303
|
+
hellos[0].should_receive(:text).and_return('hello')
|
304
|
+
hellos << double(:Hello)
|
305
|
+
hellos[1].should_receive(:text).and_return('Hello')
|
306
|
+
hellos << double(:HELLO)
|
307
|
+
hellos[2].should_receive(:text).and_return('HELLO')
|
308
|
+
context.stub(:hellos).and_return(hellos)
|
309
|
+
subject.should == "helloHelloHELLO"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe 'this keyword in helpers' do
|
315
|
+
before { FS.register_helper(:foo) { |value| "bar #{value}" } }
|
316
|
+
|
317
|
+
describe 'this keyword in arguments' do
|
318
|
+
let(:template) { "{{#goodbyes}}{{foo this}}{{/goodbyes}}" }
|
319
|
+
|
320
|
+
it 'evaluates to current context' do
|
321
|
+
context.stub(:goodbyes).and_return(["goodbye", "Goodbye", "GOODBYE"])
|
322
|
+
subject.should == "bar goodbyebar Goodbyebar GOODBYE"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe 'this keyword in object path arguments' do
|
327
|
+
let(:template) { "{{#hellos}}{{foo this/text}}{{/hellos}}" }
|
328
|
+
|
329
|
+
it 'evaluates to current context' do
|
330
|
+
hellos = []
|
331
|
+
hellos << double(:hello)
|
332
|
+
hellos[0].should_receive(:text).and_return('hello')
|
333
|
+
hellos << double(:Hello)
|
334
|
+
hellos[1].should_receive(:text).and_return('Hello')
|
335
|
+
hellos << double(:HELLO)
|
336
|
+
hellos[2].should_receive(:text).and_return('HELLO')
|
337
|
+
context.stub(:hellos).and_return(hellos)
|
338
|
+
subject.should == "bar hellobar Hellobar HELLO"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
describe 'Inverted sections' do
|
346
|
+
let(:template) { "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}" }
|
347
|
+
|
348
|
+
describe 'with unset value' do
|
349
|
+
it 'renders' do
|
350
|
+
context.stub(:goodbyes)
|
351
|
+
subject.should == 'Right On!'
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe 'with false value' do
|
356
|
+
it 'renders' do
|
357
|
+
context.stub(:goodbyes).and_return(false)
|
358
|
+
subject.should == 'Right On!'
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
describe 'with an empty set' do
|
363
|
+
it 'renders' do
|
364
|
+
context.stub(:goodbyes).and_return([])
|
365
|
+
subject.should == 'Right On!'
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
describe 'Blocks' do
|
371
|
+
let(:template) { "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!" }
|
372
|
+
|
373
|
+
it 'arrays iterate the contents with non-empty' do
|
374
|
+
goodbyes = []
|
375
|
+
goodbyes << double(:goodbye)
|
376
|
+
goodbyes[0].should_receive(:text).and_return('goodbye')
|
377
|
+
goodbyes << double(:Goodbye)
|
378
|
+
goodbyes[1].should_receive(:text).and_return('Goodbye')
|
379
|
+
goodbyes << double(:GOODBYE)
|
380
|
+
goodbyes[2].should_receive(:text).and_return('GOODBYE')
|
381
|
+
context.stub(:goodbyes).and_return(goodbyes)
|
382
|
+
context.stub(:world).and_return('world')
|
383
|
+
subject.should == "goodbye! Goodbye! GOODBYE! cruel world!"
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'ignores the contents when the array is empty' do
|
387
|
+
context.stub(:goodbyes).and_return([])
|
388
|
+
context.stub(:world).and_return('world')
|
389
|
+
subject.should == "cruel world!"
|
390
|
+
end
|
391
|
+
|
392
|
+
describe 'array with @index' do
|
393
|
+
let(:template) {"{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!"}
|
394
|
+
|
395
|
+
it 'the @index variable is used' do
|
396
|
+
goodbyes = []
|
397
|
+
goodbyes << double(:goodbye)
|
398
|
+
goodbyes[0].should_receive(:text).and_return('goodbye')
|
399
|
+
goodbyes << double(:Goodbye)
|
400
|
+
goodbyes[1].should_receive(:text).and_return('Goodbye')
|
401
|
+
goodbyes << double(:GOODBYE)
|
402
|
+
goodbyes[2].should_receive(:text).and_return('GOODBYE')
|
403
|
+
context.stub(:goodbyes).and_return(goodbyes)
|
404
|
+
context.stub(:world).and_return('world')
|
405
|
+
subject.should == "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
describe 'empty block' do
|
410
|
+
let(:template) { "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!" }
|
411
|
+
|
412
|
+
it 'arrays iterate the contents with non-empty' do
|
413
|
+
goodbyes = []
|
414
|
+
goodbyes << double(:goodbye)
|
415
|
+
goodbyes[0].stub(:text).and_return('goodbye')
|
416
|
+
goodbyes << double(:Goodbye)
|
417
|
+
goodbyes[1].stub(:text).and_return('Goodbye')
|
418
|
+
goodbyes << double(:GOODBYE)
|
419
|
+
goodbyes[2].stub(:text).and_return('GOODBYE')
|
420
|
+
context.stub(:goodbyes).and_return(goodbyes)
|
421
|
+
context.stub(:world).and_return('world')
|
422
|
+
subject.should == "cruel world!"
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'ignores the contents when the array is empty' do
|
426
|
+
context.stub(:goodbyes).and_return([])
|
427
|
+
context.stub(:world).and_return('world')
|
428
|
+
subject.should == "cruel world!"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
describe 'nested iteration'
|
433
|
+
|
434
|
+
describe 'block with complex lookup' do
|
435
|
+
let(:template) {"{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}"}
|
436
|
+
|
437
|
+
it 'templates can access variables in contexts up the stack with relative path syntax' do
|
438
|
+
context.stub(:name).and_return('Alan')
|
439
|
+
goodbyes = []
|
440
|
+
goodbyes << double(:goodbye)
|
441
|
+
goodbyes[0].should_receive(:text).and_return('goodbye')
|
442
|
+
goodbyes << double(:Goodbye)
|
443
|
+
goodbyes[1].should_receive(:text).and_return('Goodbye')
|
444
|
+
goodbyes << double(:GOODBYE)
|
445
|
+
goodbyes[2].should_receive(:text).and_return('GOODBYE')
|
446
|
+
context.stub(:goodbyes).and_return(goodbyes)
|
447
|
+
subject.should == "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! "
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
describe 'helper with complex lookup' do
|
452
|
+
let(:template) {"{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"}
|
453
|
+
before do
|
454
|
+
FS.register_helper(:link) do |prefix|
|
455
|
+
"<a href='#{prefix}/#{url}'>#{text}</a>"
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
it 'renders correctly' do
|
460
|
+
context.stub(:prefix).and_return('/root')
|
461
|
+
goodbyes = []
|
462
|
+
goodbyes << double(:Goodbye)
|
463
|
+
goodbyes[0].should_receive(:text).and_return('Goodbye')
|
464
|
+
goodbyes[0].should_receive(:url).and_return('goodbye')
|
465
|
+
context.stub(:goodbyes).and_return(goodbyes)
|
466
|
+
subject.should == "<a href='/root/goodbye'>Goodbye</a>"
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
describe 'helper with complex lookup expression' do
|
471
|
+
let(:template) { "{{#goodbyes}}{{../name}}{{/goodbyes}}" }
|
472
|
+
before do
|
473
|
+
FS.register_helper(:goodbyes) do |&b|
|
474
|
+
["Goodbye", "goodbye", "GOODBYE"].map do |bye|
|
475
|
+
"#{bye} #{b.call.contents}! "
|
476
|
+
end.join('')
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'renders correctly' do
|
481
|
+
context.stub(:name).and_return('Alan')
|
482
|
+
subject.should == "Goodbye Alan! goodbye Alan! GOODBYE Alan! "
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
describe 'helper with complex lookup and nested template' do
|
487
|
+
let(:template) { "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}" }
|
488
|
+
before do
|
489
|
+
FS.register_helper(:link) do |prefix,&b|
|
490
|
+
"<a href='#{prefix}/#{url}'>#{b.call.contents}</a>"
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
it 'renders correctly' do
|
495
|
+
context.stub(:prefix).and_return('/root')
|
496
|
+
goodbye = double(:goodbye)
|
497
|
+
goodbye.stub(:text).and_return('Goodbye')
|
498
|
+
goodbye.stub(:url).and_return('goodbye')
|
499
|
+
context.stub(:goodbyes).and_return([goodbye])
|
500
|
+
subject.should == "<a href='/root/goodbye'>Goodbye</a>"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
describe 'block with deep nested complex lookup' do
|
505
|
+
let(:template) { "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}" }
|
506
|
+
|
507
|
+
example do
|
508
|
+
goodbye = double(:goodbye)
|
509
|
+
goodbye.stub(:text).and_return('goodbye')
|
510
|
+
inner = double(:inner)
|
511
|
+
inner.stub(:inner).and_return([goodbye])
|
512
|
+
context.stub(:omg).and_return('OMG!')
|
513
|
+
context.stub(:outer).and_return([inner])
|
514
|
+
subject.should == "Goodbye cruel OMG!"
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
describe 'block helper' do
|
519
|
+
let(:template) { "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!" }
|
520
|
+
before do
|
521
|
+
FS.register_helper(:goodbyes) do |&block|
|
522
|
+
block.call.contents Struct.new(:text).new('GOODBYE')
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
example do
|
527
|
+
context.stub(:world).and_return('world')
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
describe 'block helper staying in the same context' do
|
532
|
+
let(:template) { "{{#form}}<p>{{name}}</p>{{/form}}" }
|
533
|
+
before do
|
534
|
+
FS.register_helper(:form) do |&block|
|
535
|
+
"<form>#{block.call.contents}</form>"
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
example do
|
540
|
+
context.stub(:name).and_return('Yehuda')
|
541
|
+
subject.should == "<form><p>Yehuda</p></form>"
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
describe 'block helper should have context in this' do
|
546
|
+
let(:template) { "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>" }
|
547
|
+
before do
|
548
|
+
FS.register_helper(:link) do |&block|
|
549
|
+
"<a href=\"/people/#{this.id}\">#{block.call.contents}</a>"
|
550
|
+
end
|
551
|
+
end
|
552
|
+
example do
|
553
|
+
person = Struct.new(:name, :id)
|
554
|
+
context.stub(:people).and_return([person.new('Alan', 1), person.new('Yehuda', 2)])
|
555
|
+
subject.should == "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
describe 'block helper for undefined value' do
|
560
|
+
let(:template) { "{{#empty}}shoulnd't render{{/empty}}" }
|
561
|
+
example do
|
562
|
+
subject.should == ""
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
describe 'block helper passing a new context' do
|
567
|
+
let(:template) { "{{#form yehuda}}<p>{{name}}</p>{{/form}}" }
|
568
|
+
before do
|
569
|
+
FS.register_helper(:form) do |whom,&block|
|
570
|
+
"<form>#{block.call.contents whom}</form>"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
example do
|
574
|
+
context.stub_chain(:yehuda,:name).and_return('Yehuda')
|
575
|
+
subject.should == "<form><p>Yehuda</p></form>"
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
describe 'block helper passing a complex path context' do
|
580
|
+
let(:template) { "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}" }
|
581
|
+
before do
|
582
|
+
FS.register_helper(:form) do |context,&block|
|
583
|
+
"<form>#{block.call.contents context}</form>"
|
584
|
+
end
|
585
|
+
end
|
586
|
+
example do
|
587
|
+
yehuda = double(:yehuda)
|
588
|
+
yehuda.stub(:name).and_return('Yehuda')
|
589
|
+
yehuda.stub_chain(:cat,:name).and_return('Harold')
|
590
|
+
context.stub(:yehuda).and_return(yehuda)
|
591
|
+
subject.should == "<form><p>Harold</p></form>"
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
describe 'nested block helpers' do
|
596
|
+
let(:template) { "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}" }
|
597
|
+
before do
|
598
|
+
FS.register_helper(:link) do |&block|
|
599
|
+
"<a href='#{name}'>#{block.call.contents}</a>"
|
600
|
+
end
|
601
|
+
FS.register_helper(:form) do |context,&block|
|
602
|
+
"<form>#{block.call.contents context}</form>"
|
603
|
+
end
|
604
|
+
end
|
605
|
+
example do
|
606
|
+
context.stub_chain(:yehuda,:name).and_return('Yehuda')
|
607
|
+
subject.should == "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>"
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
describe 'block inverted sections' do
|
612
|
+
let(:template) { "{{#people}}{{name}}{{^}}{{none}}{{/people}}" }
|
613
|
+
example do
|
614
|
+
context.stub(:none).and_return("No people")
|
615
|
+
subject.should == "No people"
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
describe 'block inverted sections with empty arrays' do
|
620
|
+
let(:template) { "{{#people}}{{name}}{{^}}{{none}}{{/people}}" }
|
621
|
+
example do
|
622
|
+
context.stub(:none).and_return('No people')
|
623
|
+
context.stub(:people).and_return([])
|
624
|
+
subject.should == "No people"
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
describe 'block helpers with inverted sections' do
|
629
|
+
let (:template) { "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}" }
|
630
|
+
before do
|
631
|
+
FS.register_helper(:list) do |context,&block|
|
632
|
+
if context.any?
|
633
|
+
"<ul>" +
|
634
|
+
context.map { |e| "<li>#{block.call.contents e}</li>" }.join('') +
|
635
|
+
"</ul>"
|
636
|
+
else
|
637
|
+
"<p>#{block.call.inverse}</p>"
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
example 'an inverse wrapper is passed in as a new context' do
|
643
|
+
person = Struct.new(:name)
|
644
|
+
context.stub(:people).and_return([person.new('Alan'),person.new('Yehuda')])
|
645
|
+
subject.should == "<ul><li>Alan</li><li>Yehuda</li></ul>"
|
646
|
+
end
|
647
|
+
|
648
|
+
example 'an inverse wrapper can optionally be called' do
|
649
|
+
context.stub(:people).and_return([])
|
650
|
+
subject.should == "<p><em>Nobody's here</em></p>"
|
651
|
+
end
|
652
|
+
|
653
|
+
describe 'the context of an inverse is the parent of the block' do
|
654
|
+
let(:template) { "{{#list people}}Hello{{^}}{{message}}{{/list}}" }
|
655
|
+
example do
|
656
|
+
context.stub(:people).and_return([])
|
657
|
+
context.stub(:message).and_return("Nobody's here")
|
658
|
+
if RUBY_VERSION == '2.0.0'
|
659
|
+
subject.should == "<p>Nobody's here</p>"
|
660
|
+
else
|
661
|
+
subject.should == "<p>Nobody's here</p>"
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
describe 'partials' do
|
669
|
+
let(:template) { "Dudes: {{#dudes}}{{> dude}}{{/dudes}}" }
|
670
|
+
before do
|
671
|
+
FS.register_partial(:dude, "{{name}} ({{url}}) ")
|
672
|
+
end
|
673
|
+
example do
|
674
|
+
person = Struct.new(:name, :url)
|
675
|
+
context.stub(:dudes).and_return([person.new('Yehuda', 'http://yehuda'), person.new('Alan', 'http://alan')])
|
676
|
+
subject.should == "Dudes: Yehuda (http://yehuda) Alan (http://alan) "
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
describe 'partials with context' do
|
681
|
+
let(:template) {"Dudes: {{>dude dudes}}"}
|
682
|
+
before do
|
683
|
+
FS.register_partial(:dude, "{{#this}}{{name}} ({{url}}) {{/this}}")
|
684
|
+
end
|
685
|
+
example "Partials can be passed a context" do
|
686
|
+
person = Struct.new(:name, :url)
|
687
|
+
context.stub(:dudes).and_return([person.new('Yehuda', 'http://yehuda'), person.new('Alan', 'http://alan')])
|
688
|
+
subject.should == "Dudes: Yehuda (http://yehuda) Alan (http://alan) "
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
describe 'partial in a partial' do
|
693
|
+
let(:template) {"Dudes: {{#dudes}}{{>dude}}{{/dudes}}"}
|
694
|
+
before do
|
695
|
+
FS.register_partial(:dude, "{{name}} {{>url}} ")
|
696
|
+
FS.register_partial(:url, "<a href='{{url}}'>{{url}}</a>")
|
697
|
+
end
|
698
|
+
example "Partials can be passed a context" do
|
699
|
+
person = Struct.new(:name, :url)
|
700
|
+
context.stub(:dudes).and_return([person.new('Yehuda', 'http://yehuda'), person.new('Alan', 'http://alan')])
|
701
|
+
subject.should == "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> "
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
describe 'rendering undefined partial throws an exception' do
|
706
|
+
let(:template) { "{{> whatever}}" }
|
707
|
+
example do
|
708
|
+
-> { subject }.should raise_error(FS::UnknownPartialException)
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
describe 'rendering a function partial' do
|
713
|
+
let(:template) { "Dudes: {{#dudes}}{{> dude}}{{/dudes}}" }
|
714
|
+
before do
|
715
|
+
FS.register_partial(:dude) do |context|
|
716
|
+
"#{context.name} (#{context.url}) "
|
717
|
+
end
|
718
|
+
end
|
719
|
+
example do
|
720
|
+
person = Struct.new(:name, :url)
|
721
|
+
context.stub(:dudes).and_return([person.new('Yehuda', 'http://yehuda'), person.new('Alan', 'http://alan')])
|
722
|
+
subject.should == "Dudes: Yehuda (http://yehuda) Alan (http://alan) "
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
describe 'a partial preceding a selector' do
|
727
|
+
let(:template) { "Dudes: {{>dude}} {{another_dude}}" }
|
728
|
+
before do
|
729
|
+
FS.register_partial(:dude, "{{name}}")
|
730
|
+
end
|
731
|
+
example do
|
732
|
+
context.stub(:name).and_return('Jeepers')
|
733
|
+
context.stub(:another_dude).and_return('Creepers')
|
734
|
+
subject.should == "Dudes: Jeepers Creepers"
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
describe 'partials with literal paths' do
|
739
|
+
let(:template) { "Dudes: {{> [dude]}}" }
|
740
|
+
before do
|
741
|
+
FS.register_partial(:dude, "{{name}}")
|
742
|
+
end
|
743
|
+
example do
|
744
|
+
context.stub(:name).and_return('Jeepers')
|
745
|
+
context.stub(:another_dude).and_return('Creepers')
|
746
|
+
subject.should == "Dudes: Jeepers"
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
describe 'string literal parameters' do
|
751
|
+
|
752
|
+
describe 'simple literals work' do
|
753
|
+
let(:template) { "Message: {{hello \"world\" 12 true false}}" }
|
754
|
+
before do
|
755
|
+
FS.register_helper(:hello) do |param,times,bool1,bool2|
|
756
|
+
times = "NaN" unless times.is_a? Fixnum
|
757
|
+
bool1 = "NaB" unless bool1 == true
|
758
|
+
bool2 = "NaB" unless bool2 == false
|
759
|
+
"Hello #{param} #{times} times: #{bool1} #{bool2}"
|
760
|
+
end
|
761
|
+
end
|
762
|
+
example do
|
763
|
+
subject.should == "Message: Hello world 12 times: true false"
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
describe 'using a quote in the middle of a parameter raises an error' do
|
768
|
+
let(:template) { "Message: {{hello wo\"rld\"}}" }
|
769
|
+
example do
|
770
|
+
-> { subject }.should raise_error
|
771
|
+
end
|
772
|
+
end
|
773
|
+
|
774
|
+
describe 'escaping a string is possible' do
|
775
|
+
let(:template) { 'Message: {{{hello "\"world\""}}}' }
|
776
|
+
before do
|
777
|
+
FS.register_helper(:hello) do |param|
|
778
|
+
"Hello #{param}"
|
779
|
+
end
|
780
|
+
end
|
781
|
+
example do
|
782
|
+
subject.should == 'Message: Hello \"world\"'
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
describe 'string work with ticks' do
|
787
|
+
let(:template) { 'Message: {{{hello "Alan\'s world"}}}' }
|
788
|
+
before do
|
789
|
+
FS.register_helper(:hello) do |param|
|
790
|
+
"Hello #{param}"
|
791
|
+
end
|
792
|
+
end
|
793
|
+
example do
|
794
|
+
subject.should == "Message: Hello Alan's world"
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
798
|
+
end
|
799
|
+
|
800
|
+
describe 'multi-params' do
|
801
|
+
describe 'simple multi-params work' do
|
802
|
+
let(:template) { "Message: {{goodbye cruel world}}" }
|
803
|
+
before { FS.register_helper(:goodbye) { |cruel,world| "Goodbye #{cruel} #{world}" } }
|
804
|
+
example do
|
805
|
+
context.stub(:cruel).and_return('cruel')
|
806
|
+
context.stub(:world).and_return('world')
|
807
|
+
subject.should == "Message: Goodbye cruel world"
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
describe 'block multi-params' do
|
812
|
+
let(:template) { "Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}" }
|
813
|
+
before { FS.register_helper(:goodbye) { |adj,noun,&b| b.call.contents Struct.new(:greeting,:adj,:noun).new('Goodbye', adj, noun) } }
|
814
|
+
example do
|
815
|
+
context.stub(:cruel).and_return('cruel')
|
816
|
+
context.stub(:world).and_return('world')
|
817
|
+
subject.should == "Message: Goodbye cruel world"
|
818
|
+
end
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
describe 'built-in helpers' do
|
823
|
+
describe 'with' do
|
824
|
+
let(:template) { "{{#with person}}{{first}} {{last}}{{/with}}" }
|
825
|
+
example do
|
826
|
+
context.stub(:person).and_return(Struct.new(:first,:last).new('Alan','Johnson'))
|
827
|
+
subject.should == 'Alan Johnson'
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
describe 'if' do
|
832
|
+
let(:template) { "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!" }
|
833
|
+
|
834
|
+
example 'if with boolean argument shows the contents when true' do
|
835
|
+
context.stub(:goodbye).and_return(true)
|
836
|
+
context.stub(:world).and_return('world')
|
837
|
+
subject.should == "GOODBYE cruel world!"
|
838
|
+
end
|
839
|
+
|
840
|
+
example 'if with string argument shows the contents with true' do
|
841
|
+
context.stub(:goodbye).and_return('dummy')
|
842
|
+
context.stub(:world).and_return('world')
|
843
|
+
subject.should == "GOODBYE cruel world!"
|
844
|
+
end
|
845
|
+
|
846
|
+
example 'if with boolean argument does not show the contents when false' do
|
847
|
+
context.stub(:goodbye).and_return(false)
|
848
|
+
context.stub(:world).and_return('world')
|
849
|
+
subject.should == "cruel world!"
|
850
|
+
end
|
851
|
+
|
852
|
+
example 'if with undefined does not show the contents' do
|
853
|
+
context.stub(:goodbye)
|
854
|
+
context.stub(:world).and_return('world')
|
855
|
+
subject.should == "cruel world!"
|
856
|
+
end
|
857
|
+
|
858
|
+
example 'if with non-empty array shows the contents' do
|
859
|
+
context.stub(:goodbye).and_return(['foo'])
|
860
|
+
context.stub(:world).and_return('world')
|
861
|
+
subject.should == "GOODBYE cruel world!"
|
862
|
+
end
|
863
|
+
|
864
|
+
example 'if with empty array does not show the contents' do
|
865
|
+
context.stub(:goodbye).and_return([])
|
866
|
+
context.stub(:world).and_return('world')
|
867
|
+
subject.should == "cruel world!"
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
describe '#each' do
|
872
|
+
let(:template) { "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!" }
|
873
|
+
|
874
|
+
example 'each with array iterates over the contents with non-empty' do
|
875
|
+
g = Struct.new(:text)
|
876
|
+
context.stub(:goodbyes).and_return([g.new('goodbye'), g.new('Goodbye'), g.new('GOODBYE')])
|
877
|
+
context.stub(:world).and_return('world')
|
878
|
+
subject.should == "goodbye! Goodbye! GOODBYE! cruel world!"
|
879
|
+
end
|
880
|
+
|
881
|
+
example 'each with array ignores the contents when empty' do
|
882
|
+
context.stub(:goodbyes).and_return([])
|
883
|
+
context.stub(:world).and_return('world')
|
884
|
+
subject.should == "cruel world!"
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
describe 'each with @index' do
|
889
|
+
let(:template) { "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!" }
|
890
|
+
|
891
|
+
example 'the @index variable is used' do
|
892
|
+
g = Struct.new(:text)
|
893
|
+
context.stub(:goodbyes).and_return([g.new('goodbye'), g.new('Goodbye'), g.new('GOODBYE')])
|
894
|
+
context.stub(:world).and_return('world')
|
895
|
+
subject.should == "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!"
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
describe 'log' do
|
900
|
+
let(:template) { "{{log blah}}" }
|
901
|
+
let(:log) { double(:log) }
|
902
|
+
before { FS.logger = log }
|
903
|
+
after { FS.logger = nil }
|
904
|
+
example do
|
905
|
+
context.stub(:blah).and_return('whee')
|
906
|
+
log.should_receive(:debug).with('FlavourSaver: whee')
|
907
|
+
subject.should == ''
|
908
|
+
end
|
909
|
+
end
|
910
|
+
end
|
911
|
+
end
|