hobo 0.5.3 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/bin/hobo +18 -4
  2. data/hobo_files/plugin/CHANGES.txt +511 -0
  3. data/hobo_files/plugin/README +8 -3
  4. data/hobo_files/plugin/Rakefile +81 -0
  5. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +4 -4
  6. data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -1
  7. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
  8. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +16 -22
  9. data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +4 -6
  10. data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +6 -5
  11. data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +4 -6
  12. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +237 -0
  13. data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +9 -0
  14. data/hobo_files/plugin/generators/hobo_model/USAGE +2 -3
  15. data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +1 -14
  16. data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +1 -6
  17. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +10 -4
  18. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +7 -6
  19. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_base.css +68 -0
  20. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.css +93 -0
  21. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +11 -6
  22. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/plus.png +0 -0
  23. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +24 -14
  24. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +28 -44
  25. data/hobo_files/plugin/generators/hobo_user_model/USAGE +2 -12
  26. data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +1 -14
  27. data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +0 -6
  28. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +8 -1
  29. data/hobo_files/plugin/init.rb +6 -2
  30. data/hobo_files/plugin/lib/active_record/has_many_association.rb +23 -12
  31. data/hobo_files/plugin/lib/extensions.rb +134 -40
  32. data/hobo_files/plugin/lib/extensions/test_case.rb +0 -1
  33. data/hobo_files/plugin/lib/hobo.rb +77 -46
  34. data/hobo_files/plugin/lib/hobo/authenticated_user.rb +24 -2
  35. data/hobo_files/plugin/lib/hobo/authentication_support.rb +2 -1
  36. data/hobo_files/plugin/lib/hobo/controller.rb +35 -12
  37. data/hobo_files/plugin/lib/hobo/define_tags.rb +4 -4
  38. data/hobo_files/plugin/lib/hobo/dryml.rb +33 -51
  39. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +47 -34
  40. data/hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb +37 -0
  41. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +27 -5
  42. data/hobo_files/plugin/lib/hobo/dryml/template.rb +545 -302
  43. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +305 -135
  44. data/hobo_files/plugin/lib/hobo/email_address.rb +5 -0
  45. data/hobo_files/plugin/lib/hobo/field_spec.rb +66 -0
  46. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +325 -0
  47. data/hobo_files/plugin/lib/hobo/html_string.rb +2 -0
  48. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +13 -1
  49. data/hobo_files/plugin/lib/hobo/markdown_string.rb +3 -1
  50. data/hobo_files/plugin/lib/hobo/model.rb +185 -66
  51. data/hobo_files/plugin/lib/hobo/model_controller.rb +56 -49
  52. data/hobo_files/plugin/lib/hobo/password_string.rb +2 -0
  53. data/hobo_files/plugin/lib/hobo/plugins.rb +75 -0
  54. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +98 -0
  55. data/hobo_files/plugin/lib/hobo/static_tags +0 -3
  56. data/hobo_files/plugin/lib/hobo/textile_string.rb +11 -1
  57. data/hobo_files/plugin/lib/hobo/undefined.rb +1 -1
  58. data/hobo_files/plugin/lib/rexml.rb +166 -75
  59. data/hobo_files/plugin/spec/fixtures/users.yml +9 -0
  60. data/hobo_files/plugin/spec/spec.opts +6 -0
  61. data/hobo_files/plugin/spec/spec_helper.rb +28 -0
  62. data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +650 -0
  63. data/hobo_files/plugin/tags/core.dryml +58 -4
  64. data/hobo_files/plugin/tags/rapid.dryml +289 -135
  65. data/hobo_files/plugin/tags/rapid_document_tags.dryml +49 -0
  66. data/hobo_files/plugin/tags/rapid_editing.dryml +92 -69
  67. data/hobo_files/plugin/tags/rapid_forms.dryml +242 -0
  68. data/hobo_files/plugin/tags/rapid_navigation.dryml +65 -65
  69. data/hobo_files/plugin/tags/rapid_pages.dryml +197 -124
  70. data/hobo_files/plugin/tags/rapid_support.dryml +23 -0
  71. metadata +29 -22
  72. data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +0 -13
  73. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +0 -11
  74. data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +0 -15
  75. data/hobo_files/plugin/lib/hobo/HtmlString +0 -3
  76. data/hobo_files/plugin/lib/hobo/controller_helpers.rb +0 -135
  77. data/hobo_files/plugin/lib/hobo/core.rb +0 -475
  78. data/hobo_files/plugin/lib/hobo/rapid.rb +0 -447
  79. data/hobo_files/plugin/test/hobo_dryml_template_test.rb +0 -7
  80. data/hobo_files/plugin/test/hobo_test.rb +0 -7
