bike 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. data/LICENSE +19 -0
  2. data/README.rdoc +124 -0
  3. data/bin/bike +35 -0
  4. data/lib/_error.rb +14 -0
  5. data/lib/_field.rb +260 -0
  6. data/lib/_i18n.rb +144 -0
  7. data/lib/_parser.rb +256 -0
  8. data/lib/_path.rb +86 -0
  9. data/lib/_storage/_storage.rb +215 -0
  10. data/lib/_storage/file.rb +201 -0
  11. data/lib/_storage/sequel.rb +174 -0
  12. data/lib/_storage/temp.rb +73 -0
  13. data/lib/_widget/action_create.rb +23 -0
  14. data/lib/_widget/action_login.rb +22 -0
  15. data/lib/_widget/action_signup.rb +16 -0
  16. data/lib/_widget/action_update.rb +16 -0
  17. data/lib/_widget/crumb.rb +24 -0
  18. data/lib/_widget/done.rb +16 -0
  19. data/lib/_widget/login.rb +25 -0
  20. data/lib/_widget/me.rb +31 -0
  21. data/lib/_widget/message.rb +51 -0
  22. data/lib/_widget/navi.rb +88 -0
  23. data/lib/_widget/submit.rb +49 -0
  24. data/lib/_widget/view_ym.rb +77 -0
  25. data/lib/_workflow/_workflow.rb +89 -0
  26. data/lib/_workflow/attachment.rb +50 -0
  27. data/lib/_workflow/blog.rb +28 -0
  28. data/lib/_workflow/contact.rb +23 -0
  29. data/lib/_workflow/forum.rb +26 -0
  30. data/lib/_workflow/register.rb +39 -0
  31. data/lib/bike.rb +396 -0
  32. data/lib/meta/_meta.rb +20 -0
  33. data/lib/meta/group.rb +19 -0
  34. data/lib/meta/id.rb +59 -0
  35. data/lib/meta/owner.rb +21 -0
  36. data/lib/meta/timestamp.rb +118 -0
  37. data/lib/scalar/checkbox.rb +68 -0
  38. data/lib/scalar/file.rb +144 -0
  39. data/lib/scalar/img.rb +112 -0
  40. data/lib/scalar/password.rb +58 -0
  41. data/lib/scalar/radio.rb +47 -0
  42. data/lib/scalar/select.rb +47 -0
  43. data/lib/scalar/text.rb +38 -0
  44. data/lib/scalar/textarea.rb +35 -0
  45. data/lib/scalar/textarea_pre.rb +14 -0
  46. data/lib/scalar/textarea_wiki.rb +173 -0
  47. data/lib/set/_set.rb +196 -0
  48. data/lib/set/dynamic.rb +177 -0
  49. data/lib/set/static.rb +102 -0
  50. data/lib/set/static_folder.rb +96 -0
  51. data/locale/en/index.po +242 -0
  52. data/locale/index.pot +243 -0
  53. data/locale/ja/index.po +242 -0
  54. data/locale/lazy_parser.rb +54 -0
  55. data/skel/config.ru +27 -0
  56. data/skel/skin/_users/00000000_frank-avatar.jpg +0 -0
  57. data/skel/skin/_users/00000000_frank-avatar_small.jpg +0 -0
  58. data/skel/skin/_users/00000000_frank.yaml +12 -0
  59. data/skel/skin/_users/00000000_root-avatar.jpg +0 -0
  60. data/skel/skin/_users/00000000_root-avatar_small.jpg +0 -0
  61. data/skel/skin/_users/00000000_root.yaml +11 -0
  62. data/skel/skin/_users/css/users.css +21 -0
  63. data/skel/skin/_users/css/users.less +25 -0
  64. data/skel/skin/_users/done.html +42 -0
  65. data/skel/skin/_users/index.html +46 -0
  66. data/skel/skin/_users/index.yaml +3 -0
  67. data/skel/skin/_users/summary.html +40 -0
  68. data/skel/skin/css/base.css +93 -0
  69. data/skel/skin/css/base.less +139 -0
  70. data/skel/skin/css/coax.css +199 -0
  71. data/skel/skin/css/coax.less +244 -0
  72. data/skel/skin/examples/blog/20091214_0001.yaml +8 -0
  73. data/skel/skin/examples/blog/20100630_0001.yaml +8 -0
  74. data/skel/skin/examples/blog/20100630_0002.yaml +14 -0
  75. data/skel/skin/examples/blog/20100701_0001.yaml +8 -0
  76. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f.jpg +0 -0
  77. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f_small.jpg +0 -0
  78. data/skel/skin/examples/blog/20100701_0002.yaml +19 -0
  79. data/skel/skin/examples/blog/frank/20100701_0001.yaml +10 -0
  80. data/skel/skin/examples/blog/frank/index.yaml +4 -0
  81. data/skel/skin/examples/blog/index.html +51 -0
  82. data/skel/skin/examples/blog/rss.xml +18 -0
  83. data/skel/skin/examples/contact/20100701_0001-file.txt +1 -0
  84. data/skel/skin/examples/contact/20100701_0001.yaml +15 -0
  85. data/skel/skin/examples/contact/20100701_0002.yaml +8 -0
  86. data/skel/skin/examples/contact/20100701_0003.yaml +9 -0
  87. data/skel/skin/examples/contact/index.html +47 -0
  88. data/skel/skin/examples/contact/js/contact.js +13 -0
  89. data/skel/skin/examples/contact/summary.html +54 -0
  90. data/skel/skin/examples/forum/20100701_0001.yaml +41 -0
  91. data/skel/skin/examples/forum/20100701_0002.yaml +25 -0
  92. data/skel/skin/examples/forum/index.html +68 -0
  93. data/skel/skin/examples/forum/summary.html +47 -0
  94. data/skel/skin/examples/index.html +73 -0
  95. data/skel/skin/index.html +39 -0
  96. data/skel/skin/js/base.js +50 -0
  97. data/t/locale/de/index.po +19 -0
  98. data/t/locale/en-GB/index.po +25 -0
  99. data/t/locale/ja/index.po +30 -0
  100. data/t/skin/_users/00000000_test.yaml +3 -0
  101. data/t/skin/_users/index.html +13 -0
  102. data/t/skin/foo/20091120_0001.yaml +7 -0
  103. data/t/skin/foo/bar/20091120_0001.yaml +5 -0
  104. data/t/skin/foo/bar/index.yaml +5 -0
  105. data/t/skin/foo/baz/css/baz.css +1 -0
  106. data/t/skin/foo/css/foo.css +1 -0
  107. data/t/skin/foo/index.html +14 -0
  108. data/t/skin/foo/index.yaml +7 -0
  109. data/t/skin/foo/not_css/foo.css +1 -0
  110. data/t/skin/foo/qux/index.html +8 -0
  111. data/t/skin/foo/qux/moo/index.html +6 -0
  112. data/t/skin/foo/sub-20100306_0001.yaml +3 -0
  113. data/t/skin/index.yaml +3 -0
  114. data/t/skin/t_attachment/index.html +13 -0
  115. data/t/skin/t_contact/done.html +6 -0
  116. data/t/skin/t_contact/index.html +9 -0
  117. data/t/skin/t_file/index.html +16 -0
  118. data/t/skin/t_img/index.html +14 -0
  119. data/t/skin/t_img/test.jpg +0 -0
  120. data/t/skin/t_select/index.html +9 -0
  121. data/t/skin/t_store/index.html +9 -0
  122. data/t/skin/t_summary/20100326_0001.yaml +3 -0
  123. data/t/skin/t_summary/create.html +9 -0
  124. data/t/skin/t_summary/index.html +9 -0
  125. data/t/skin/t_summary/summary.html +9 -0
  126. data/t/t.rb +27 -0
  127. data/t/test_bike.rb +768 -0
  128. data/t/test_call.rb +1281 -0
  129. data/t/test_checkbox.rb +273 -0
  130. data/t/test_field.rb +330 -0
  131. data/t/test_file.rb +900 -0
  132. data/t/test_i18n.rb +325 -0
  133. data/t/test_id.rb +215 -0
  134. data/t/test_img.rb +328 -0
  135. data/t/test_meta.rb +57 -0
  136. data/t/test_parser.rb +1516 -0
  137. data/t/test_password.rb +188 -0
  138. data/t/test_radio.rb +226 -0
  139. data/t/test_role.rb +249 -0
  140. data/t/test_select.rb +182 -0
  141. data/t/test_set_complex.rb +527 -0
  142. data/t/test_set_dynamic.rb +1504 -0
  143. data/t/test_set_folder.rb +515 -0
  144. data/t/test_set_permit.rb +246 -0
  145. data/t/test_set_static.rb +468 -0
  146. data/t/test_storage.rb +915 -0
  147. data/t/test_text.rb +125 -0
  148. data/t/test_textarea.rb +138 -0
  149. data/t/test_timestamp.rb +473 -0
  150. data/t/test_workflow.rb +367 -0
  151. metadata +347 -0
