erector 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/README.txt +17 -3
  2. data/VERSION.yml +2 -2
  3. data/bin/erector +1 -1
  4. data/lib/erector.rb +22 -2
  5. data/lib/erector/after_initialize.rb +34 -0
  6. data/lib/erector/caching.rb +93 -0
  7. data/lib/erector/convenience.rb +58 -0
  8. data/lib/erector/dependencies.rb +24 -0
  9. data/lib/erector/dependency.rb +21 -0
  10. data/lib/erector/{erect.rb → erect/erect.rb} +14 -4
  11. data/lib/erector/{erected.rb → erect/erected.rb} +6 -4
  12. data/lib/erector/{indenting.rb → erect/indenting.rb} +0 -0
  13. data/lib/erector/{rhtml.treetop → erect/rhtml.treetop} +51 -11
  14. data/lib/erector/errors.rb +12 -0
  15. data/lib/erector/extensions/hash.rb +21 -0
  16. data/lib/erector/externals.rb +88 -24
  17. data/lib/erector/html.rb +352 -0
  18. data/lib/erector/inline.rb +5 -5
  19. data/lib/erector/jquery.rb +36 -0
  20. data/lib/erector/mixin.rb +3 -5
  21. data/lib/erector/needs.rb +94 -0
  22. data/lib/erector/output.rb +117 -0
  23. data/lib/erector/rails.rb +2 -2
  24. data/lib/erector/rails/extensions/action_controller.rb +5 -3
  25. data/lib/erector/rails/extensions/rails_helpers.rb +159 -0
  26. data/lib/erector/rails/extensions/rails_widget.rb +98 -56
  27. data/lib/erector/rails/rails_form_builder.rb +8 -4
  28. data/lib/erector/rails/rails_version.rb +2 -2
  29. data/lib/erector/rails/template_handlers/ert_handler.rb +1 -1
  30. data/lib/erector/rails/template_handlers/rb_handler.rb +42 -1
  31. data/lib/erector/raw_string.rb +2 -2
  32. data/lib/erector/sass.rb +22 -0
  33. data/lib/erector/widget.rb +100 -653
  34. data/lib/erector/widgets.rb +1 -0
  35. data/lib/erector/widgets/external_renderer.rb +51 -0
  36. data/lib/erector/widgets/page.rb +45 -63
  37. data/lib/erector/widgets/table.rb +9 -1
  38. data/spec/erect/erect_rails_spec.rb +19 -17
  39. data/spec/erect/erect_spec.rb +11 -1
  40. data/spec/erect/erected_spec.rb +76 -5
  41. data/spec/erect/rhtml_parser_spec.rb +11 -1
  42. data/spec/erector/caching_spec.rb +267 -0
  43. data/spec/erector/convenience_spec.rb +258 -0
  44. data/spec/erector/dependency_spec.rb +46 -0
  45. data/spec/erector/externals_spec.rb +233 -0
  46. data/spec/erector/html_spec.rb +508 -0
  47. data/spec/erector/indentation_spec.rb +84 -24
  48. data/spec/erector/inline_spec.rb +19 -8
  49. data/spec/erector/jquery_spec.rb +35 -0
  50. data/spec/erector/mixin_spec.rb +1 -1
  51. data/spec/erector/needs_spec.rb +120 -0
  52. data/spec/erector/output_spec.rb +199 -0
  53. data/spec/erector/sample-file.txt +1 -0
  54. data/spec/erector/sass_spec.rb +33 -0
  55. data/spec/erector/widget_spec.rb +113 -932
  56. data/spec/erector/widgets/field_table_spec.rb +6 -6
  57. data/spec/erector/widgets/form_spec.rb +3 -3
  58. data/spec/erector/widgets/page_spec.rb +52 -6
  59. data/spec/erector/widgets/table_spec.rb +4 -4
  60. data/spec/spec_helper.rb +70 -29
  61. metadata +56 -19
  62. data/lib/erector/rails/extensions/rails_widget/rails_helpers.rb +0 -137
  63. data/spec/core_spec_suite.rb +0 -3
  64. data/spec/erector/external_spec.rb +0 -110
  65. data/spec/rails_spec_suite.rb +0 -3
  66. data/spec/spec.opts +0 -1
  67. data/spec/spec_suite.rb +0 -40