@@ -0,0 +1,650 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+
3
+ Template = Hobo::Dryml::Template
4
+ DrymlException = Hobo::Dryml::DrymlException
5
+
6
+ describe Template do
7
+
8
+ # --- Tag Compilation Examples --- #
9
+
10
+ # --- Compilation: Calling Block Tags --- #
11
+
12
+ it "should compile block-tag calls as method calls" do
13
+ compile_dryml("<foo/>").should == "<%= foo() %>"
14
+ end
15
+
16
+ it "should compile attributes as keyword parameters" do
17
+ compile_dryml("<foo a='1' b='2'/>").should == '<%= foo({:a => "1", :b => "2"}) %>'
18
+ end
19
+
20
+ it "should compile code attributes as ruby code" do
21
+ compile_dryml("<foo a='&1 + 2'/>").should == '<%= foo({:a => (1 + 2)}) %>'
22
+ end
23
+
24
+ it "should compile block-tag attributes with no RHS as passing `true`" do
25
+ compile_dryml("<foo a/>").should == '<%= foo({:a => (true)}) %>'
26
+ end
27
+
28
+ it "should compile content of a block-tag call as a Ruby block" do
29
+ compile_dryml("<foo>the body</foo>").should == "<% _output(foo() do |foo_default_tagbody| %>the body<% end) %>"
30
+ end
31
+
32
+ it "should support <default_tagbody/> inside the content of a block-tag" do
33
+ compile_dryml("<foo>!!<default_tagbody/>??</foo>").should ==
34
+ "<% _output(foo() do |foo_default_tagbody| %>!!<% foo_default_tagbody && foo_default_tagbody.call %>??<% end) %>"
35
+ end
36
+
37
+ it "should support the 'for' attribute on <default_tagbody/>" do
38
+ compile_dryml("<x><y>123<default_tagbody for='x'/>456</y></x>").should ==
39
+ "<% _output(x() do |x_default_tagbody| %><% _output(y() do |y_default_tagbody| %>" +
40
+ "123<% x_default_tagbody && x_default_tagbody.call %>456" +
41
+ "<% end) %><% end) %>"
42
+ end
43
+
44
+ it "should allow :foo as a shorthand for field='foo' on block tags" do
45
+ compile_dryml("<foo:name/>").should == '<%= foo({:field => "name"}) %>'
46
+ end
47
+
48
+ it "should allow :title as a shorthand for field='title' on block tags (title is a static tag)" do
49
+ compile_dryml("<foo:name/>").should == '<%= foo({:field => "name"}) %>'
50
+ end
51
+
52
+ it "should allow close tags to ommit the :field_name part" do
53
+ compile_dryml("<foo:name></foo>").should == '<%= foo({:field => "name"}) %>'
54
+ end
55
+
56
+ it "should compile block-tag calls with merge_attrs" do
57
+ compile_dryml("<foo merge_attrs/>").should == "<%= foo({}.merge((attributes) || {})) %>"
58
+ compile_dryml("<foo a='1' merge_attrs/>").should == '<%= foo({:a => "1"}.merge((attributes) || {})) %>'
59
+ end
60
+
61
+ # --- Compilation: Defining Block Tags --- #
62
+
63
+ it "should compile defs with lower-case names as block tags" do
64
+ compile_def("<def tag='foo'></def>").should ==
65
+ "<% def foo(all_attributes={}, &__block__); " +
66
+ "parameters = nil; " +
67
+ "_tag_context(all_attributes, __block__) do |tagbody| attributes, = _tag_locals(all_attributes, []) %>" +
68
+ "<% _erbout; end; end %>"
69
+ end
70
+
71
+ it "should compile attrs in defs as local variables" do
72
+ compile_def("<def tag='foo' attrs='a, b'></def>").should ==
73
+ "<% def foo(all_attributes={}, &__block__); " +
74
+ "parameters = nil; " +
75
+ "_tag_context(all_attributes, __block__) do |tagbody| " +
76
+ "a, b, attributes, = _tag_locals(all_attributes, [:a, :b]) %>" +
77
+ "<% _erbout; end; end %>"
78
+ end
79
+
80
+ it "should allow a default tagbody to be given inside the <tagbody> tag" do
81
+ compile_def("<def tag='foo'>123 <tagbody>blah</tagbody> 456</def>").should ==
82
+ "<% def foo(all_attributes={}, &__block__); " +
83
+ "parameters = nil; " +
84
+ "_tag_context(all_attributes, __block__) do |tagbody| attributes, = _tag_locals(all_attributes, []) %>" +
85
+ "123 " +
86
+ "<% _output(do_tagbody(tagbody, {}, proc { %>blah<% })) %>" +
87
+ " 456" +
88
+ "<% _erbout; end; end %>"
89
+ end
90
+
91
+ # --- Compilation: Defining Templates --- #
92
+
93
+ it "should compile defs with cap names as templates" do
94
+ # Note the presence of the `parameters` param, which block-tags don't have
95
+ compile_def("<def tag='Foo'></def>").should ==
96
+ "<% def Foo(all_attributes={}, all_parameters={}, &__block__); " +
97
+ "parameters = all_parameters - []; " +
98
+ "_tag_context(all_attributes, __block__) do |tagbody| attributes, = _tag_locals(all_attributes, []) %>" +
99
+ "<% _erbout; end; end %>"
100
+ end
101
+
102
+ it "should dissallow `param` outside of template definitions" do
103
+ proc { compile_dryml("<foo param/>") }.should raise_error(DrymlException)
104
+ end
105
+
106
+ it "should compile param tag-calls as calls to `call_block_tag_parameter`" do
107
+ compile_in_template("<foo param a='1'/>").should == '<%= call_block_tag_parameter(:foo, {:a => "1"}, all_parameters[:foo]) %>'
108
+ end
109
+
110
+ it "should compile with support for named params" do
111
+ compile_in_template("<foo param='zap'/>").should == "<%= call_block_tag_parameter(:foo, {}, all_parameters[:zap]) %>"
112
+ end
113
+
114
+ it "should compile a param tag-call with a body" do
115
+ compile_in_template("<foo param>abc</foo>").should ==
116
+ "<% _output(call_block_tag_parameter(:foo, {}, all_parameters[:foo]) do |foo_default_tagbody| %>abc<% end) %>"
117
+ end
118
+
119
+ it "should compile a param tag-call with a body and a call to <default_tagbody/>" do
120
+ compile_in_template("<foo param>!!<default_tagbody/>!!</foo>").should ==
121
+ "<% _output(call_block_tag_parameter(:foo, {}, all_parameters[:foo]) do |foo_default_tagbody| %>" +
122
+ "!!<% foo_default_tagbody && foo_default_tagbody.call %>!!<% end) %>"
123
+ end
124
+
125
+ it "should compile param template-calls as calls to `call_template_parameter`" do
126
+ compile_in_template("<Foo param/>").should ==
127
+ "<% _output(call_template_parameter(:Foo, {}, {}, all_parameters[:Foo])) %>"
128
+ end
129
+
130
+ it "should compile param template-calls with parameters as calls to `call_template_parameter`" do
131
+ compile_in_template("<Foo param><a x='1'/></Foo>").should ==
132
+ '<% _output(call_template_parameter(:Foo, {}, {:a => proc { {:x => "1"} }, }, all_parameters[:Foo])) %>'
133
+ end
134
+
135
+ it "should compile template parameters with param" do
136
+ compile_in_template("<Foo><abc param/></Foo>").should ==
137
+ '<% _output(Foo({}, {:abc => merge_option_procs(proc { {} }, all_parameters[:abc]), })) %>'
138
+ end
139
+
140
+ it "should compile template parameters with named params" do
141
+ compile_in_template("<Foo><abc param='x'/></Foo>").should ==
142
+ '<% _output(Foo({}, {:abc => merge_option_procs(proc { {} }, all_parameters[:x]), })) %>'
143
+ end
144
+
145
+ it "should compile template parameters with param and attributes" do
146
+ compile_in_template("<Foo><abc param='x' a='b'/></Foo>").should ==
147
+ '<% _output(Foo({}, {:abc => merge_option_procs(proc { {:a => "b"} }, all_parameters[:x]), })) %>'
148
+ end
149
+
150
+ it "should compile template parameters with param and a tag body" do
151
+ compile_in_template("<Foo><abc param>ha!</abc></Foo>").should ==
152
+ '<% _output(Foo({}, {:abc => merge_option_procs(' +
153
+ 'proc { {:tagbody => proc {|abc_default_tagbody| new_context { %>ha!<% } } } }, all_parameters[:abc]), })) %>'
154
+ end
155
+
156
+ it "should compile template parameters which are template calls themselves" do
157
+ compile_in_template("<Foo><Baa param x='1'/></Foo>").should ==
158
+ '<% _output(Foo({}, {:Baa => merge_template_parameter_procs(proc { [{:x => "1"}, {}] }, all_parameters[:Baa]), })) %>'
159
+ end
160
+
161
+ it "should compile template parameters which are templates themselves with their own parameters" do
162
+ compile_in_template("<Foo><Baa param><x>hello</x></Baa></Foo>").should ==
163
+ '<% _output(Foo({}, {:Baa => merge_template_parameter_procs(' +
164
+ 'proc { [{}, {:x => proc { {:tagbody => proc {|x_default_tagbody| new_context { %>hello<% } } } }, }] }, all_parameters[:Baa]), })) %>'
165
+ end
166
+
167
+ # --- Compilation: Calling Templates --- #
168
+
169
+ it "should compile template calls as method calls" do
170
+ compile_dryml("<Foo/>").should == "<% _output(Foo({}, {})) %>"
171
+ end
172
+
173
+ it "should compile attributes on template calls as keyword parameters" do
174
+ compile_dryml("<Foo a='1' b='2'/>").should == '<% _output(Foo({:a => "1", :b => "2"}, {})) %>'
175
+ end
176
+
177
+ it "should compile template parameters as procs" do
178
+ compile_dryml("<Foo><x>hello</x><y>world</y></Foo>").should ==
179
+ '<% _output(Foo({}, {' +
180
+ ':x => proc { {:tagbody => proc {|x_default_tagbody| new_context { %>hello<% } } } }, ' +
181
+ ':y => proc { {:tagbody => proc {|y_default_tagbody| new_context { %>world<% } } } }, })) %>'
182
+ end
183
+
184
+ it "should compile template parameters with attributes" do
185
+ compile_dryml("<Foo><abc x='1'>hello</abc></Foo>").should ==
186
+ '<% _output(Foo({}, {:abc => proc { {:x => "1", :tagbody => proc {|abc_default_tagbody| new_context { %>hello<% } } } }, })) %>'
187
+ end
188
+
189
+ it "should allow :foo as a shorthand for field='foo' on template tags" do
190
+ compile_dryml("<Foo:name/>").should == '<% _output(Foo({:field => "name"}, {})) %>'
191
+ end
192
+
193
+ it "should dissallow tag-bodies on template calls" do
194
+ proc { compile_dryml("<Foo>this is a tag body</Foo>") }.should raise_error(DrymlException)
195
+ end
196
+
197
+ it "should dissallow tag-bodies on nested template calls" do
198
+ proc { compile_dryml("<Foo><Baa>this is a tag body</Baa></Foo>") }.should raise_error(DrymlException)
199
+ end
200
+
201
+ it "should compile template parameters which are themselves templates" do
202
+ # Template parameters which are themselves templates are procs
203
+ # that return a pair of hashes, the first is the attributes to the
204
+ # template, the second is the sub-template procs
205
+ compile_dryml("<Foo><Baa x='1'><a>hello</a></Baa></Foo>").should ==
206
+ '<% _output(Foo({}, ' +
207
+ '{:Baa => proc { [{:x => "1"}, {:a => proc { {:tagbody => proc {|a_default_tagbody| new_context { %>hello<% } } } }, }] }, })) %>'
208
+ end
209
+
210
+ it "should compile 'replace' parameters" do
211
+ compile_dryml("<Page><head replace>abc</head></Page>").should ==
212
+ '<% _output(Page({}, {:head => proc {|head__default| new_context { %>abc<% } }, })) %>'
213
+ end
214
+
215
+ it "should compile 'replace' parameters with a default parameter call" do
216
+ compile_dryml("<Page><head replace>abc <head restore>blah</head></head></Page>").should ==
217
+
218
+ '<% _output(Page({}, {:head => proc {|head__default| new_context { %>abc ' +
219
+ '<% _output(head__default.call_with_block({}) do |head_default_tagbody| %>blah<% end) %>' +
220
+ '<% } }, })) %>'
221
+ end
222
+
223
+ it "should compile 'replace' template parameters with a default parameter call" do
224
+ compile_dryml("<Page><Head replace>abc <Head restore/></Head></Page>").should ==
225
+
226
+ '<% _output(Page({}, {:Head => proc {|Head__default| new_context { %>abc ' +
227
+ '<% _output(Head__default.call({}, {})) %>' +
228
+ '<% } }, })) %>'
229
+ end
230
+
231
+ # --- Tag Evalutation Examples --- #
232
+
233
+
234
+ # --- Static Tags --- #
235
+
236
+ it "should pass through tags declared as static" do
237
+ "p".should be_in(Hobo.static_tags)
238
+ eval_dryml("<p>abc</p>").should == "<p>abc</p>"
239
+ eval_dryml("<p x='1' y='2'>abc</p>").should == "<p x='1' y='2'>abc</p>"
240
+ end
241
+
242
+ it "should pass through attributes with no rhs on static tags" do
243
+ "p".should be_in(Hobo.static_tags)
244
+ eval_dryml("<p foo baa>abc</p>").should == "<p foo baa>abc</p>"
245
+ end
246
+
247
+ it "should support attribute merging on static tags" do
248
+ eval_dryml(%(<p class="big" id="x" merge_attrs="&{:class => 'small', :id => 'y', :a => 'b'}"/>)).
249
+ should be_dom_equal_to('<p class="big small" id="y" a="b"/>')
250
+ end
251
+
252
+ it "should support attribute merging of all extra attributes on static tags" do
253
+ eval_dryml(%(<def tag="x"><p class="big" id="x" merge_attrs/></def>
254
+ <x class='small' id='y' a='b'/>)).
255
+ should be_dom_equal_to('<p class="big small" id="y" a="b"/>')
256
+ end
257
+
258
+
259
+ # --- Block Tags --- #
260
+
261
+
262
+ def eval_with_defs(dryml)
263
+ eval_dryml(<<-END + dryml).strip
264
+ <def tag="t">plain tag</def>
265
+
266
+ <def tag="t_attr" attrs="x">it is <%= x %></def>
267
+
268
+ <def tag="t_body">( <tagbody>hmm</tagbody> )</def>
269
+
270
+ <def tag="merge_attrs_example"><p merge_attrs>hi</p></def>
271
+ END
272
+ end
273
+
274
+ it "should call block tags" do
275
+ eval_with_defs("<t/>").should == "plain tag"
276
+ end
277
+
278
+
279
+ it "should call block tags passing attributes" do
280
+ eval_with_defs("<t_attr x='10'/>").should == "it is 10"
281
+ end
282
+
283
+ it "should call block tags with a body" do
284
+ eval_with_defs("<t_body>foo</t_body>").should == "( foo )"
285
+ end
286
+
287
+ it "should allow tagbody to have a default" do
288
+ eval_with_defs("<t_body/>").should == "( hmm )"
289
+ end
290
+
291
+ it "should provide access to the default tagbody when overriding the body" do
292
+ eval_with_defs("<t_body>[<default_tagbody/>]</t_body>").should == "( [hmm] )"
293
+ end
294
+
295
+ it "should support merge_attrs on static tags" do
296
+ eval_with_defs('<merge_attrs_example class="x"/>').should == '<p class="x">hi</p>'
297
+ end
298
+
299
+ it "should make the declared attributes available via the 'attrs_for' method" do
300
+ eval_with_defs('<%= attrs_for(:t_attr).inspect %>').should == '[:x]'
301
+ end
302
+
303
+
304
+
305
+ # --- Template Tags --- #
306
+
307
+ def eval_with_templates(dryml)
308
+ eval_dryml(<<-END + dryml).strip
309
+ <def tag="defined" attrs="a, b">a is <%= a %>, b is <%= b %>, body is <tagbody/></def>
310
+
311
+ <def tag="T">plain template</def>
312
+
313
+ <def tag="StaticMerge"><p>a <b name="big" param>bold</b> word</p></def>
314
+
315
+ <def tag="EmptyStaticMerge"><img name="big" src="..." param/></def>
316
+
317
+ <def tag="DefTagMerge">foo <defined param b="3">baa</defined>!</def>
318
+
319
+ <def tag="NestedStaticMerge">merge StaticMerge: <StaticMerge param/></def>
320
+
321
+ <def tag="ParameterMerge">parameter merge: <StaticMerge><b param/></StaticMerge></def>
322
+ END
323
+ end
324
+
325
+ it "should call template tags" do
326
+ eval_with_templates("<T/>").should == "plain template"
327
+ end
328
+
329
+ it "should add attributes to static tags when merging" do
330
+ eval_with_templates("<StaticMerge><b onclick='alert()'/></StaticMerge>").should ==
331
+ '<p>a <b name="big" onclick="alert()">bold</b> word</p>'
332
+ end
333
+
334
+ it "should override attributes on static tags when merging" do
335
+ eval_with_templates("<StaticMerge><b name='small'/></StaticMerge>").should ==
336
+ '<p>a <b name="small">bold</b> word</p>'
337
+ end
338
+
339
+ it "should replace tag bodies on static tags when merging" do
340
+ eval_with_templates('<StaticMerge><b>BOLD</b></StaticMerge>').should ==
341
+ '<p>a <b name="big">BOLD</b> word</p>'
342
+ end
343
+
344
+ it "should add attributes to defined tags when merging" do
345
+ eval_with_templates('<DefTagMerge><defined a="2"/></DefTagMerge>').should ==
346
+ 'foo a is 2, b is 3, body is baa!'
347
+ end
348
+
349
+ it "should override attributes on defined tags when merging" do
350
+ eval_with_templates('<DefTagMerge><defined b="2"/></DefTagMerge>').should ==
351
+ 'foo a is , b is 2, body is baa!'
352
+ end
353
+
354
+ it "should replace tag bodies on defined tags when merging" do
355
+ eval_with_templates('<DefTagMerge><defined>zip</defined></DefTagMerge>').should ==
356
+ 'foo a is , b is 3, body is zip!'
357
+ end
358
+
359
+ it "should leave non-merged tags unchanged" do
360
+ eval_with_templates('<StaticMerge></StaticMerge>').should ==
361
+ '<p>a <b name="big">bold</b> word</p>'
362
+ end
363
+
364
+ it "should merge into static tags with no body" do
365
+ eval_with_templates("<EmptyStaticMerge><img name='small'/></EmptyStaticMerge>").should ==
366
+ '<img name="small" src="..." />'
367
+ end
368
+
369
+ it "should merge template parameters into nested templates" do
370
+ eval_with_templates('<NestedStaticMerge><StaticMerge><b name="small"/></StaticMerge></NestedStaticMerge>').should ==
371
+ 'merge StaticMerge: <p>a <b name="small">bold</b> word</p>'
372
+ end
373
+
374
+ it "should merge the body of template parameters into nested templates" do
375
+ eval_with_templates('<NestedStaticMerge><StaticMerge><b>BOLD</b></StaticMerge></NestedStaticMerge>').should ==
376
+ 'merge StaticMerge: <p>a <b name="big">BOLD</b> word</p>'
377
+ end
378
+
379
+ it "should allow param names to be defined dynamically" do
380
+ eval_dryml('<def tag="T"><p param="& :a.to_s + :b.to_s"/></def>' +
381
+ '<T><ab x="1"/></T>').should == '<p x="1" />'
382
+ end
383
+
384
+ it "should allow params to be defined on other params" do
385
+ eval_with_templates('<ParameterMerge><b name="small">foo</b></ParameterMerge>').should ==
386
+ 'parameter merge: <p>a <b name="small">foo</b> word</p>'
387
+
388
+ end
389
+
390
+ it "should allow parameter bodies to be restored with static tag params" do
391
+ eval_with_templates("<StaticMerge><b>very <default_tagbody/></b></StaticMerge>").should ==
392
+ '<p>a <b name="big">very bold</b> word</p>'
393
+ end
394
+
395
+ it "should allow parameter bodies to be restored with defined tag params" do
396
+ eval_with_templates("<DefTagMerge><defined>hum<default_tagbody/></defined></DefTagMerge>").should ==
397
+ 'foo a is , b is 3, body is humbaa!'
398
+ end
399
+
400
+
401
+
402
+ # --- Replacing Template Parameters --- #
403
+
404
+ it "should allow template parameters to be replaced" do
405
+ eval_with_templates('<StaticMerge><b replace>short</b></StaticMerge>').should ==
406
+ '<p>a short word</p>'
407
+ end
408
+
409
+ it "should allow template parameters to be replaced and then re-instated" do
410
+ eval_with_templates('<StaticMerge><b replace>short <b restore/></b></StaticMerge>').should ==
411
+ '<p>a short <b name="big">bold</b> word</p>'
412
+ end
413
+
414
+ it "should allow template parameters to be replaced and then re-instated with different attributes" do
415
+ eval_with_templates('<StaticMerge><b replace>short <b restore name="small"/></b></StaticMerge>').should ==
416
+ '<p>a short <b name="small">bold</b> word</p>'
417
+ end
418
+
419
+ it "should allow template parameters to be replaced and then re-instated with a different tagbody" do
420
+ eval_with_templates('<StaticMerge><b replace>short <b restore>big</b></b></StaticMerge>').should ==
421
+ '<p>a short <b name="big">big</b> word</p>'
422
+ end
423
+
424
+ # --- Merge Params --- #
425
+
426
+
427
+ it "should support merge_param on template calls" do
428
+ tags = %(<def tag="Page"><h1 param='title'/><div param='footer'/></def>
429
+ <def tag="MyPage"><Page merge_params><footer>the footer</footer></Page></def>)
430
+
431
+ eval_dryml(tags + "<MyPage><title>Hi!</title></MyPage>").should == '<h1>Hi!</h1><div>the footer</div>'
432
+ end
433
+
434
+
435
+ # --- Polymorphic Tags --- #
436
+
437
+ it "should allow tags to be selected based on types" do
438
+ tags = %(<def tag="do"><tagbody/></def>
439
+ <def tag="t" for="String">A string</def>
440
+ <def tag="t" for="TrueClass">A boolean</def>)
441
+
442
+ eval_dryml(tags + '<do with="&\'foo\'"><%= call_polymorphic_tag(:t) %></do>').should == "A string"
443
+ eval_dryml(tags + '<do with="&false"><%= call_polymorphic_tag(:t) %></do>').should == "A boolean"
444
+ end
445
+
446
+ # --- The Context --- #
447
+
448
+ def context_eval(context, src)
449
+ eval_dryml(src, :context => context)
450
+ end
451
+
452
+ def a_user
453
+ Struct.new(:name, :email).new("Tom", "tom@foo.net")
454
+ end
455
+
456
+ def show_tag
457
+ '<def tag="show"><%= this %></def>'
458
+ end
459
+
460
+ it "should make the initial context available as `this`" do
461
+ context_eval('hello', "<%= this %>").should == "hello"
462
+ end
463
+
464
+ it "should allow the context to be changed with the :<field-name> syntax" do
465
+ context_eval(a_user, show_tag + '<show:name/>').should == "Tom"
466
+ end
467
+
468
+ it "should allow the :<field-name> to be ommitted from the close tag" do
469
+ context_eval(a_user, show_tag + '<show:name></show:name>').should == "Tom"
470
+ context_eval(a_user, show_tag + '<show:name></show>').should == "Tom"
471
+ end
472
+
473
+
474
+ it "should allow the context to be changed with a 'with' attribute" do
475
+ eval_dryml(show_tag + %(<show with="&'hello'"/>)).should == 'hello'
476
+ end
477
+
478
+ it "should allow the context to be changed with a 'field' attribute" do
479
+ context_eval(a_user, show_tag + '<show field="name"/>').should == "Tom"
480
+ end
481
+
482
+ it "should allow the context to be changed inside template parameters" do
483
+ tags = %(<def tag="do"><tagbody/></def>
484
+ <def tag="Template"><do:name><p param/></do></def>)
485
+ context_eval(a_user, tags + '<Template><p><%= this %></p></Template>').should == '<p>Tom</p>'
486
+ end
487
+
488
+
489
+ # --- <set> --- #
490
+
491
+ it "should provide <set> to create local variables" do
492
+ eval_dryml("<set x='&1' y='&2'/><%= x + y %>").should == '3'
493
+ end
494
+
495
+ it 'should interpolate #{...} blocks in attributes of any tag' do
496
+ tag = '<def tag="t" attrs="x"><%= x %></def>'
497
+
498
+ eval_dryml(tag + "<t x='#{1+2}'/>").should == '3'
499
+ eval_dryml(tag + "<t x='hey #{1+2} ho'/>").should == 'hey 3 ho'
500
+
501
+ eval_dryml(tag + "<p class='#{1+2}'/>").should == "<p class='3'/>"
502
+ eval_dryml(tag + "<p class='hey #{1+2} ho'/>").should == "<p class='hey 3 ho'/>"
503
+ end
504
+
505
+
506
+ # --- <set_scoped> --- #
507
+
508
+ it "should support scoped variables" do
509
+ tags =
510
+ "<def tag='t1'><set_scoped x='ping'><tagbody/></set_scoped></def>" +
511
+ "<def tag='t2'><set_scoped x='pong'><tagbody/></set_scoped></def>"
512
+ eval_dryml(tags + "<t1><%= scope.x %></t1>").should == 'ping'
513
+ eval_dryml(tags + "<t1><t2><%= scope.x %></t2> <%= scope.x %></t1>").should == 'pong ping'
514
+ end
515
+
516
+
517
+ # --- Taglibs --- #
518
+
519
+ it "should import tags from taglibs with the <include> tag" do
520
+ eval_dryml("<include src='taglibs/simple'/> <foo/>").should == "I am the foo tag"
521
+ end
522
+
523
+ it "should import tags from taglibs into a namespace with <include as/>" do
524
+ proc { eval_dryml("<include src='taglibs/simple' as='a'/> <foo/>") }.should raise_error
525
+ eval_dryml("<include src='taglibs/simple' as='a'/> <a.foo/>").should == "I am the foo tag"
526
+ end
527
+
528
+
529
+ # --- Control Attributes --- #
530
+
531
+ it "should alow static tags to be conditional with the 'if' attribute" do
532
+ eval_dryml("<p if='&false'/>").should == ""
533
+ eval_dryml("<p if='&true'/>").should == "<p />"
534
+
535
+ eval_dryml("<p if='&false'>hello</p>").should == ""
536
+ eval_dryml("<p if='&true'>hello</p>").should == "<p>hello</p>"
537
+ end
538
+
539
+ it "should alow static tags to be repeated with the 'repeat' attribute" do
540
+ eval_dryml('<img repeat="&[1,2,3]" src="#{this}" />').should ==
541
+ '<img src="1" /><img src="2" /><img src="3" />'
542
+
543
+ # Make sure <%= %> doesn't break
544
+ eval_dryml('<img repeat="&[1,2,3]" src="<%= this %>" />').should ==
545
+ '<img src="1" /><img src="2" /><img src="3" />'
546
+ end
547
+
548
+ it "should alow templates to be repeated with the 'repeat' attribute" do
549
+ eval_dryml('<def tag="T"><%= this %></def><T repeat="&[1,2,3]"/>').should == '123'
550
+ end
551
+
552
+ it "should allow <else> to be used with the if attribute" do
553
+ eval_dryml("<p if='&false'/><%= Hobo::Dryml.last_if %>").should == "false"
554
+ end
555
+
556
+
557
+ # --- Test Helpers --- #
558
+
559
+ def prepare_template(src, options)
560
+ options.reverse_merge!(:template_path => "TEST")
561
+
562
+ Hobo::Dryml::Template.clear_build_cache
563
+ @env = Class.new(Hobo::Dryml::TemplateEnvironment)
564
+ template = Template.new(src, @env, options[:template_path])
565
+
566
+ template.instance_variable_set("@builder", options[:builder]) if options[:builder]
567
+ template
568
+ end
569
+
570
+
571
+ def eval_dryml(src, options={})
572
+ options.reverse_merge!(:locals => {}, :implicit_imports => [])
573
+
574
+ template = prepare_template(src, options)
575
+ template.compile(options[:locals].keys, options[:implicit_imports])
576
+ new_renderer.render_page(options[:context], options[:locals]).strip
577
+ end
578
+
579
+ def compile_dryml(src, options={})
580
+ template = prepare_template(src, options)
581
+ CompiledDryml.new(template.process_src)
582
+ end
583
+
584
+ def new_renderer
585
+ @env.new("test-view", nil)
586
+ end
587
+
588
+ def compile_in_template(src)
589
+ builder = mock("builder", :null_object => true)
590
+ def_src = nil
591
+ builder.should_receive(:add_build_instruction) do |type, args|
592
+ def_src = args[:src]
593
+ end
594
+ compile_dryml("<def tag='MyTemplate'>#{src}</def>", :builder => builder)
595
+
596
+ # get rid of first and last scriptlets - they're to do with the method declaration
597
+ def_src.to_s.sub(/^\<\%.*?\%\>/, "").sub(/<% _erbout; end; end %><% _register_tag_attrs.*$/, "")
598
+ end
599
+
600
+ def compile_def(src)
601
+ builder = mock("builder", :null_object => true)
602
+ def_src = nil
603
+ builder.should_receive(:add_build_instruction) do |type, args|
604
+ def_src = args[:src]
605
+ end
606
+ compile_dryml(src, :builder => builder)
607
+
608
+ def_src.sub(/<% _register_tag_attrs.*/, "")
609
+ end
610
+
611
+ end
612
+
613
+ class CompiledDryml < String
614
+
615
+ def ==(other)
616
+ self.to_s.gsub(/\s+/, ' ').strip == other.gsub(/\s+/, ' ').strip
617
+ end
618
+
619
+ end
620
+
621
+
622
+ module Spec
623
+ module Matchers
624
+
625
+ class BeDomEqualTo #:nodoc:
626
+ def initialize(expected)
627
+ @expected = expected
628
+ end
629
+
630
+ def matches?(actual)
631
+ @actual = actual
632
+ expected_dom = HTML::Document.new(@expected).root
633
+ actual_dom = HTML::Document.new(@actual).root
634
+ expected_dom == actual_dom
635
+ end
636
+
637
+ def failure_message
638
+ "#{@expected}\nexpected to be == (by DOM) to\n#{@actual}"
639
+ end
640
+
641
+ def description
642
+ "be DOM equal to\n#{@expected}"
643
+ end
644
+ end
645
+
646
+ def be_dom_equal_to(expected)
647
+ Matchers::BeDomEqualTo.new(expected)
648
+ end
649
+ end
650
+ end