@@ -0,0 +1,328 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ require "#{::File.dirname __FILE__}/t"
7
+
8
+ class TC_Img < Test::Unit::TestCase
9
+
10
+ def setup
11
+ Bike.current[:uri] = nil
12
+
13
+ File.open('t/skin/t_img/test.jpg') {|f|
14
+ @img = f.read
15
+ @file = Tempfile.open('tc_img')
16
+ @file << @img
17
+ }
18
+
19
+ meta = nil
20
+ Bike::Parser.gsub_scalar('$(foo img 32*32 1..100000 jpg, gif, png crop)') {|id, m|
21
+ meta = m
22
+ ''
23
+ }
24
+ @f = Bike::Field.instance meta.merge(:id => 'foo')
25
+ end
26
+
27
+ def test_meta
28
+ assert_equal(
29
+ 1,
30
+ @f[:min],
31
+ 'Img#initialize should set :min from the range token'
32
+ )
33
+ assert_equal(
34
+ 100000,
35
+ @f[:max],
36
+ 'Img#initialize should set :max from the range token'
37
+ )
38
+ assert_equal(
39
+ ['jpg', 'gif', 'png'],
40
+ @f[:options],
41
+ 'Img#initialize should set :options from the csv token'
42
+ )
43
+ assert_equal(
44
+ true,
45
+ @f[:crop],
46
+ 'Img#initialize should set :options from the csv token'
47
+ )
48
+ end
49
+
50
+ def test_val_cast_from_rack
51
+ @f.create(
52
+ :type => 'image/jpeg',
53
+ :tempfile => @file,
54
+ :head => <<'_eos',
55
+ Content-Disposition: form-data; name="t_img"; filename="baz.jpg"
56
+ Content-Type: image/jpeg
57
+ _eos
58
+ :filename => 'baz.jpg',
59
+ :name => 't_img'
60
+ )
61
+
62
+ assert_equal(
63
+ {
64
+ 'basename' => 'baz.jpg',
65
+ 'type' => 'image/jpeg',
66
+ 'size' => @file.length,
67
+ },
68
+ @f.val,
69
+ 'Img#val_cast should re-map a hash from Rack'
70
+ )
71
+ assert_equal(
72
+ @img,
73
+ @f.body,
74
+ 'Img#val_cast should store the file body in @body'
75
+ )
76
+ assert_equal(
77
+ @f.send(:_thumbnail, @file),
78
+ @f.thumbnail,
79
+ 'Img#val_cast should store the thumbnail in @thumbnail'
80
+ )
81
+ end
82
+
83
+ def test_val_cast_load
84
+ @f.load(
85
+ 'basename' => 'baz.jpg',
86
+ 'type' => 'image/jpeg',
87
+ 'size' => 123
88
+ )
89
+ assert_equal(
90
+ {
91
+ 'basename' => 'baz.jpg',
92
+ 'type' => 'image/jpeg',
93
+ 'size' => 123,
94
+ },
95
+ @f.val,
96
+ 'Img#val_cast should load() a hash without :tempfile like Set#load'
97
+ )
98
+ end
99
+
100
+ def test_large_thumbnail
101
+ @f[:width] = @f[:height] = 640
102
+ @f.create(
103
+ :type => 'image/jpeg',
104
+ :tempfile => @file,
105
+ :head => <<'_eos',
106
+ Content-Disposition: form-data; name="t_img"; filename="baz.jpg"
107
+ Content-Type: image/jpeg
108
+ _eos
109
+ :filename => 'baz.jpg',
110
+ :name => 't_img'
111
+ )
112
+ assert_not_equal(
113
+ 0,
114
+ @f.instance_variable_get(:@thumbnail).to_s.size,
115
+ 'Img#_thumbnail should make a thumbnail larger than the original img'
116
+ )
117
+ end
118
+
119
+ def test_get
120
+ Bike.client = 'root'
121
+
122
+ @f[:parent] = Bike::Set::Static::Folder.root.item('t_img', 'main')
123
+ Bike.current[:base] = @f[:parent]
124
+ tid = @f[:parent][:tid]
125
+
126
+ @f.load({})
127
+ assert_equal(
128
+ <<'_html'.chomp,
129
+ <span class="dummy_img" style="width: 32px; height: 32px;"></span>
130
+ _html
131
+ @f.get,
132
+ 'Img#get should return default span when the val is empty'
133
+ )
134
+
135
+ @f.load(
136
+ 'basename' => 'baz.jpg',
137
+ 'type' => 'image/jpeg',
138
+ 'size' => 12
139
+ )
140
+ assert_equal(
141
+ <<'_html'.chomp,
142
+ <a href="/t_img/main/foo/baz.jpg"><img src="/t_img/main/foo/baz_small.jpg" alt="baz.jpg" /></a>
143
+ _html
144
+ @f.get,
145
+ 'Img#get should return proper string'
146
+ )
147
+ assert_equal(
148
+ <<"_html",
149
+ <span class="img">
150
+ <a href="/t_img/#{tid}/foo/baz.jpg"><img src="/t_img/#{tid}/foo/baz_small.jpg" alt="baz.jpg" /></a>
151
+ <input type="file" name="foo" size="" class="file" />
152
+ </span>
153
+ _html
154
+ @f.get(:action => :update),
155
+ 'Img#get should return proper string'
156
+ )
157
+
158
+ @f.load(
159
+ 'basename' => '<baz>.jpg',
160
+ 'type' => 'image/<jpeg>',
161
+ 'size' => 12
162
+ )
163
+ assert_equal(
164
+ <<'_html'.chomp,
165
+ <a href="/t_img/main/foo/&lt;baz&gt;.jpg"><img src="/t_img/main/foo/&lt;baz&gt;_small.jpg" alt="&lt;baz&gt;.jpg" /></a>
166
+ _html
167
+ @f.get,
168
+ 'Img#get should escape the special characters in file information'
169
+ )
170
+ end
171
+
172
+ def test_get_not_image
173
+ @f.create(
174
+ :type => 'text/plain',
175
+ :tempfile => @file,
176
+ :head => <<'_eos',
177
+ Content-Disposition: form-data; name="t_img"; filename="baz.txt"
178
+ Content-Type: text/plain
179
+ _eos
180
+ :filename => 'baz.txt',
181
+ :name => 't_img'
182
+ )
183
+ assert_equal(
184
+ '<a href="foo/baz.txt">baz.txt (3535 bytes)</a>',
185
+ @f.get,
186
+ 'Img#get should fall back to File#get if the file is not an image'
187
+ )
188
+ end
189
+
190
+ def test_call_body
191
+ Bike.client = 'root'
192
+ sd = Bike::Set::Static::Folder.root.item('t_img', 'main')
193
+ sd.storage.clear
194
+
195
+ # post a multipart request
196
+ input = <<"_eos".gsub(/\r?\n/, "\r\n").sub('@img', @img)
197
+ ---foobarbaz
198
+ Content-Disposition: form-data; name="_1-foo"; filename="foo.jpg"
199
+ Content-Type: image/jpeg
200
+ Content-Transfer-Encoding: binary
201
+
202
+ @img
203
+ ---foobarbaz
204
+ Content-Disposition: form-data; name="_token"
205
+
206
+ #{Bike.token}
207
+ ---foobarbaz--
208
+ _eos
209
+ res = Rack::MockRequest.new(Bike.new).post(
210
+ 'http://example.com/t_img/main/update.html',
211
+ {
212
+ :input => input,
213
+ 'CONTENT_TYPE' => 'multipart/form-data; boundary=-foobarbaz',
214
+ 'CONTENT_LENGTH' => input.respond_to?(:bytesize) ? input.bytesize : input.size,
215
+ }
216
+ )
217
+ tid = res.headers['Location'][Bike::REX::TID]
218
+
219
+ # commit the base
220
+ res = Rack::MockRequest.new(Bike.new).post(
221
+ "http://example.com/#{tid}/update.html",
222
+ {
223
+ :input => ".status-public=create&_token=#{Bike.token}",
224
+ }
225
+ )
226
+
227
+ res.headers['Location'] =~ Bike::REX::PATH_ID
228
+ new_id = sprintf('%.8d_%.4d', $1, $2)
229
+
230
+ res = Rack::MockRequest.new(Bike.new).get(
231
+ "http://example.com/t_img/#{new_id}/foo/foo.jpg"
232
+ )
233
+ assert_equal(
234
+ 'image/jpeg',
235
+ res.headers['Content-Type'],
236
+ 'Bike#call to a img item should return the mime type of the file'
237
+ )
238
+ assert_equal(
239
+ @img.respond_to?(:bytesize) ? @img.bytesize : @img.size,
240
+ res.body.respond_to?(:bytesize) ? res.body.bytesize : res.body.size,
241
+ 'Bike#call to a img item should return the binary body of the file'
242
+ )
243
+
244
+ res = Rack::MockRequest.new(Bike.new).get(
245
+ "http://example.com/t_img/#{new_id}/foo/foo_small.jpg"
246
+ )
247
+ assert_equal(
248
+ 'image/jpeg',
249
+ res.headers['Content-Type'],
250
+ "Bike#call to 'file-small.*' should return the thumbnail of the file"
251
+ )
252
+ @file.rewind
253
+ assert_equal(
254
+ @f.send(:_thumbnail, @file).size,
255
+ res.body.size,
256
+ "Bike#call to 'file-small.*' should return the thumbnail of the file"
257
+ )
258
+
259
+ # delete
260
+ Rack::MockRequest.new(Bike.new).post(
261
+ 'http://example.com/t_img/update.html',
262
+ {
263
+ :input => "#{new_id}.action=delete&.status-public=delete&_token=#{Bike.token}",
264
+ }
265
+ )
266
+ res = Rack::MockRequest.new(Bike.new).get(
267
+ "http://example.com/t_img/#{new_id}/foo/foo.jpg"
268
+ )
269
+ assert_equal(
270
+ 404,
271
+ res.status,
272
+ 'Bike#call should delete child files as well'
273
+ )
274
+ res = Rack::MockRequest.new(Bike.new).get(
275
+ "http://example.com/t_img/#{new_id}/foo/foo_small.jpg"
276
+ )
277
+ assert_equal(
278
+ 404,
279
+ res.status,
280
+ 'Bike#call should delete child files as well'
281
+ )
282
+ end
283
+
284
+ def test_errors
285
+ File.open('t/skin/t_img/index.html') {|f|
286
+ @img = f.read
287
+ @file = Tempfile.open('tc_img')
288
+ @file << @img
289
+ }
290
+ @f.create(
291
+ :type => 'image/jpeg',
292
+ :tempfile => @file,
293
+ :head => <<'_eos',
294
+ Content-Disposition: form-data; name="t_img"; filename="baz.jpg"
295
+ Content-Type: image/jpeg
296
+ _eos
297
+ :filename => 'baz.jpg',
298
+ :name => 't_img'
299
+ )
300
+ assert_equal(
301
+ ['wrong file type: should be jpg/gif/png'],
302
+ @f.errors,
303
+ "Img#errors should regard quick_magick errors as 'wrong file type'"
304
+ )
305
+
306
+ File.open('t/skin/t_img/test.jpg') {|f|
307
+ @img = f.read
308
+ @file = Tempfile.open('tc_img')
309
+ @file << @img
310
+ }
311
+ @f.update(
312
+ :type => 'image/jpeg',
313
+ :tempfile => @file,
314
+ :head => <<'_eos',
315
+ Content-Disposition: form-data; name="t_img"; filename="baz.jpg"
316
+ Content-Type: image/jpeg
317
+ _eos
318
+ :filename => 'baz.jpg',
319
+ :name => 't_img'
320
+ )
321
+ assert_equal(
322
+ [],
323
+ @f.errors,
324
+ "Img#errors should raise no errors for good imgs"
325
+ )
326
+ end
327
+
328
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ require "#{::File.dirname __FILE__}/t"
7
+
8
+ class TC_Meta < Test::Unit::TestCase
9
+
10
+ def setup
11
+ end
12
+
13
+ def teardown
14
+ end
15
+
16
+ def test_owner
17
+ ss = Bike::Set::Static.new(
18
+ :item => {
19
+ '_owner' => {:klass => 'meta-owner'},
20
+ }
21
+ )
22
+ assert_instance_of(
23
+ Bike::Meta::Owner,
24
+ ss.item('_owner'),
25
+ "Set::Static#item('_owner') should be an instance of the meta field"
26
+ )
27
+
28
+ ss.item('_owner').load 'frank'
29
+ assert_equal(
30
+ 'frank',
31
+ ss.val('_owner'),
32
+ 'Meta::Owner#load should work like normal fields'
33
+ )
34
+ assert_equal(
35
+ 'frank',
36
+ ss[:owner],
37
+ 'Meta::Owner#load should update the [:owner] of the parent set'
38
+ )
39
+
40
+ ss.item('_owner').update 'carl'
41
+ assert_equal(
42
+ 'frank',
43
+ ss.val('_owner'),
44
+ 'Meta::Owner should not be updated'
45
+ )
46
+ assert(
47
+ !ss.item('_owner').pending?,
48
+ 'Meta::Owner should not be updated'
49
+ )
50
+ assert_equal(
51
+ 'frank',
52
+ ss[:owner],
53
+ 'Meta::Owner should not be updated'
54
+ )
55
+ end
56
+
57
+ end
@@ -0,0 +1,1516 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ require "#{::File.dirname __FILE__}/t"
7
+
8
+ class TC_Parser < Test::Unit::TestCase
9
+
10
+ def setup
11
+ end
12
+
13
+ def teardown
14
+ end
15
+
16
+ def test_scan_tokens
17
+ assert_equal(
18
+ {:tokens => ['foo', 'bar', 'baz']},
19
+ Bike::Parser.scan_tokens(StringScanner.new('foo bar baz')),
20
+ 'Parser.scan_tokens should be able to parse unquoted tokens into array'
21
+ )
22
+ assert_equal(
23
+ {:tokens => ['foo', 'bar', 'baz baz']},
24
+ Bike::Parser.scan_tokens(StringScanner.new('foo "bar" "baz baz"')),
25
+ 'Parser.scan_tokens should be able to parse quoted tokens'
26
+ )
27
+ assert_equal(
28
+ {:tokens => ['foo', 'bar', 'baz']},
29
+ Bike::Parser.scan_tokens(StringScanner.new("foo 'bar' baz")),
30
+ 'Parser.scan_tokens should be able to parse quoted tokens'
31
+ )
32
+
33
+ assert_equal(
34
+ {:tokens => ['foo', 'bar', 'baz']},
35
+ Bike::Parser.scan_tokens(StringScanner.new("foo 'bar' baz) qux")),
36
+ 'Parser.scan_tokens should stop scanning at an ending bracket'
37
+ )
38
+ assert_equal(
39
+ {:tokens => ['foo', 'bar (bar?)', 'baz']},
40
+ Bike::Parser.scan_tokens(StringScanner.new("foo 'bar (bar?)' baz) qux")),
41
+ 'Parser.scan_tokens should ignore brackets inside quoted tokens'
42
+ )
43
+ end
44
+
45
+ def test_parse_empty_tag
46
+ result = Bike::Parser.parse_html('hello $(foo = bar "baz baz") world')
47
+ assert_equal(
48
+ {'foo' => {:klass => 'bar', :tokens => ['baz baz']}},
49
+ result[:item],
50
+ 'Parser.parse_html should be able to parse empty bike tags'
51
+ )
52
+ assert_equal(
53
+ {:index => 'hello $(foo) world'},
54
+ result[:tmpl],
55
+ 'Parser.parse_html[:tmpl] should be a proper template'
56
+ )
57
+
58
+ result = Bike::Parser.parse_html <<'_html'
59
+ <h1>$(foo=bar "baz baz")</h1>
60
+ <p>$(bar=a b c)</p>
61
+ _html
62
+ assert_equal(
63
+ {
64
+ 'foo' => {:klass => 'bar', :tokens => ['baz baz']},
65
+ 'bar' => {:klass => 'a', :tokens => ['b', 'c']},
66
+ },
67
+ result[:item],
68
+ 'Parser.parse_html should be able to parse empty bike tags'
69
+ )
70
+ assert_equal(
71
+ {:index => <<'_html'},
72
+ <h1>$(foo)</h1>
73
+ <p>$(bar)</p>
74
+ _html
75
+ result[:tmpl],
76
+ 'Parser.parse_html[:tmpl] should be a proper template'
77
+ )
78
+ end
79
+
80
+ def test_parse_empty_tag_in_comment
81
+ html = 'hello <!-- $(foo = bar "baz baz") --> world'
82
+ result = Bike::Parser.parse_html html
83
+ assert_equal(
84
+ {},
85
+ result[:item],
86
+ 'Parser.parse_html should skip bike tags in a comment'
87
+ )
88
+ assert_equal(
89
+ {:index => html},
90
+ result[:tmpl],
91
+ 'Parser.parse_html should skip bike tags in a comment'
92
+ )
93
+
94
+ html = '<script><![CDATA[ $(foo = bar "baz baz") ]]></script>'
95
+ result = Bike::Parser.parse_html html
96
+ assert_equal(
97
+ {},
98
+ result[:item],
99
+ 'Parser.parse_html should skip bike tags in a comment'
100
+ )
101
+ assert_equal(
102
+ {:index => html},
103
+ result[:tmpl],
104
+ 'Parser.parse_html should skip bike tags in a comment'
105
+ )
106
+ end
107
+
108
+ def test_obscure_markup
109
+ result = Bike::Parser.parse_html('hello $(foo = bar $(baz=1) baz) world')
110
+ assert_equal(
111
+ {'foo' => {:klass => 'bar', :tokens => ['$(baz=1']}},
112
+ result[:item],
113
+ 'Parser.parse_html should not parse nested empty tag'
114
+ )
115
+ assert_equal(
116
+ {:index => 'hello $(foo) baz) world'},
117
+ result[:tmpl],
118
+ 'Parser.parse_html[:tmpl] should be a proper template'
119
+ )
120
+
121
+ result = Bike::Parser.parse_html('hello $(foo = bar baz world')
122
+ assert_equal(
123
+ {'foo' => {:klass => 'bar', :tokens => ['baz', 'world']}},
124
+ result[:item],
125
+ 'Parser.parse_html should be able to parse a tag that is not closed'
126
+ )
127
+ assert_equal(
128
+ {:index => 'hello $(foo)'},
129
+ result[:tmpl],
130
+ 'Parser.parse_html should be able to parse a tag that is not closed'
131
+ )
132
+
133
+ result = Bike::Parser.parse_html('hello $(foo = bar "baz"world)')
134
+ assert_equal(
135
+ {'foo' => {:klass => 'bar', :tokens => ['baz', 'world']}},
136
+ result[:item],
137
+ 'Parser.parse_html should be able to parse tokens without a delimiter'
138
+ )
139
+ assert_equal(
140
+ {:index => 'hello $(foo)'},
141
+ result[:tmpl],
142
+ 'Parser.parse_html should be able to parse tokens without a delimiter'
143
+ )
144
+
145
+ result = Bike::Parser.parse_html('hello $(foo = bar, "baz")')
146
+ assert_equal(
147
+ {'foo' => {:klass => 'bar', :options => ['baz']}},
148
+ result[:item],
149
+ 'The first token should be regarded as [:klass]'
150
+ )
151
+ end
152
+
153
+ def test_parse_token
154
+ assert_equal(
155
+ {:width => 160, :height => 120},
156
+ Bike::Parser.parse_token(nil, '160*120', {}),
157
+ 'Parser.parse_token should be able to parse dimension tokens'
158
+ )
159
+ assert_equal(
160
+ {:min => 1, :max => 32},
161
+ Bike::Parser.parse_token(nil, '1..32', {}),
162
+ 'Parser.parse_token should be able to parse range tokens'
163
+ )
164
+ assert_equal(
165
+ {:max => 32},
166
+ Bike::Parser.parse_token(nil, '..32', {}),
167
+ 'Parser.parse_token should be able to parse partial range tokens'
168
+ )
169
+ assert_equal(
170
+ {:min => 1},
171
+ Bike::Parser.parse_token(nil, '1..', {}),
172
+ 'Parser.parse_token should be able to parse partial range tokens'
173
+ )
174
+ assert_equal(
175
+ {:min => -32, :max => -1},
176
+ Bike::Parser.parse_token(nil, '-32..-1', {}),
177
+ 'Parser.parse_token should be able to parse minus range tokens'
178
+ )
179
+
180
+ assert_equal(
181
+ {:options => ['foo']},
182
+ Bike::Parser.parse_token(',', 'foo', {}),
183
+ 'Parser.parse_token should be able to parse option tokens'
184
+ )
185
+ assert_equal(
186
+ {:options => ['foo', 'bar']},
187
+ Bike::Parser.parse_token(',', 'bar', {:options => ['foo']}),
188
+ 'Parser.parse_token should be able to parse option tokens'
189
+ )
190
+
191
+ assert_equal(
192
+ {:default => 'bar'},
193
+ Bike::Parser.parse_token(':', 'bar', {}),
194
+ 'Parser.parse_token should be able to parse default tokens'
195
+ )
196
+ assert_equal(
197
+ {:defaults => ['bar', 'baz']},
198
+ Bike::Parser.parse_token(';', 'baz', {:defaults => ['bar']}),
199
+ 'Parser.parse_token should be able to parse defaults tokens'
200
+ )
201
+ end
202
+
203
+ def test_parse_options
204
+ result = Bike::Parser.parse_html('hello $(foo = bar , "baz baz", "world", hi qux)')
205
+ assert_equal(
206
+ {'foo' => {:klass => 'bar', :options => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
207
+ result[:item],
208
+ 'Parser.parse_html should be able to parse a sequence of CSV'
209
+ )
210
+ result = Bike::Parser.parse_html('hello $(foo = bar "baz baz", "world", hi qux)')
211
+ assert_equal(
212
+ {'foo' => {:klass => 'bar', :options => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
213
+ result[:item],
214
+ 'Parser.parse_html should be able to parse a sequence of CSV'
215
+ )
216
+ end
217
+
218
+ def test_parse_options_with_spaces
219
+ result = Bike::Parser.parse_html('hello $(foo = bar world, qux)')
220
+ assert_equal(
221
+ {'foo' => {:klass => 'bar', :options => ['world', 'qux']}},
222
+ result[:item],
223
+ 'Parser.parse_html should allow spaces after the comma'
224
+ )
225
+ result = Bike::Parser.parse_html('hello $(foo = bar world , qux)')
226
+ assert_equal(
227
+ {'foo' => {:klass => 'bar', :options => ['qux'], :tokens => ['world']}},
228
+ result[:item],
229
+ 'Parser.parse_html should not allow spaces before the comma'
230
+ )
231
+ result = Bike::Parser.parse_html('hello $(foo = bar "baz baz", "world", hi qux)')
232
+ assert_equal(
233
+ {'foo' => {:klass => 'bar', :options => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
234
+ result[:item],
235
+ 'Parser.parse_html should allow spaces after the comma'
236
+ )
237
+
238
+ result = Bike::Parser.parse_html(<<'_eos')
239
+ hello $(foo =
240
+ bar
241
+ "baz baz",
242
+ "world",
243
+ hi
244
+ qux)
245
+ _eos
246
+ assert_equal(
247
+ {'foo' => {:klass => 'bar', :options => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
248
+ result[:item],
249
+ 'Parser.parse_html should allow spaces after the comma'
250
+ )
251
+ end
252
+
253
+ def test_parse_defaults
254
+ result = Bike::Parser.parse_html('hello $(foo = bar ;"baz baz";"world";hi qux)')
255
+ assert_equal(
256
+ {'foo' => {:klass => 'bar', :defaults => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
257
+ result[:item],
258
+ 'Parser.parse_html should be able to parse a sequence of CSV as [:defaults]'
259
+ )
260
+ result = Bike::Parser.parse_html('hello $(foo = bar "baz baz";"world";hi qux)')
261
+ assert_equal(
262
+ {'foo' => {:klass => 'bar', :defaults => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
263
+ result[:item],
264
+ 'Parser.parse_html should be able to parse a sequence of CSV as [:defaults]'
265
+ )
266
+ end
267
+
268
+ def test_parse_defaults_with_spaces
269
+ result = Bike::Parser.parse_html('hello $(foo=bar world; qux)')
270
+ assert_equal(
271
+ {'foo' => {:klass => 'bar', :defaults => ['world', 'qux']}},
272
+ result[:item],
273
+ 'Parser.parse_html should allow spaces after the semicolon'
274
+ )
275
+ result = Bike::Parser.parse_html('hello $(foo=bar world ;qux)')
276
+ assert_equal(
277
+ {'foo' => {:klass => 'bar', :defaults => ['qux'], :tokens => ['world']}},
278
+ result[:item],
279
+ 'Parser.parse_html should not allow spaces before the semicolon'
280
+ )
281
+ result = Bike::Parser.parse_html('hello $(foo=bar "baz baz"; "world"; hi qux)')
282
+ assert_equal(
283
+ {'foo' => {:klass => 'bar', :defaults => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
284
+ result[:item],
285
+ 'Parser.parse_html should allow spaces after the comma'
286
+ )
287
+
288
+ result = Bike::Parser.parse_html(<<'_eos')
289
+ hello $(foo =
290
+ bar
291
+ "baz baz";
292
+ "world";
293
+ hi
294
+ qux)
295
+ _eos
296
+ assert_equal(
297
+ {'foo' => {:klass => 'bar', :defaults => ['baz baz', 'world', 'hi'], :tokens => ['qux']}},
298
+ result[:item],
299
+ 'Parser.parse_html should allow spaces after the comma'
300
+ )
301
+ end
302
+
303
+ def test_parse_meta_tag
304
+ result = Bike::Parser.parse_html <<'_html'
305
+ <html>
306
+ <meta name="bike-owner" content="frank" />
307
+ </html>
308
+ _html
309
+ assert_equal(
310
+ {
311
+ :tmpl => {
312
+ :index => <<'_html',
313
+ <html>
314
+ </html>
315
+ _html
316
+ },
317
+ :item => {},
318
+ :owner => 'frank',
319
+ :label => nil,
320
+ },
321
+ result,
322
+ 'Parser.parse_html should scrape meta vals from <meta>'
323
+ )
324
+
325
+ result = Bike::Parser.parse_html <<'_html'
326
+ <html>
327
+ <meta name="bike-owner" content="frank" />
328
+ <meta name="bike-group" content="bob,carl" />
329
+ <meta name="bike-foo" content="bar, baz" />
330
+ <meta name="bike-label" content="Qux" />
331
+ </html>
332
+ _html
333
+ assert_equal(
334
+ {
335
+ :tmpl => {
336
+ :index => <<'_html',
337
+ <html>
338
+ </html>
339
+ _html
340
+ },
341
+ :item => {},
342
+ :owner => 'frank',
343
+ :group => %w(bob carl),
344
+ :foo => %w(bar baz),
345
+ :label => 'Qux',
346
+ },
347
+ result,
348
+ 'Parser.parse_html should scrape meta vals from <meta>'
349
+ )
350
+ end
351
+
352
+ def test_parse_duplicate_tag
353
+ result = Bike::Parser.parse_html('hello $(foo = bar "baz baz") world $(foo=boo) $(foo)!')
354
+ assert_equal(
355
+ {'foo' => {:klass => 'boo'}},
356
+ result[:item],
357
+ 'definition tags are overridden by a preceding definition'
358
+ )
359
+ assert_equal(
360
+ {:index => 'hello $(foo) world $(foo) $(foo)!'},
361
+ result[:tmpl],
362
+ 'Parser.parse_html[:tmpl] should be a proper template'
363
+ )
364
+ end
365
+
366
+ def test_scan_inner_html
367
+ s = StringScanner.new 'bar</foo>bar'
368
+ inner_html, close_tag = Bike::Parser.scan_inner_html(s, 'foo')
369
+ assert_equal(
370
+ 'bar',
371
+ inner_html,
372
+ 'Parser.scan_inner_html should extract the inner html from the scanner'
373
+ )
374
+ assert_equal(
375
+ '</foo>',
376
+ close_tag,
377
+ 'Parser.scan_inner_html should extract the inner html from the scanner'
378
+ )
379
+
380
+ s = StringScanner.new '<foo>bar</foo></foo>'
381
+ inner_html, close_tag = Bike::Parser.scan_inner_html(s, 'foo')
382
+ assert_equal(
383
+ '<foo>bar</foo>',
384
+ inner_html,
385
+ 'Parser.scan_inner_html should be aware of nested tags'
386
+ )
387
+
388
+ s = StringScanner.new "baz\n <foo>bar</foo>\n</foo>"
389
+ inner_html, close_tag = Bike::Parser.scan_inner_html(s, 'foo')
390
+ assert_equal(
391
+ "baz\n <foo>bar</foo>\n",
392
+ inner_html,
393
+ 'Parser.scan_inner_html should be aware of nested tags'
394
+ )
395
+ end
396
+
397
+ def test_scan_comment
398
+ s = StringScanner.new 'baz -->'
399
+ inner_html, close_tag = Bike::Parser.scan_inner_html(s, '!--')
400
+ assert_equal(
401
+ 'baz',
402
+ inner_html,
403
+ 'Parser.scan_inner_html should parse html comments'
404
+ )
405
+ assert_equal(
406
+ ' -->',
407
+ close_tag,
408
+ 'Parser.scan_inner_html should parse html comments'
409
+ )
410
+
411
+ s = StringScanner.new "baz\n <!--bar-->\n-->"
412
+ inner_html, close_tag = Bike::Parser.scan_inner_html(s, '!--')
413
+ assert_equal(
414
+ "baz\n <!--bar-->\n",
415
+ inner_html,
416
+ 'Parser.scan_inner_html should parse nested comments'
417
+ )
418
+ end
419
+
420
+ def test_scan_cdata
421
+ s = StringScanner.new 'baz ]]>'
422
+ inner_html, close_tag = Bike::Parser.scan_inner_html(s, '<![CDATA[')
423
+ assert_equal(
424
+ 'baz',
425
+ inner_html,
426
+ 'Parser.scan_inner_html should parse CDATA section'
427
+ )
428
+ assert_equal(
429
+ ' ]]>',
430
+ close_tag,
431
+ 'Parser.scan_inner_html should parse CDATA section'
432
+ )
433
+ end
434
+
435
+ def test_parse_block_tag
436
+ result = Bike::Parser.parse_html <<'_html'
437
+ <ul class="app-blog" id="foo"><li>hello</li></ul>
438
+ _html
439
+ assert_equal(
440
+ {
441
+ 'foo' => {
442
+ :klass => 'set-dynamic',
443
+ :workflow => 'blog',
444
+ :tmpl => {
445
+ :index => <<'_tmpl'.chomp,
446
+ <ul class="app-blog" id="@(name)">$()</ul>
447
+ $(.navi)$(.submit)$(.action_create)
448
+ _tmpl
449
+ },
450
+ :item => {
451
+ 'default' => {
452
+ :label => nil,
453
+ :tmpl => {:index => '<li>hello</li>'},
454
+ :item => {},
455
+ },
456
+ },
457
+ },
458
+ },
459
+ result[:item],
460
+ 'Parser.parse_html should be able to parse block bike tags'
461
+ )
462
+ assert_equal(
463
+ {:index => '$(foo.message)$(foo)'},
464
+ result[:tmpl],
465
+ 'Parser.parse_html[:tmpl] should be a proper template'
466
+ )
467
+
468
+ result = Bike::Parser.parse_html <<'_html'
469
+ <ul class="app-blog" id="foo">
470
+ <li>hello</li>
471
+ </ul>
472
+ _html
473
+ assert_equal(
474
+ {
475
+ 'foo' => {
476
+ :klass => 'set-dynamic',
477
+ :workflow => 'blog',
478
+ :tmpl => {
479
+ :index => <<'_tmpl'.chomp,
480
+ <ul class="app-blog" id="@(name)">
481
+ $()</ul>
482
+ $(.navi)$(.submit)$(.action_create)
483
+ _tmpl
484
+ },
485
+ :item => {
486
+ 'default' => {
487
+ :label => nil,
488
+ :tmpl => {:index => " <li>hello</li>\n"},
489
+ :item => {},
490
+ },
491
+ }
492
+ },
493
+ },
494
+ result[:item],
495
+ 'Parser.parse_html should be able to parse block bike tags'
496
+ )
497
+ assert_equal(
498
+ {:index => '$(foo.message)$(foo)'},
499
+ result[:tmpl],
500
+ 'Parser.parse_html[:tmpl] should be a proper template'
501
+ )
502
+
503
+ result = Bike::Parser.parse_html <<'_html'
504
+ hello <ul class="app-blog" id="foo"><li>hello</li></ul> world
505
+ _html
506
+ assert_equal(
507
+ {
508
+ 'foo' => {
509
+ :klass => 'set-dynamic',
510
+ :workflow => 'blog',
511
+ :tmpl => {
512
+ :index => <<'_tmpl'.chomp,
513
+ <ul class="app-blog" id="@(name)">$()</ul>$(.navi)$(.submit)$(.action_create)
514
+ _tmpl
515
+ },
516
+ :item => {
517
+ 'default' => {
518
+ :label => nil,
519
+ :tmpl => {:index => '<li>hello</li>'},
520
+ :item => {},
521
+ },
522
+ },
523
+ },
524
+ },
525
+ result[:item],
526
+ 'Parser.parse_html should be able to parse block bike tags'
527
+ )
528
+ assert_equal(
529
+ {:index => <<'_html'},
530
+ hello$(foo.message)$(foo) world
531
+ _html
532
+ result[:tmpl],
533
+ 'Parser.parse_html[:tmpl] should be a proper template'
534
+ )
535
+
536
+ result = Bike::Parser.parse_html <<'_html'
537
+ hello <!-- cruel --> <ul class="app-blog" id="foo"><li>hello</li></ul> world
538
+ _html
539
+ assert_equal(
540
+ {
541
+ 'foo' => {
542
+ :klass => 'set-dynamic',
543
+ :workflow => 'blog',
544
+ :tmpl => {
545
+ :index => <<'_tmpl'.chomp,
546
+ <ul class="app-blog" id="@(name)">$()</ul>$(.navi)$(.submit)$(.action_create)
547
+ _tmpl
548
+ },
549
+ :item => {
550
+ 'default' => {
551
+ :label => nil,
552
+ :tmpl => {:index => '<li>hello</li>'},
553
+ :item => {},
554
+ },
555
+ },
556
+ },
557
+ },
558
+ result[:item],
559
+ 'Parser.parse_html should be able to parse block bike tags'
560
+ )
561
+ assert_equal(
562
+ {:index => <<'_html'},
563
+ hello <!-- cruel -->$(foo.message)$(foo) world
564
+ _html
565
+ result[:tmpl],
566
+ 'Parser.parse_html[:tmpl] should be a proper template'
567
+ )
568
+ end
569
+
570
+ def test_parse_block_tag_in_comment
571
+ [
572
+ <<'_html',
573
+ hello <!--<ul class="app-blog" id="test1"><li>hello</li></ul>--> world
574
+ _html
575
+ <<'_html',
576
+ <!--
577
+ <ul class="app-blog" id="test2">
578
+ <li>hello</li>
579
+ </ul>
580
+ -->
581
+ _html
582
+ <<'_html',
583
+ foo <!--
584
+ <ul class="app-blog" id="test3">
585
+ <li>hello</li>
586
+ </ul>
587
+ --> bar
588
+ _html
589
+ <<'_html',
590
+ foo <!--
591
+ <ul class="app-blog" id="test4">
592
+ <li>hello</li>
593
+ </ul>
594
+ --> bar
595
+ _html
596
+ <<'_html',
597
+ foo <!--
598
+ <ul class="app-blog" id="test5">
599
+ <!-- may_preview -->
600
+ <li>hello</li>
601
+ </ul>
602
+ --> bar
603
+ _html
604
+ <<'_html',
605
+ <![CDATA[
606
+ <ul class="app-blog" id="test6">
607
+ <!-- may_preview -->
608
+ <li>hello</li>
609
+ </ul>
610
+ ]]>
611
+ _html
612
+ <<'_html',
613
+ <!--
614
+ <ul class="app-blog" id="test7">
615
+ <!-- may_preview -->
616
+ <li>hello</li>
617
+ </ul>
618
+ _html
619
+ ].each {|html|
620
+ result = Bike::Parser.parse_html html
621
+ assert_equal(
622
+ {},
623
+ result[:item],
624
+ 'Parser.parse_html should skip bike tags in a comment'
625
+ )
626
+ assert_equal(
627
+ {:index => html},
628
+ result[:tmpl],
629
+ 'Parser.parse_html should skip bike tags in a comment'
630
+ )
631
+ }
632
+ end
633
+
634
+ def test_parse_block_tag_obsolete_runo_class
635
+ result = Bike::Parser.parse_html <<'_html'
636
+ <ul class="runo-blog" id="foo"><li>hello</li></ul>
637
+ _html
638
+ assert_equal(
639
+ {
640
+ 'foo' => {
641
+ :klass => 'set-dynamic',
642
+ :workflow => 'blog',
643
+ :tmpl => {
644
+ :index => <<'_html'.chomp,
645
+ <ul class="runo-blog" id="@(name)">$()</ul>
646
+ $(.navi)$(.submit)$(.action_create)
647
+ _html
648
+ },
649
+ :item => {
650
+ 'default' => {
651
+ :label => nil,
652
+ :tmpl => {:index => '<li>hello</li>'},
653
+ :item => {},
654
+ },
655
+ },
656
+ },
657
+ },
658
+ result[:item],
659
+ 'Parser.parse_html should be able to parse old runo tags'
660
+ )
661
+ end
662
+
663
+ def test_parse_block_tag_obsolete_body_class
664
+ result = Bike::Parser.parse_html <<'_html'
665
+ <ul class="app-blog" id="foo"><div>oops.</div><li class="body">hello</li></ul>
666
+ _html
667
+ assert_equal(
668
+ {
669
+ 'foo' => {
670
+ :klass => 'set-dynamic',
671
+ :workflow => 'blog',
672
+ :tmpl => {
673
+ :index => <<'_html'.chomp,
674
+ <ul class="app-blog" id="@(name)"><div>oops.</div>$()</ul>
675
+ $(.navi)$(.submit)$(.action_create)
676
+ _html
677
+ },
678
+ :item => {
679
+ 'default' => {
680
+ :label => nil,
681
+ :tmpl => {:index => '<li class="body">hello</li>'},
682
+ :item => {},
683
+ },
684
+ },
685
+ },
686
+ },
687
+ result[:item],
688
+ 'Parser.parse_html should be able to parse block bike tags'
689
+ )
690
+ end
691
+
692
+ def test_look_a_like_block_tag
693
+ result = Bike::Parser.parse_html <<'_html'
694
+ hello <ul class="not-app-blog" id="foo"><li>hello</li></ul> world
695
+ _html
696
+ assert_equal(
697
+ {:index => <<'_tmpl'},
698
+ hello <ul class="not-app-blog" id="foo"><li>hello</li></ul> world
699
+ _tmpl
700
+ result[:tmpl],
701
+ "Parser.parse_html[:tmpl] should skip a class which does not start with 'bike'"
702
+ )
703
+ end
704
+
705
+ def test_block_tags_with_options
706
+ result = Bike::Parser.parse_html <<'_html'
707
+ hello
708
+ <table class="app-blog" id="foo">
709
+ <!-- 1..20 barbaz -->
710
+ <tbody class="model"><!-- qux --><tr><th>$(bar=text)</th><th>$(baz=text)</th></tr></tbody>
711
+ </table>
712
+ world
713
+ _html
714
+ assert_equal(
715
+ {
716
+ 'foo' => {
717
+ :min => 1,
718
+ :max => 20,
719
+ :tokens => ['barbaz'],
720
+ :klass => 'set-dynamic',
721
+ :workflow => 'blog',
722
+ :tmpl => {
723
+ :index => <<'_tmpl'.chomp,
724
+ <table class="app-blog" id="@(name)">
725
+ <!-- 1..20 barbaz -->
726
+ $() </table>
727
+ $(.navi)$(.submit)$(.action_create)
728
+ _tmpl
729
+ },
730
+ :item => {
731
+ 'default' => {
732
+ :label => nil,
733
+ :tmpl => {
734
+ :index => <<'_tmpl',
735
+ <tbody class="model"><!-- qux --><tr><th>$(.a_update)$(bar)</a></th><th>$(baz)$(.hidden)</th></tr></tbody>
736
+ _tmpl
737
+ },
738
+ :item => {
739
+ 'bar' => {:klass => 'text'},
740
+ 'baz' => {:klass => 'text'},
741
+ },
742
+ },
743
+ },
744
+ },
745
+ },
746
+ result[:item],
747
+ 'Parser.parse_html should aware of <tbody class="model">'
748
+ )
749
+ end
750
+
751
+ def test_block_tags_with_tbody
752
+ result = Bike::Parser.parse_html <<'_html'
753
+ hello
754
+ <table class="app-blog" id="foo">
755
+ <thead><tr><th>BAR</th><th>BAZ</th></tr></thead>
756
+ <tbody class="model"><tr><th>$(bar=text)</th><th>$(baz=text)</th></tr></tbody>
757
+ </table>
758
+ world
759
+ _html
760
+ assert_equal(
761
+ {
762
+ 'foo' => {
763
+ :klass => 'set-dynamic',
764
+ :workflow => 'blog',
765
+ :tmpl => {
766
+ :index => <<'_tmpl'.chomp,
767
+ <table class="app-blog" id="@(name)">
768
+ <thead><tr><th>BAR</th><th>BAZ</th></tr></thead>
769
+ $() </table>
770
+ $(.navi)$(.submit)$(.action_create)
771
+ _tmpl
772
+ },
773
+ :item => {
774
+ 'default' => {
775
+ :label => nil,
776
+ :tmpl => {
777
+ :index => <<'_tmpl',
778
+ <tbody class="model"><tr><th>$(.a_update)$(bar)</a></th><th>$(baz)$(.hidden)</th></tr></tbody>
779
+ _tmpl
780
+ },
781
+ :item => {
782
+ 'bar' => {:klass => 'text'},
783
+ 'baz' => {:klass => 'text'},
784
+ },
785
+ },
786
+ },
787
+ },
788
+ },
789
+ result[:item],
790
+ 'Parser.parse_html should aware of <tbody class="model">'
791
+ )
792
+ assert_equal(
793
+ {:index => <<'_tmpl'},
794
+ hello
795
+ $(foo.message)$(foo)world
796
+ _tmpl
797
+ result[:tmpl],
798
+ 'Parser.parse_html[:tmpl] should be a proper template'
799
+ )
800
+ end
801
+
802
+ def test_parse_xml
803
+ result = Bike::Parser.parse_xml <<'_html'
804
+ <channel class="app-rss">
805
+ <link>@(href)</link>
806
+ <item class="model">
807
+ <title>$(title)</title>
808
+ </item>
809
+ </channel>
810
+ _html
811
+ assert_equal(
812
+ {
813
+ :label => nil,
814
+ :tmpl => {:index => '$(main)'},
815
+ :item => {
816
+ 'main' => {
817
+ :item => {
818
+ 'default' => {
819
+ :label => nil,
820
+ :item => {},
821
+ :tmpl => {
822
+ :index => <<'_xml'
823
+ <item>
824
+ <title>$(title)</title>
825
+ </item>
826
+ _xml
827
+ },
828
+ },
829
+ },
830
+ :tmpl => {
831
+ :index => <<'_xml',
832
+ <channel>
833
+ <link>@(href)</link>
834
+ $()</channel>
835
+ _xml
836
+ },
837
+ :klass => 'set-dynamic',
838
+ :workflow => 'rss',
839
+ }
840
+ },
841
+ },
842
+ result,
843
+ 'Parser.parse_html should aware of <item>'
844
+ )
845
+ end
846
+
847
+ def test_parse_item_label
848
+ result = Bike::Parser.parse_html <<'_html'
849
+ <ul class="app-blog" id="foo"><li title="Greeting">hello</li></ul>
850
+ _html
851
+ assert_equal(
852
+ 'Greeting',
853
+ result[:item]['foo'][:item]['default'][:label],
854
+ 'Parser.parse_html should pick up item labels from title attrs'
855
+ )
856
+
857
+ result = Bike::Parser.parse_html <<'_html'
858
+ <ul class="app-blog" id="foo"><!-- foo --><li title="Greeting">hello</li></ul>
859
+ _html
860
+ assert_equal(
861
+ 'Greeting',
862
+ result[:item]['foo'][:item]['default'][:label],
863
+ 'Parser.parse_html should pick up item labels from title attrs'
864
+ )
865
+
866
+ result = Bike::Parser.parse_html <<'_html'
867
+ <ul class="app-blog" id="foo"><!-- foo --><li><div title="Foo">hello</div></li></ul>
868
+ _html
869
+ assert_nil(
870
+ result[:item]['foo'][:item]['default'][:label],
871
+ 'Parser.parse_html should pick up item labels only from the first tags'
872
+ )
873
+ end
874
+
875
+ def test_parse_item_label_plural
876
+ result = Bike::Parser.parse_html <<'_html'
877
+ <ul class="app-blog" id="foo"><li title="tEntry, tEntries">hello</li></ul>
878
+ _html
879
+ assert_equal(
880
+ 'tEntry',
881
+ result[:item]['foo'][:item]['default'][:label],
882
+ 'Parser.parse_html should split plural item labels'
883
+ )
884
+ assert_equal(
885
+ ['tEntry', 'tEntries'],
886
+ Bike::I18n.msg['tEntry'],
887
+ 'Parser.parse_html should I18n.merge_msg! the plural item labels'
888
+ )
889
+
890
+ result = Bike::Parser.parse_html <<'_html'
891
+ <ul class="app-blog" id="foo"><li title="tFooFoo, BarBar, BazBaz">hello</li></ul>
892
+ _html
893
+ assert_equal(
894
+ 'tFooFoo',
895
+ result[:item]['foo'][:item]['default'][:label],
896
+ 'Parser.parse_html should split plural item labels'
897
+ )
898
+ assert_equal(
899
+ ['tFooFoo', 'BarBar', 'BazBaz'],
900
+ Bike::I18n.msg['tFooFoo'],
901
+ 'Parser.parse_html should I18n.merge_msg! the plural item labels'
902
+ )
903
+
904
+ result = Bike::Parser.parse_html <<'_html'
905
+ <ul class="app-blog" id="foo"><li title="tQux">hello</li></ul>
906
+ _html
907
+ assert_equal(
908
+ 'tQux',
909
+ result[:item]['foo'][:item]['default'][:label],
910
+ 'Parser.parse_html should split plural item labels'
911
+ )
912
+ assert_equal(
913
+ ['tQux', 'tQux', 'tQux', 'tQux'],
914
+ Bike::I18n.msg['tQux'],
915
+ 'Parser.parse_html should repeat a singular label to fill all possible plural forms'
916
+ )
917
+ end
918
+
919
+ def test_block_tags_with_nested_tbody
920
+ result = Bike::Parser.parse_html <<'_html'
921
+ hello
922
+ <table class="app-blog" id="foo">
923
+ <thead><tr><th>BAR</th><th>BAZ</th></tr></thead>
924
+ <tbody class="model"><tbody><tr><th>$(bar=text)</th><th>$(baz=text)</th></tr></tbody></tbody>
925
+ </table>
926
+ world
927
+ _html
928
+ assert_equal(
929
+ {
930
+ 'foo' => {
931
+ :klass => 'set-dynamic',
932
+ :workflow => 'blog',
933
+ :tmpl => {
934
+ :index => <<'_tmpl'.chomp,
935
+ <table class="app-blog" id="@(name)">
936
+ <thead><tr><th>BAR</th><th>BAZ</th></tr></thead>
937
+ $() </table>
938
+ $(.navi)$(.submit)$(.action_create)
939
+ _tmpl
940
+ },
941
+ :item => {
942
+ 'default' => {
943
+ :label => nil,
944
+ :tmpl => {
945
+ :index => <<'_tmpl',
946
+ <tbody class="model"><tbody><tr><th>$(.a_update)$(bar)</a></th><th>$(baz)$(.hidden)</th></tr></tbody></tbody>
947
+ _tmpl
948
+ },
949
+ :item => {
950
+ 'bar' => {:klass => 'text'},
951
+ 'baz' => {:klass => 'text'},
952
+ },
953
+ },
954
+ },
955
+ },
956
+ },
957
+ result[:item],
958
+ 'Parser.parse_html should aware of nested <tbody class="model">'
959
+ )
960
+ end
961
+
962
+ def test_nested_block_tags
963
+ result = Bike::Parser.parse_html <<'_html'
964
+ <ul class="app-blog" id="foo">
965
+ <li>
966
+ <ul class="app-blog" id="bar"><li>baz</li></ul>
967
+ </li>
968
+ </ul>
969
+ _html
970
+ assert_equal(
971
+ {
972
+ 'foo' => {
973
+ :klass => 'set-dynamic',
974
+ :workflow => 'blog',
975
+ :tmpl => {
976
+ :index => <<'_tmpl'.chomp,
977
+ <ul class="app-blog" id="@(name)">
978
+ $()</ul>
979
+ $(.navi)$(.submit)$(.action_create)
980
+ _tmpl
981
+ },
982
+ :item => {
983
+ 'default' => {
984
+ :label => nil,
985
+ :tmpl => {
986
+ :index => <<'_tmpl',
987
+ <li>
988
+ $(bar.message)$(.a_update)$(bar)$(.hidden)</a> </li>
989
+ _tmpl
990
+ },
991
+ :item => {
992
+ 'bar' => {
993
+ :klass => 'set-dynamic',
994
+ :workflow => 'blog',
995
+ :tmpl => {
996
+ :index => <<'_tmpl'.chomp,
997
+ <ul class="app-blog" id="@(name)">$()</ul>
998
+ $(.navi)$(.submit)$(.action_create)
999
+ _tmpl
1000
+ },
1001
+ :item => {
1002
+ 'default' => {
1003
+ :label => nil,
1004
+ :tmpl => {:index => '<li>baz</li>'},
1005
+ :item => {},
1006
+ },
1007
+ },
1008
+ },
1009
+ },
1010
+ },
1011
+ },
1012
+ },
1013
+ },
1014
+ result[:item],
1015
+ 'Parser.parse_html should be able to parse nested block bike tags'
1016
+ )
1017
+ assert_equal(
1018
+ {:index => '$(foo.message)$(foo)'},
1019
+ result[:tmpl],
1020
+ 'Parser.parse_html[:tmpl] should be a proper template'
1021
+ )
1022
+ end
1023
+
1024
+ def test_combination
1025
+ result = Bike::Parser.parse_html <<'_html'
1026
+ <html>
1027
+ <h1>$(title=text 32)</h1>
1028
+ <ul id="foo" class="app-blog">
1029
+ <li>
1030
+ $(subject=text 64)
1031
+ $(body=textarea 72*10)
1032
+ <ul><li>qux</li></ul>
1033
+ </li>
1034
+ </ul>
1035
+ </html>
1036
+ _html
1037
+ assert_equal(
1038
+ {
1039
+ 'title' => {:klass => 'text', :tokens => ['32']},
1040
+ 'foo' => {
1041
+ :klass => 'set-dynamic',
1042
+ :workflow => 'blog',
1043
+ :tmpl => {
1044
+ :index => <<'_tmpl'.chomp,
1045
+ <ul id="@(name)" class="app-blog">
1046
+ $() </ul>
1047
+ $(.navi)$(.submit)$(.action_create)
1048
+ _tmpl
1049
+ },
1050
+ :item => {
1051
+ 'default' => {
1052
+ :label => nil,
1053
+ :tmpl => {
1054
+ :index => <<'_tmpl',
1055
+ <li>
1056
+ $(.a_update)$(subject)</a>
1057
+ $(body)$(.hidden)
1058
+ <ul><li>qux</li></ul>
1059
+ </li>
1060
+ _tmpl
1061
+ },
1062
+ :item => {
1063
+ 'body' => {
1064
+ :width => 72,
1065
+ :height => 10,
1066
+ :klass => 'textarea',
1067
+ },
1068
+ 'subject' => {
1069
+ :tokens => ['64'],
1070
+ :klass => 'text',
1071
+ },
1072
+ },
1073
+ },
1074
+ },
1075
+ },
1076
+ },
1077
+ result[:item],
1078
+ 'Parser.parse_html should be able to parse combination of mixed bike tags'
1079
+ )
1080
+ assert_equal(
1081
+ {:index => <<'_tmpl'},
1082
+ <html>
1083
+ <h1>$(title)</h1>
1084
+ $(foo.message)$(foo)</html>
1085
+ _tmpl
1086
+ result[:tmpl],
1087
+ 'Parser.parse_html[:tmpl] should be a proper template'
1088
+ )
1089
+ end
1090
+
1091
+ def test_gsub_block
1092
+ match = nil
1093
+ result = Bike::Parser.gsub_block('a<div class="foo">bar</div>c', 'foo') {|open, inner, close|
1094
+ match = [open, inner, close]
1095
+ 'b'
1096
+ }
1097
+ assert_equal(
1098
+ 'abc',
1099
+ result,
1100
+ 'Parser.gsub_block should replace tag blocks of the matching class with the given value'
1101
+ )
1102
+ assert_equal(
1103
+ ['<div class="foo">', 'bar', '</div>'],
1104
+ match,
1105
+ 'Parser.gsub_block should pass the matching element to its block'
1106
+ )
1107
+
1108
+ result = Bike::Parser.gsub_block('<p><div class="foo">bar</div></p>', 'foo') {|open, inner, close|
1109
+ match = [open, inner, close]
1110
+ 'b'
1111
+ }
1112
+ assert_equal(
1113
+ '<p>b</p>',
1114
+ result,
1115
+ 'Parser.gsub_block should replace tag blocks of the matching class with the given value'
1116
+ )
1117
+ assert_equal(
1118
+ ['<div class="foo">', 'bar', '</div>'],
1119
+ match,
1120
+ 'Parser.gsub_block should pass the matching element to its block'
1121
+ )
1122
+
1123
+ result = Bike::Parser.gsub_block('a<p><div class="foo">bar</div></p>c', 'foo') {|open, inner, close|
1124
+ match = [open, inner, close]
1125
+ 'b'
1126
+ }
1127
+ assert_equal(
1128
+ 'a<p>b</p>c',
1129
+ result,
1130
+ 'Parser.gsub_block should replace tag blocks of the matching class with the given value'
1131
+ )
1132
+ assert_equal(
1133
+ ['<div class="foo">', 'bar', '</div>'],
1134
+ match,
1135
+ 'Parser.gsub_block should pass the matching element to its block'
1136
+ )
1137
+ end
1138
+
1139
+ def _test_gsub_action_tmpl(html)
1140
+ result = {}
1141
+ html = Bike::Parser.gsub_action_tmpl(html) {|id, action, *tmpl|
1142
+ result[:id] = id
1143
+ result[:action] = action
1144
+ result[:tmpl] = tmpl.join
1145
+ 'b'
1146
+ }
1147
+ [result, html]
1148
+ end
1149
+
1150
+ def test_gsub_action_tmpl
1151
+ result, html = _test_gsub_action_tmpl 'a<div class="foo-navi">Foo</div>c'
1152
+ assert_equal(
1153
+ {
1154
+ :id => 'foo',
1155
+ :action => :navi,
1156
+ :tmpl => '<div class="foo-navi">Foo</div>',
1157
+ },
1158
+ result,
1159
+ 'Parser.gsub_action_tmpl should yield action templates'
1160
+ )
1161
+ assert_equal(
1162
+ 'abc',
1163
+ html,
1164
+ 'Parser.gsub_action_tmpl should replace the action template with a value from the block'
1165
+ )
1166
+
1167
+ result, html = _test_gsub_action_tmpl 'a<div class="bar foo-navi">Foo</div>c'
1168
+ assert_equal(
1169
+ {
1170
+ :id => 'foo',
1171
+ :action => :navi,
1172
+ :tmpl => '<div class="bar foo-navi">Foo</div>',
1173
+ },
1174
+ result,
1175
+ 'Parser.gsub_action_tmpl should yield action templates'
1176
+ )
1177
+
1178
+ result, html = _test_gsub_action_tmpl 'a<div class="bar foo-navi baz">Foo</div>c'
1179
+ assert_equal(
1180
+ {
1181
+ :id => 'foo',
1182
+ :action => :navi,
1183
+ :tmpl => '<div class="bar foo-navi baz">Foo</div>',
1184
+ },
1185
+ result,
1186
+ 'Parser.gsub_action_tmpl should yield action templates'
1187
+ )
1188
+
1189
+ result, html = _test_gsub_action_tmpl 'a<div class="bar foo-done baz">Foo</div>c'
1190
+ assert_equal(
1191
+ {
1192
+ :id => 'foo',
1193
+ :action => :done,
1194
+ :tmpl => '<div class="bar foo-done baz">Foo</div>',
1195
+ },
1196
+ result,
1197
+ 'Parser.gsub_action_tmpl should yield action templates'
1198
+ )
1199
+ end
1200
+
1201
+ def test_gsub_action_tmpl_with_empty_id
1202
+ result, html = _test_gsub_action_tmpl 'a<div class="navi">Foo</div>c'
1203
+ assert_equal(
1204
+ {
1205
+ :id => nil,
1206
+ :action => :navi,
1207
+ :tmpl => '<div class="navi">Foo</div>',
1208
+ },
1209
+ result,
1210
+ 'Parser.gsub_action_tmpl should yield action templates'
1211
+ )
1212
+
1213
+ result, html = _test_gsub_action_tmpl 'a<div class="foo navi">Foo</div>c'
1214
+ assert_equal(
1215
+ {
1216
+ :id => nil,
1217
+ :action => :navi,
1218
+ :tmpl => '<div class="foo navi">Foo</div>',
1219
+ },
1220
+ result,
1221
+ 'Parser.gsub_action_tmpl should yield action templates'
1222
+ )
1223
+
1224
+ result, html = _test_gsub_action_tmpl 'a<div class="foo navi baz">Foo</div>c'
1225
+ assert_equal(
1226
+ {
1227
+ :id => nil,
1228
+ :action => :navi,
1229
+ :tmpl => '<div class="foo navi baz">Foo</div>',
1230
+ },
1231
+ result,
1232
+ 'Parser.gsub_action_tmpl should yield action templates'
1233
+ )
1234
+ end
1235
+
1236
+ def test_gsub_action_tmpl_with_ambiguous_klass
1237
+ result, html = _test_gsub_action_tmpl 'a<div class="not_navi">Foo</div>c'
1238
+ assert_equal(
1239
+ {},
1240
+ result,
1241
+ 'Parser.gsub_action_tmpl should ignore classes other than action, view, navi or submit'
1242
+ )
1243
+
1244
+ result, html = _test_gsub_action_tmpl 'a<div class="navi_bar">Foo</div>c'
1245
+ assert_equal(
1246
+ {
1247
+ :id => nil,
1248
+ :action => :navi_bar,
1249
+ :tmpl => '<div class="navi_bar">Foo</div>',
1250
+ },
1251
+ result,
1252
+ 'Parser.gsub_action_tmpl should yield an action template if the klass looks like special'
1253
+ )
1254
+ end
1255
+
1256
+ def test_action_tmpl_in_ss
1257
+ result = Bike::Parser.parse_html <<'_html'
1258
+ <html>
1259
+ <ul id="foo" class="app-blog">
1260
+ <li>$(subject=text)</li>
1261
+ </ul>
1262
+ <div class="foo-navi">bar</div>
1263
+ </html>
1264
+ _html
1265
+ assert_equal(
1266
+ <<'_tmpl',
1267
+ <div class="foo-navi">bar</div>
1268
+ _tmpl
1269
+ result[:item]['foo'][:tmpl][:navi],
1270
+ 'Parser.parse_html should parse action templates in the html'
1271
+ )
1272
+ assert_equal(
1273
+ {:index => <<'_tmpl'},
1274
+ <html>
1275
+ $(foo.message)$(foo)$(foo.navi)</html>
1276
+ _tmpl
1277
+ result[:tmpl],
1278
+ 'Parser.parse_html should replace action templates with proper tags'
1279
+ )
1280
+ end
1281
+
1282
+ def test_action_tmpl_in_ss_with_nil_id
1283
+ result = Bike::Parser.parse_html <<'_html'
1284
+ <html>
1285
+ <ul id="main" class="app-blog">
1286
+ <li>$(subject=text)</li>
1287
+ </ul>
1288
+ <div class="navi">bar</div>
1289
+ </html>
1290
+ _html
1291
+ assert_equal(
1292
+ <<'_tmpl',
1293
+ <div class="navi">bar</div>
1294
+ _tmpl
1295
+ result[:item]['main'][:tmpl][:navi],
1296
+ "Parser.parse_html should set action templates to item['main'] by default"
1297
+ )
1298
+ assert_equal(
1299
+ {:index => <<'_tmpl'},
1300
+ <html>
1301
+ $(main.message)$(main)$(main.navi)</html>
1302
+ _tmpl
1303
+ result[:tmpl],
1304
+ "Parser.parse_html should set action templates to item['main'] by default"
1305
+ )
1306
+ end
1307
+
1308
+ def test_action_tmpl_in_ss_with_non_existent_id
1309
+ result = Bike::Parser.parse_html <<'_html'
1310
+ <html>
1311
+ <ul id="main" class="app-blog">
1312
+ <li>$(subject=text)</li>
1313
+ </ul>
1314
+ <div class="non_existent-navi">bar</div>
1315
+ </html>
1316
+ _html
1317
+ assert_nil(
1318
+ result[:item]['non_existent'],
1319
+ 'Parser.parse_html should ignore the action template without a corresponding SD'
1320
+ )
1321
+ assert_equal(
1322
+ {:index => <<'_tmpl'},
1323
+ <html>
1324
+ $(main.message)$(main) <div class="non_existent-navi">bar</div>
1325
+ </html>
1326
+ _tmpl
1327
+ result[:tmpl],
1328
+ 'Parser.parse_html should ignore the action template without a corresponding SD'
1329
+ )
1330
+ end
1331
+
1332
+ def test_action_tmpl_in_ss_with_nested_action_tmpl
1333
+ result = Bike::Parser.parse_html <<'_html'
1334
+ <html>
1335
+ <ul id="foo" class="app-blog">
1336
+ <li>$(subject=text)</li>
1337
+ </ul>
1338
+ <div class="foo-navi"><span class="navi_prev">prev</span></div>
1339
+ </html>
1340
+ _html
1341
+ assert_equal(
1342
+ <<'_html',
1343
+ <div class="foo-navi">$(.navi_prev)</div>
1344
+ _html
1345
+ result[:item]['foo'][:tmpl][:navi],
1346
+ 'Parser.parse_html should parse nested action templates'
1347
+ )
1348
+ assert_equal(
1349
+ '<span class="navi_prev">prev</span>',
1350
+ result[:item]['foo'][:tmpl][:navi_prev],
1351
+ 'Parser.parse_html should parse nested action templates'
1352
+ )
1353
+ assert_equal(
1354
+ {
1355
+ :index => <<'_html'.chomp,
1356
+ <ul id="@(name)" class="app-blog">
1357
+ $() </ul>
1358
+ $(.submit)$(.action_create)
1359
+ _html
1360
+ :navi => <<'_html',
1361
+ <div class="foo-navi">$(.navi_prev)</div>
1362
+ _html
1363
+ :navi_prev => '<span class="navi_prev">prev</span>',
1364
+ },
1365
+ result[:item]['foo'][:tmpl],
1366
+ 'Parser.parse_html should parse nested action templates'
1367
+ )
1368
+
1369
+ result = Bike::Parser.parse_html <<'_html'
1370
+ <html>
1371
+ <ul id="foo" class="app-blog">
1372
+ <li>$(subject=text)</li>
1373
+ </ul>
1374
+ <div class="foo-navi"><span class="bar-navi_prev">prev</span></div>
1375
+ </html>
1376
+ _html
1377
+ assert_equal(
1378
+ '<span class="bar-navi_prev">prev</span>',
1379
+ result[:item]['foo'][:tmpl][:navi_prev],
1380
+ 'Parser.parse_html should ignore the id of a nested action template'
1381
+ )
1382
+ end
1383
+
1384
+ def test_action_tmpl_in_sd
1385
+ result = Bike::Parser.parse_html <<'_html'
1386
+ <ul id="foo" class="app-blog">
1387
+ <li class="model">$(text)</li>
1388
+ <div class="navi">bar</div>
1389
+ </ul>
1390
+ _html
1391
+ assert_equal(
1392
+ <<'_html',
1393
+ <div class="navi">bar</div>
1394
+ _html
1395
+ result[:item]['foo'][:tmpl][:navi],
1396
+ 'Parser.parse_html should parse action templates in sd[:tmpl]'
1397
+ )
1398
+ assert_match(
1399
+ %r{\$\(\.navi\)},
1400
+ result[:item]['foo'][:tmpl][:index],
1401
+ 'Parser.parse_html should parse action templates in sd[:tmpl]'
1402
+ )
1403
+ end
1404
+
1405
+ def test_action_tmpl_in_sd_with_nested_action_tmpl
1406
+ result = Bike::Parser.parse_html <<'_html'
1407
+ <ul id="foo" class="app-blog">
1408
+ <li class="model">$(text)</li>
1409
+ <div class="navi"><span class="navi_prev">prev</span></div>
1410
+ </ul>
1411
+ _html
1412
+ assert_equal(
1413
+ <<'_html',
1414
+ <div class="navi">$(.navi_prev)</div>
1415
+ _html
1416
+ result[:item]['foo'][:tmpl][:navi],
1417
+ 'Parser.parse_html should parse nested action templates in sd[:tmpl]'
1418
+ )
1419
+ assert_equal(
1420
+ '<span class="navi_prev">prev</span>',
1421
+ result[:item]['foo'][:tmpl][:navi_prev],
1422
+ 'Parser.parse_html should parse nested action templates in sd[:tmpl]'
1423
+ )
1424
+ end
1425
+
1426
+ def test_action_tmpl_in_comment
1427
+ result = Bike::Parser.parse_html <<'_html'
1428
+ <ul id="foo" class="app-blog">
1429
+ <li class="model">$(text)</li>
1430
+ <!--
1431
+ <div class="navi"><span class="navi_prev">prev</span></div>
1432
+ -->
1433
+ </ul>
1434
+ _html
1435
+ assert_nil(
1436
+ result[:item]['foo'][:tmpl][:navi],
1437
+ 'Parser.parse_html should skip action templates in a comment'
1438
+ )
1439
+
1440
+ result = Bike::Parser.parse_html <<'_html'
1441
+ <ul id="foo" class="app-blog">
1442
+ <li class="model">$(text)</li>
1443
+ <div class="navi"><!--<span class="navi_prev">prev</span>--></div>
1444
+ </ul>
1445
+ _html
1446
+ assert_equal(
1447
+ <<'_html',
1448
+ <div class="navi"><!--<span class="navi_prev">prev</span>--></div>
1449
+ _html
1450
+ result[:item]['foo'][:tmpl][:navi],
1451
+ 'Parser.parse_html should skip action templates in a comment'
1452
+ )
1453
+ end
1454
+
1455
+ def test_supplement_sd
1456
+ result = Bike::Parser.parse_html <<'_html'
1457
+ <ul id="foo" class="app-blog">
1458
+ <li class="model">$(text)</li>
1459
+ </ul>
1460
+ _html
1461
+ assert_match(
1462
+ /\$\(\.navi\)/,
1463
+ result[:item]['foo'][:tmpl][:index],
1464
+ 'Parser.supplement_sd should supplement sd[:tmpl] with default menus'
1465
+ )
1466
+
1467
+ result = Bike::Parser.parse_html <<'_html'
1468
+ <ul id="foo" class="app-blog">
1469
+ <div class="navi">bar</div>
1470
+ <li class="model">$(text)</li>
1471
+ </ul>
1472
+ _html
1473
+ assert_no_match(
1474
+ /\$\(\.navi\).*\$\(\.navi\)/m,
1475
+ result[:item]['foo'][:tmpl][:index],
1476
+ 'Parser.supplement_sd should not supplement sd[:tmpl] when it already has the menu'
1477
+ )
1478
+
1479
+ result = Bike::Parser.parse_html <<'_html'
1480
+ <div class="foo-navi">bar</div>
1481
+ <ul id="foo" class="app-blog">
1482
+ <li class="model">$(text)</li>
1483
+ </ul>
1484
+ _html
1485
+ assert_no_match(
1486
+ /\$\(\.navi\)/,
1487
+ result[:item]['foo'][:tmpl][:index],
1488
+ 'Parser.supplement_sd should not supplement sd[:tmpl] when it already has the menu'
1489
+ )
1490
+ end
1491
+
1492
+ def test_supplement_ss
1493
+ result = Bike::Parser.parse_html <<'_html'
1494
+ <ul id="foo" class="app-blog">
1495
+ <li class="model">$(text)</li>
1496
+ </ul>
1497
+ _html
1498
+ assert_match(
1499
+ /\$\(\.a_update\)/,
1500
+ result[:item]['foo'][:item]['default'][:tmpl][:index],
1501
+ 'Parser.supplement_ss should supplement ss[:tmpl] with default menus'
1502
+ )
1503
+
1504
+ result = Bike::Parser.parse_html <<'_html'
1505
+ <ul id="foo" class="app-blog">
1506
+ <li class="model">$(text) $(.action_update)</li>
1507
+ </ul>
1508
+ _html
1509
+ assert_no_match(
1510
+ /\$\(\.a_update\)/,
1511
+ result[:item]['foo'][:item]['default'][:tmpl][:index],
1512
+ 'Parser.supplement_ss should not supplement ss[:tmpl] when it already has the menu'
1513
+ )
1514
+ end
1515
+
1516
+ end