@@ -0,0 +1,46 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+ require 'benchmark'
3
+ require 'active_support' # for Symbol#to_proc
4
+
5
+ module DependencySpec
6
+ describe Erector::Dependency do
7
+ it "can be constructed with type and text" do
8
+ x = Erector::Dependency.new(:foo, "abc")
9
+ x.type.should == :foo
10
+ x.text.should == "abc"
11
+ x.options.should == {}
12
+ end
13
+
14
+ it "can be constructed with type, text, and options" do
15
+ x = Erector::Dependency.new(:foo, "abc", {:bar => 7})
16
+ x.options.should == {:bar => 7}
17
+ end
18
+
19
+ it "can be constructed with a file" do
20
+ file = File.new("#{File.dirname(__FILE__)}/sample-file.txt")
21
+ x = Erector::Dependency.new(:foo, file)
22
+ x.text.should == "sample file contents, 2 + 2 = \#{2 + 2}\n"
23
+ end
24
+
25
+ it "can be constructed with a file and interpolate the text" do
26
+ file = File.new("#{File.dirname(__FILE__)}/sample-file.txt")
27
+ x = Erector::Dependency.new(:foo, file, :interpolate => true)
28
+ x.text.should == "sample file contents, 2 + 2 = 4\n"
29
+ end
30
+
31
+ it "is equal to an identical external" do
32
+ x = Erector::Dependency.new(:foo, "abc", {:bar => 7})
33
+ y = Erector::Dependency.new(:foo, "abc", {:bar => 7})
34
+ x.should == y
35
+ [x].should include(y)
36
+ end
37
+
38
+ it "is not equal to an otherwise identical external with different options" do
39
+ x = Erector::Dependency.new(:foo, "abc")
40
+ y = Erector::Dependency.new(:foo, "abc", {:bar => 7})
41
+ x.should_not == y
42
+ [x].should_not include(y)
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,233 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+ require 'benchmark'
3
+ require 'active_support' # for Symbol#to_proc
4
+
5
+ module ExternalsSpec
6
+
7
+ describe "adding dependencies" do
8
+ before do
9
+ @args = [:what, :ever, :is, :passed]
10
+ @result = Erector::Dependency.new :js, '/foo.js'
11
+ @result2 = Erector::Dependency.new :css, '/foo.css'
12
+ end
13
+
14
+ after do
15
+ Erector::Widget.my_dependencies.clear
16
+ end
17
+
18
+ it "calls #interpret_args with given arguments and passes result to #push_dependency" do
19
+ mock(Erector::Widget).interpret_args(*@args).returns(@result)
20
+ Erector::Widget.depends_on *@args
21
+ end
22
+
23
+ it "starts out with no items in @_dependencies" do
24
+ class Quesadilla < Erector::Widget
25
+ end
26
+ Quesadilla.my_dependencies.should == []
27
+ end
28
+
29
+
30
+ describe '#interpret_args' do
31
+
32
+ class Test
33
+ include Erector::Externals
34
+ end
35
+
36
+ it "will infer that a .js extension is javascript" do
37
+ x = Test.send :interpret_args, ('/path/to/a.js')
38
+ x.text.should == '/path/to/a.js'
39
+ x.type.should == :js
40
+ end
41
+
42
+ it "will infer that a .css extension is a stylesheet" do
43
+ x = Test.send :interpret_args, ('/path/to/a.css')
44
+ x.text.should == '/path/to/a.css'
45
+ x.type.should == :css
46
+ end
47
+
48
+ it "will capture render options when just a file is mentioned" do
49
+ x = Test.send(:interpret_args, '/path/to/a.css', :render=>:link)
50
+ x.text.should == '/path/to/a.css'
51
+ x.type.should == :css
52
+ x.options.should == {:render=>:link} # could also be "embed"
53
+ end
54
+
55
+ it "embeds javascript" do
56
+ x = Test.send :interpret_args, :js, "alert('foo')"
57
+ x.text.should == "alert('foo')"
58
+ x.type.should == :js
59
+ end
60
+
61
+ it "guesses Javascript type from .js" do
62
+ x = Test.send :interpret_args, "/script/foo.js"
63
+ x.text.should == "/script/foo.js"
64
+ x.type.should == :js
65
+ end
66
+
67
+ it "guesses CSS type from .css" do
68
+ x = Test.send :interpret_args, "/script/foo.css"
69
+ x.text.should == "/script/foo.css"
70
+ x.type.should == :css
71
+ end
72
+
73
+ it "add multiple files without an options hash" do
74
+ x = Test.send :interpret_args, :js, "/script/foo.js", "/script/bar.js"
75
+ x.size.should == 2
76
+ x[0].text.should == "/script/foo.js"
77
+ x[0].type.should == :js
78
+ x[1].text.should == "/script/bar.js"
79
+ x[1].type.should == :js
80
+ end
81
+
82
+ it "add multiple files with an options hash" do
83
+ x = Test.send :interpret_args, :js, "/script/foo.js", "/script/bar.js", :embed=>true
84
+ x.size.should == 2
85
+ x[0].text.should == "/script/foo.js"
86
+ x[0].type.should == :js
87
+ x[0].options[:embed].should == true
88
+ x[1].text.should == "/script/bar.js"
89
+ x[1].type.should == :js
90
+ x[1].options[:embed].should == true
91
+ end
92
+
93
+ it "adds multiple files from hash" do
94
+ x = Test.send :interpret_args, :js => ["foo.js", "bar.js"]
95
+ x.size.should == 2
96
+ x[0].text.should == "foo.js"
97
+ x[0].type.should == :js
98
+ x[1].text.should == "bar.js"
99
+ x[1].type.should == :js
100
+ end
101
+ it "adds multiple files from hash of different types" do
102
+ x = Test.send :interpret_args, :js => ["foo.js", "bar.js"], :css=>'file.css'
103
+ x.size.should == 3
104
+ x.map(&:text).include?('foo.js')
105
+ x.map(&:text).include?('bar.js')
106
+ x.map(&:text).include?('file.css')
107
+ end
108
+ it "adds multiple files from hash and preserves the options" do
109
+ x = Test.send :interpret_args, :js => ["foo.js", "bar.js"], :foo=>false
110
+ x.size.should == 2
111
+ x[0].text.should == "foo.js"
112
+ x[0].type.should == :js
113
+ x[0].options.should == {:foo=>false}
114
+ x[1].text.should == "bar.js"
115
+ x[1].type.should == :js
116
+ x[1].options.should == {:foo=>false}
117
+ end
118
+ end
119
+
120
+ end
121
+
122
+ describe 'extracting the dependencies (integration tests)' do
123
+ attr_reader :HotSauce, :SourCream, :Tabasco
124
+
125
+ before do
126
+ @HotSauce = Class.new(Erector::Widget) do
127
+ depends_on :css, "/css/tapatio.css", :media => "print"
128
+ depends_on :css, "/css/salsa_picante.css"
129
+ depends_on :js, "/lib/jquery.js"
130
+ depends_on :js, "/lib/picante.js"
131
+ end
132
+ @SourCream = Class.new(Erector::Widget) do
133
+ depends_on :css, "/css/sourcream.css"
134
+ depends_on :js, "/lib/jquery.js"
135
+ depends_on :js, "/lib/dairy.js"
136
+ end
137
+ @Tabasco = Class.new(self.HotSauce) do
138
+ depends_on :js, "tabasco.js"
139
+ depends_on :css, "/css/salsa_picante.css"
140
+ end
141
+ end
142
+
143
+ it "can be fetched via the type" do
144
+ self.HotSauce.dependencies(:css).map(&:text).should == [
145
+ "/css/tapatio.css",
146
+ "/css/salsa_picante.css",
147
+ ]
148
+ end
149
+
150
+ it "can be filtered via the class" do
151
+ self.SourCream.dependencies(:css).map(&:text).should == [
152
+ "/css/sourcream.css",
153
+ ]
154
+ end
155
+
156
+ it "grabs dependencies from superclasses too" do
157
+ self.Tabasco.dependencies(:js).map(&:text).should == ["/lib/jquery.js", "/lib/picante.js", "tabasco.js"]
158
+ end
159
+
160
+ it "retains the options" do
161
+ self.HotSauce.dependencies(:css).map(&:options).should == [
162
+ {:media => "print"},
163
+ {}
164
+ ]
165
+ end
166
+
167
+ it "removes duplicates" do
168
+ self.Tabasco.dependencies(:css).map(&:text).should == [
169
+ "/css/tapatio.css",
170
+ "/css/salsa_picante.css",
171
+ ]
172
+ end
173
+
174
+ it "works with strings or symbols" do
175
+ self.HotSauce.dependencies("css").map(&:text).should == [
176
+ "/css/tapatio.css",
177
+ "/css/salsa_picante.css",
178
+ ]
179
+ end
180
+
181
+ class Taco < Erector::Widget
182
+ depends_on :filling, "beef"
183
+ depends_on :filling, "beef", :media => "print"
184
+ end
185
+
186
+ it "considers options when removing duplicates" do
187
+ Taco.dependencies(:filling).map(&:text).should == ["beef", "beef"]
188
+ end
189
+
190
+
191
+ end
192
+
193
+ describe "rendering with externals" do
194
+ class Dinner < Erector::Widget
195
+ external :js, "/dinner.js"
196
+
197
+ def content
198
+ span "dinner"
199
+ widget Dessert
200
+ end
201
+ end
202
+
203
+ class Dessert < Erector::Widget
204
+ external :js, "/dessert.js"
205
+ external :css, "/dessert.css"
206
+
207
+ def content
208
+ span "dessert"
209
+ end
210
+ end
211
+
212
+ it "#render_with_externals sticks the externals for all its rendered sub-widgets at the end of the output buffer" do
213
+ s = Dinner.new.render_with_externals
214
+ s.to_s.should ==
215
+ "<span>dinner</span>" +
216
+ "<span>dessert</span>" +
217
+ "<link href=\"/dessert.css\" media=\"all\" rel=\"stylesheet\" type=\"text/css\" />" +
218
+ "<script src=\"/dinner.js\" type=\"text/javascript\"></script>" +
219
+ "<script src=\"/dessert.js\" type=\"text/javascript\"></script>"
220
+ end
221
+
222
+ it "#render_externals returns externals for all rendered sub-widgets to an output buffer" do
223
+ widget = Dinner.new
224
+ widget.to_html
225
+ widget.render_externals.to_s.should ==
226
+ "<link href=\"/dessert.css\" media=\"all\" rel=\"stylesheet\" type=\"text/css\" />" +
227
+ "<script src=\"/dinner.js\" type=\"text/javascript\"></script>" +
228
+ "<script src=\"/dessert.js\" type=\"text/javascript\"></script>"
229
+ end
230
+ end
231
+
232
+
233
+ end
@@ -0,0 +1,508 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+
3
+ describe Erector::HTML do
4
+ include Erector::Mixin
5
+
6
+ describe ".all_tags" do
7
+ it "returns set of full and empty tags" do
8
+ Erector::Widget.all_tags.class.should == Array
9
+ Erector::Widget.all_tags.should == Erector::Widget.full_tags + Erector::Widget.empty_tags
10
+ end
11
+ end
12
+
13
+ describe "#instruct" do
14
+ it "when passed no arguments; returns an XML declaration with version 1 and utf-8" do
15
+ # version must precede encoding, per XML 1.0 4th edition (section 2.8)
16
+ erector { instruct }.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
17
+ end
18
+ end
19
+
20
+ describe "#element" do
21
+ context "when receiving one argument" do
22
+ it "returns an empty element" do
23
+ erector { element('div') }.should == "<div></div>"
24
+ end
25
+ end
26
+
27
+ context "with a attribute hash" do
28
+ it "returns an empty element with the attributes" do
29
+ html = erector do
30
+ element(
31
+ 'div',
32
+ :class => "foo bar",
33
+ :style => "display: none; color: white; float: left;",
34
+ :nil_attribute => nil
35
+ )
36
+ end
37
+ doc = Nokogiri::HTML(html)
38
+ div = doc.at('div')
39
+ div[:class].should == "foo bar"
40
+ div[:style].should == "display: none; color: white; float: left;"
41
+ div[:nil_attribute].should be_nil
42
+ end
43
+ end
44
+
45
+ context "with an array of CSS classes" do
46
+ it "returns a tag with the classes separated" do
47
+ erector do
48
+ element('div', :class => [:foo, :bar])
49
+ end.should == "<div class=\"foo bar\"></div>";
50
+ end
51
+ end
52
+
53
+ context "with an array of CSS classes as strings" do
54
+ it "returns a tag with the classes separated" do
55
+ erector do
56
+ element('div', :class => ['foo', 'bar'])
57
+ end.should == "<div class=\"foo bar\"></div>";
58
+ end
59
+ end
60
+
61
+ context "with a CSS class which is a string" do
62
+ it "just use that as the attribute value" do
63
+ erector do
64
+ element('div', :class => "foo bar")
65
+ end.should == "<div class=\"foo bar\"></div>";
66
+ end
67
+ end
68
+
69
+ context "with an empty array of CSS classes" do
70
+ it "does not emit a class attribute" do
71
+ erector do
72
+ element('div', :class => [])
73
+ end.should == "<div></div>"
74
+ end
75
+ end
76
+
77
+ context "with many attributes" do
78
+ it "alphabetize them" do
79
+ erector do
80
+ empty_element('foo', :alpha => "", :betty => "5", :aardvark => "tough",
81
+ :carol => "", :demon => "", :erector => "", :pi => "3.14", :omicron => "", :zebra => "", :brain => "")
82
+ end.should == "<foo aardvark=\"tough\" alpha=\"\" betty=\"5\" brain=\"\" carol=\"\" demon=\"\" " \
83
+ "erector=\"\" omicron=\"\" pi=\"3.14\" zebra=\"\" />";
84
+ end
85
+ end
86
+
87
+ context "with inner tags" do
88
+ it "returns nested tags" do
89
+ erector do
90
+ element 'div' do
91
+ element 'div'
92
+ end
93
+ end.should == '<div><div></div></div>'
94
+ end
95
+ end
96
+
97
+ context "with text" do
98
+ it "returns element with inner text" do
99
+ erector do
100
+ element 'div', 'test text'
101
+ end.should == "<div>test text</div>"
102
+ end
103
+ end
104
+
105
+ context "with a widget" do
106
+ it "renders the widget inside the element" do
107
+ erector do
108
+ element 'div', Erector.inline { p "foo" }
109
+ end.should == '<div><p>foo</p></div>'
110
+ end
111
+ end
112
+
113
+ context "with object other than hash" do
114
+ it "returns element with inner text == object.to_s" do
115
+ object = ['a', 'b']
116
+ erector do
117
+ element 'div', object
118
+ end.should == "<div>#{object.to_s}</div>"
119
+ end
120
+ end
121
+
122
+ context "with parameters and block" do
123
+ it "returns element with inner html and attributes" do
124
+ erector do
125
+ element 'div', 'class' => "foobar" do
126
+ element 'span', 'style' => 'display: none;'
127
+ end
128
+ end.should == '<div class="foobar"><span style="display: none;"></span></div>'
129
+ end
130
+ end
131
+
132
+ context "with content and parameters" do
133
+ it "returns element with content as inner html and attributes" do
134
+ erector do
135
+ element 'div', 'test text', :style => "display: none;"
136
+ end.should == '<div style="display: none;">test text</div>'
137
+ end
138
+ end
139
+
140
+ context "with more than three arguments" do
141
+ it "raises ArgumentError" do
142
+ proc do
143
+ erector do
144
+ element 'div', 'foobar', {}, 'fourth'
145
+ end
146
+ end.should raise_error(ArgumentError)
147
+ end
148
+ end
149
+
150
+ it "renders the proper full tags" do
151
+ Erector::Widget.full_tags.each do |tag_name|
152
+ expected = "<#{tag_name}></#{tag_name}>"
153
+ actual = erector { send(tag_name) }
154
+ begin
155
+ actual.should == expected
156
+ rescue Spec::Expectations::ExpectationNotMetError => e
157
+ puts "Expected #{tag_name} to be a full element. Expected #{expected}, got #{actual}"
158
+ raise e
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "quoting" do
164
+ context "when outputting text" do
165
+ it "quotes it" do
166
+ erector do
167
+ element 'div', 'test &<>text'
168
+ end.should == "<div>test &amp;&lt;&gt;text</div>"
169
+ end
170
+ end
171
+
172
+ context "when outputting text via text" do
173
+ it "quotes it" do
174
+ erector do
175
+ element 'div' do
176
+ text "test &<>text"
177
+ end
178
+ end.should == "<div>test &amp;&lt;&gt;text</div>"
179
+ end
180
+ end
181
+
182
+ context "when outputting attribute value" do
183
+ it "quotes it" do
184
+ erector do
185
+ element 'a', :href => "foo.cgi?a&b"
186
+ end.should == "<a href=\"foo.cgi?a&amp;b\"></a>"
187
+ end
188
+ end
189
+
190
+ context "with raw text" do
191
+ it "does not quote it" do
192
+ erector do
193
+ element 'div' do
194
+ text raw("<b>bold</b>")
195
+ end
196
+ end.should == "<div><b>bold</b></div>"
197
+ end
198
+ end
199
+
200
+ context "with raw text and no block" do
201
+ it "does not quote it" do
202
+ erector do
203
+ element 'div', raw("<b>bold</b>")
204
+ end.should == "<div><b>bold</b></div>"
205
+ end
206
+ end
207
+
208
+ context "with raw attribute" do
209
+ it "does not quote it" do
210
+ erector do
211
+ element 'a', :href => raw("foo?x=&nbsp;")
212
+ end.should == "<a href=\"foo?x=&nbsp;\"></a>"
213
+ end
214
+ end
215
+
216
+ context "with quote in attribute" do
217
+ it "quotes it" do
218
+ erector do
219
+ element 'a', :onload => "alert(\"foo\")"
220
+ end.should == "<a onload=\"alert(&quot;foo&quot;)\"></a>"
221
+ end
222
+ end
223
+ end
224
+
225
+ context "with a non-string, non-raw" do
226
+ it "calls to_s and quotes" do
227
+ erector do
228
+ element 'a' do
229
+ text [7, "foo&bar"]
230
+ end
231
+ end.should == "<a>7foo&amp;bar</a>"
232
+ end
233
+ end
234
+ end
235
+
236
+ describe "#empty_element" do
237
+ context "when receiving attributes" do
238
+ it "renders an empty element with the attributes" do
239
+ erector do
240
+ empty_element 'input', :name => 'foo[bar]'
241
+ end.should == '<input name="foo[bar]" />'
242
+ end
243
+ end
244
+
245
+ context "when not receiving attributes" do
246
+ it "renders an empty element without attributes" do
247
+ erector do
248
+ empty_element 'br'
249
+ end.should == '<br />'
250
+ end
251
+ end
252
+
253
+ it "renders the proper empty-element tags" do
254
+ Erector::Widget.empty_tags.each do |tag_name|
255
+ expected = "<#{tag_name} />"
256
+ actual = erector { send(tag_name) }
257
+ begin
258
+ actual.should == expected
259
+ rescue Spec::Expectations::ExpectationNotMetError => e
260
+ puts "Expected #{tag_name} to be an empty-element tag. Expected #{expected}, got #{actual}"
261
+ raise e
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ describe "#comment" do
268
+ it "emits a single line comment when receiving a string" do
269
+ erector do
270
+ comment "foo"
271
+ end.should == "<!--foo-->\n"
272
+ end
273
+
274
+ it "emits a multiline comment when receiving a block" do
275
+ erector do
276
+ comment do
277
+ text "Hello"
278
+ text " world!"
279
+ end
280
+ end.should == "<!--\nHello world!\n-->\n"
281
+ end
282
+
283
+ it "emits a multiline comment when receiving a string and a block" do
284
+ erector do
285
+ comment "Hello" do
286
+ text " world!"
287
+ end
288
+ end.should == "<!--Hello\n world!\n-->\n"
289
+ end
290
+
291
+ # see http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
292
+ it "does not HTML-escape character references" do
293
+ erector do
294
+ comment "&nbsp;"
295
+ end.should == "<!--&nbsp;-->\n"
296
+ end
297
+
298
+ # see http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
299
+ # "Authors should avoid putting two or more adjacent hyphens inside comments."
300
+ it "warns if there's two hyphens in a row" do
301
+ capturing_output do
302
+ erector do
303
+ comment "he was -- awesome!"
304
+ end.should == "<!--he was -- awesome!-->\n"
305
+ end.should == "Warning: Authors should avoid putting two or more adjacent hyphens inside comments.\n"
306
+ end
307
+
308
+ it "renders an IE conditional comment with endif when receiving an if IE" do
309
+ erector do
310
+ comment "[if IE]" do
311
+ text "Hello IE!"
312
+ end
313
+ end.should == "<!--[if IE]>\nHello IE!\n<![endif]-->\n"
314
+ end
315
+
316
+ it "doesn't render an IE conditional comment if there's just some text in brackets" do
317
+ erector do
318
+ comment "[puppies are cute]"
319
+ end.should == "<!--[puppies are cute]-->\n"
320
+ end
321
+
322
+ end
323
+
324
+ describe "#nbsp" do
325
+ it "turns consecutive spaces into consecutive non-breaking spaces" do
326
+ erector do
327
+ text nbsp("a b")
328
+ end.should == "a&#160;&#160;b"
329
+ end
330
+
331
+ it "works in text context" do
332
+ erector do
333
+ element 'a' do
334
+ text nbsp("&<> foo")
335
+ end
336
+ end.should == "<a>&amp;&lt;&gt;&#160;foo</a>"
337
+ end
338
+
339
+ it "works in attribute value context" do
340
+ erector do
341
+ element 'a', :href => nbsp("&<> foo")
342
+ end.should == "<a href=\"&amp;&lt;&gt;&#160;foo\"></a>"
343
+ end
344
+
345
+ it "defaults to a single non-breaking space if given no argument" do
346
+ erector do
347
+ text nbsp
348
+ end.should == "&#160;"
349
+ end
350
+
351
+ end
352
+
353
+ describe "#character" do
354
+ it "renders a character given the codepoint number" do
355
+ erector do
356
+ text character(160)
357
+ end.should == "&#xa0;"
358
+ end
359
+
360
+ it "renders a character given the unicode name" do
361
+ erector do
362
+ text character(:right_arrow)
363
+ end.should == "&#x2192;"
364
+ end
365
+
366
+ it "renders a character above 0xffff" do
367
+ erector do
368
+ text character(:old_persian_sign_ka)
369
+ end.should == "&#x103a3;"
370
+ end
371
+
372
+ it "throws an exception if a name is not recognized" do
373
+ lambda {
374
+ erector { text character(:no_such_character_name) }
375
+ }.should raise_error("Unrecognized character no_such_character_name")
376
+ end
377
+
378
+ it "throws an exception if passed something besides a symbol or integer" do
379
+ # Perhaps calling to_s would be more ruby-esque, but that seems like it might
380
+ # be pretty confusing when this method can already take either a name or number
381
+ lambda {
382
+ erector { text character([]) }
383
+ }.should raise_error("Unrecognized argument to character: ")
384
+ end
385
+ end
386
+
387
+ describe '#h' do
388
+ before do
389
+ @widget = Erector::Widget.new
390
+ end
391
+
392
+ it "escapes regular strings" do
393
+ @widget.h("&").should == "&amp;"
394
+ end
395
+
396
+ it "does not escape raw strings" do
397
+ @widget.h(@widget.raw("&")).should == "&"
398
+ end
399
+ end
400
+
401
+ describe 'escaping' do
402
+ plain = 'if (x < y && x > z) alert("don\'t stop");'
403
+ escaped = "if (x &lt; y &amp;&amp; x &gt; z) alert(&quot;don't stop&quot;);"
404
+
405
+ describe "#text" do
406
+ it "does HTML escape its param" do
407
+ erector { text plain }.should == escaped
408
+ end
409
+
410
+ it "doesn't escape pre-escaped strings" do
411
+ erector { text h(plain) }.should == escaped
412
+ end
413
+ end
414
+ describe "#rawtext" do
415
+ it "doesn't HTML escape its param" do
416
+ erector { rawtext plain }.should == plain
417
+ end
418
+ end
419
+ describe "#text!" do
420
+ it "doesn't HTML escape its param" do
421
+ erector { text! plain }.should == plain
422
+ end
423
+ end
424
+ describe "#element" do
425
+ it "does HTML escape its param" do
426
+ erector { element "foo", plain }.should == "<foo>#{escaped}</foo>"
427
+ end
428
+ end
429
+ describe "#element!" do
430
+ it "doesn't HTML escape its param" do
431
+ erector { element! "foo", plain }.should == "<foo>#{plain}</foo>"
432
+ end
433
+ end
434
+ end
435
+
436
+ describe "#javascript" do
437
+ context "when receiving a block" do
438
+ it "renders the content inside of script text/javascript tags" do
439
+ expected = <<-EXPECTED
440
+ <script type="text/javascript">
441
+ // <![CDATA[
442
+ if (x < y && x > z) alert("don't stop");
443
+ // ]]>
444
+ </script>
445
+ EXPECTED
446
+ expected.gsub!(/^ /, '')
447
+ erector do
448
+ javascript do
449
+ rawtext 'if (x < y && x > z) alert("don\'t stop");'
450
+ end
451
+ end.should == expected
452
+ end
453
+ end
454
+
455
+ it "renders the raw content inside script tags when given text" do
456
+ expected = <<-EXPECTED
457
+ <script type="text/javascript">
458
+ // <![CDATA[
459
+ alert("&<>'hello");
460
+ // ]]>
461
+ </script>
462
+ EXPECTED
463
+ expected.gsub!(/^ /, '')
464
+ erector do
465
+ javascript('alert("&<>\'hello");')
466
+ end.should == expected
467
+ end
468
+
469
+ context "when receiving a params hash" do
470
+ it "renders a source file" do
471
+ html = erector do
472
+ javascript(:src => "/my/js/file.js")
473
+ end
474
+ doc = Nokogiri::HTML(html)
475
+ doc.at("script")[:src].should == "/my/js/file.js"
476
+ end
477
+ end
478
+
479
+ context "when receiving text and a params hash" do
480
+ it "renders a source file" do
481
+ html = erector do
482
+ javascript('alert("&<>\'hello");', :src => "/my/js/file.js")
483
+ end
484
+ doc = Nokogiri::HTML(html)
485
+ script_tag = doc.at('script')
486
+ script_tag[:src].should == "/my/js/file.js"
487
+ script_tag.inner_html.should include('alert("&<>\'hello");')
488
+ end
489
+ end
490
+
491
+ context "with too many arguments" do
492
+ it "raises ArgumentError" do
493
+ proc do
494
+ erector do
495
+ javascript 'foobar', {}, 'fourth'
496
+ end
497
+ end.should raise_error(ArgumentError)
498
+ end
499
+ end
500
+ end
501
+
502
+ describe "#close_tag" do
503
+ it "works when it's all alone, even though it messes with the indent level" do
504
+ erector { close_tag :foo }.should == "</foo>"
505
+ erector { close_tag :foo; close_tag :bar }.should == "</foo></bar>"
506
+ end
507
+ end
508
+ end