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
@@ -1,6 +1,6 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
2
 
3
- require "erector/erect"
3
+ require "erector/erect/erect"
4
4
 
5
5
  module ParserTestHelper
6
6
  def assert_evals_to_self(input)
@@ -167,6 +167,11 @@ describe RhtmlParser do
167
167
  parse("<div id='foo'>bar</div>").convert.should == "div :id => 'foo' do\n text 'bar'\nend\n"
168
168
  end
169
169
 
170
+ it "processes ERb escapes in attributes, and adds parentheses when required" do
171
+ parse("<div id=\"<%= bar %>\" />").convert.should == "div :id => bar\n"
172
+ parse("<div id=\"foo_<%= bar %>_baz\" />").convert.should == "div(:id => ('foo_' + bar + '_baz'))\n"
173
+ end
174
+
170
175
  it "escapes single quotes inside attribute values" do
171
176
  @parser.root = :attribute
172
177
  parse("a=\"don't worry\"").convert.should == ":a => 'don\\'t worry'"
@@ -228,6 +233,11 @@ describe RhtmlParser do
228
233
  parse("'foo'").value.should == "foo"
229
234
  parse("\"foo\"").value.should == "foo"
230
235
  end
236
+
237
+ it "quotes empty strings" do
238
+ @parser.root = :quoted
239
+ parse("''").convert.should == "''"
240
+ end
231
241
 
232
242
  it "converts attributes in isolation" do
233
243
  @parser.root = :attribute
@@ -0,0 +1,267 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+
3
+ describe Erector::Cache do
4
+ before do
5
+ @cache = Erector::Cache.new
6
+ end
7
+
8
+ class Johnny < Erector::Widget
9
+ end
10
+
11
+ class June < Erector::Widget
12
+ end
13
+
14
+ it 'caches a class with no parameters' do
15
+ @cache[Johnny] = "ring of fire"
16
+ @cache[Johnny].should == "ring of fire"
17
+ end
18
+
19
+ it 'caches two classes with no parameters' do
20
+ @cache[Johnny] = "ring of fire"
21
+ @cache[June] = "wildwood flower"
22
+ @cache[Johnny].should == "ring of fire"
23
+ @cache[June].should == "wildwood flower"
24
+ end
25
+
26
+ it "stores different slots for the same class with different parameters" do
27
+ @cache[Johnny, {:flames => "higher"}] = "ring of fire"
28
+ @cache[Johnny, {:working => "in a coal mine"}] = "my daddy died young"
29
+
30
+ @cache[Johnny, {:flames => "higher"}].should == "ring of fire"
31
+ @cache[Johnny, {:working => "in a coal mine"}].should == "my daddy died young"
32
+ end
33
+
34
+ it "stores different slots for the same class with same parameters and different content methods" do
35
+ @cache[Johnny, {}, :foo] = "ring of fire"
36
+ @cache[Johnny, {}, :bar] = "my daddy died young"
37
+
38
+ @cache[Johnny, {}, :foo].should == "ring of fire"
39
+ @cache[Johnny, {}, :bar].should == "my daddy died young"
40
+ end
41
+
42
+ describe 'after storing a widget with one parameter' do
43
+ before do
44
+ @cache[Johnny, {:flames => "higher"}] = "ring of fire"
45
+ end
46
+
47
+ it 'doesn\'t get it when passed the class alone' do
48
+ @cache[Johnny].should be_nil
49
+ end
50
+
51
+ it 'doesn\'t get it when passed a different class' do
52
+ @cache[June].should be_nil
53
+ end
54
+
55
+ it 'gets it' do
56
+ @cache[Johnny, {:flames => "higher"}].should == "ring of fire"
57
+ end
58
+
59
+ it 'doesn\'t get it when passed a different parameter key' do
60
+ @cache[Johnny, {:working => "coal mine"}].should be_nil
61
+ end
62
+
63
+ it 'doesn\'t get it when passed a different parameter value' do
64
+ @cache[Johnny, {:flames => "lower"}].should be_nil
65
+ end
66
+
67
+ it 'doesn\'t get it when passed an extra parameter key' do
68
+ @cache[Johnny, {:flames => "higher", :working => "coal mine"}].should be_nil
69
+ end
70
+ end
71
+
72
+ describe 'after storing a widget with more than one parameter' do
73
+ before do
74
+ @cache[Johnny, {:flames => "higher", :working => "coal mine"}] = "ring of fire"
75
+ end
76
+
77
+ it "gets it" do
78
+ @cache[Johnny, {:flames => "higher", :working => "coal mine"}].should == "ring of fire"
79
+ end
80
+
81
+ it 'doesn\'t get it when passed the class alone' do
82
+ @cache[Johnny].should be_nil
83
+ end
84
+
85
+ it "doesn't get it when passed a partial parameter set" do
86
+ @cache[Johnny, {:flames => "higher"}].should be_nil
87
+ end
88
+
89
+ it 'doesn\'t get it when passed a different class' do
90
+ @cache[June].should be_nil
91
+ end
92
+
93
+ it 'doesn\'t get it when passed different a parameter value' do
94
+ @cache[Johnny, {:flames => "lower", :working => "coal mine"}].should be_nil
95
+ end
96
+
97
+ it 'doesn\'t get it when passed an extra parameter key' do
98
+ @cache[Johnny, {:flames => "higher", :working => "coal mine", :hear => "train a' comin'"}].should be_nil
99
+ end
100
+ end
101
+
102
+ describe "expires" do
103
+ it 'a class with no parameters' do
104
+ @cache[Johnny] = "ring of fire"
105
+ @cache.delete(Johnny)
106
+ @cache[Johnny].should be_nil
107
+ end
108
+
109
+ it 'all versions of a class' do
110
+ @cache[Johnny] = "i fell in"
111
+ @cache[Johnny, {:flames => "higher"}] = "ring of fire"
112
+ @cache[Johnny, {:working => "in a coal mine"}] = "my daddy died young"
113
+
114
+ @cache.delete_all(Johnny)
115
+
116
+ @cache[Johnny].should be_nil
117
+ @cache[Johnny, {:flames => "higher"}].should be_nil
118
+ @cache[Johnny, {:working => "in a coal mine"}].should be_nil
119
+ end
120
+
121
+ it '...but not other cached values' do
122
+ @cache[Johnny] = "ring of fire"
123
+ @cache[Johnny, {:flames => 'higher'}] = "higher fire"
124
+ @cache[June] = "wildwood flower"
125
+ @cache.delete(Johnny)
126
+ @cache[Johnny].should be_nil
127
+ @cache[Johnny, {:flames => 'higher'}].should == "higher fire"
128
+ @cache[June].should == "wildwood flower"
129
+ end
130
+ end
131
+ end
132
+
133
+ describe Erector::Caching do
134
+ include Erector::Mixin
135
+
136
+ class Cash < Erector::Widget
137
+ needs :name
138
+ cachable
139
+
140
+ def content
141
+ p do
142
+ text @name
143
+ text " Cash"
144
+ end
145
+ end
146
+ end
147
+
148
+ class Family < Erector::Widget
149
+ cacheable
150
+
151
+ def content
152
+ widget Cash, :name => "Johnny"
153
+ widget Cash, :name => "June"
154
+ end
155
+ end
156
+
157
+ class NotCachable < Erector::Widget
158
+ def content
159
+ text "CONTENT"
160
+ end
161
+ end
162
+
163
+ before do
164
+ @cache = Erector::Cache.new
165
+ Erector::Widget.cache = @cache
166
+ end
167
+
168
+ after do
169
+ Erector::Widget.cache = nil
170
+ end
171
+
172
+ it "has a global cache" do
173
+ Erector::Widget.cache.should == @cache
174
+ end
175
+
176
+ it '-- a widget is not cachable by default' do
177
+ Erector::Widget.cachable?.should be_false
178
+ end
179
+
180
+ it '-- a widget is cachable if you say so in the class definition' do
181
+ Cash.cachable?.should be_true
182
+ end
183
+
184
+ it '-- can be declared cachable using the alternate spelling "cacheable"' do
185
+ Family.cachable?.should be_true
186
+ end
187
+
188
+ describe '#to_html' do
189
+
190
+ it "caches a rendered widget" do
191
+ Cash.new(:name => "Johnny").to_html
192
+ @cache[Cash, {:name => "Johnny"}].to_s.should == "<p>Johnny Cash</p>"
193
+ end
194
+
195
+ it "uses the cached value" do
196
+ @cache[Cash, {:name => "Johnny"}] = "CACHED"
197
+ Cash.new(:name => "Johnny").to_html.should == "CACHED"
198
+ end
199
+
200
+ it "doesn't use the cached value for widgets not declared cachable" do
201
+ @cache[NotCachable] = "CACHED"
202
+ NotCachable.new.to_html.should == "CONTENT"
203
+ end
204
+
205
+ it "doesn't cache widgets not declared cachable" do
206
+ NotCachable.new.to_html
207
+ @cache[NotCachable].should be_nil
208
+ end
209
+
210
+ it "doesn't cache widgets initialized with a block (yet)" do
211
+ Cash.new(:name => "June") do
212
+ text "whatever"
213
+ end.to_html
214
+ @cache[Cash, {:name => "June"}].should be_nil
215
+ end
216
+
217
+ it "caches distinct values when using :content_method_name" do
218
+ widget = Class.new(Erector::Widget) do
219
+ cacheable
220
+
221
+ def foo
222
+ text "foo"
223
+ end
224
+
225
+ def bar
226
+ text "bar"
227
+ end
228
+ end
229
+
230
+ widget.new.to_html(:content_method_name => :foo).should == "foo"
231
+ widget.new.to_html(:content_method_name => :bar).should == "bar"
232
+ end
233
+
234
+ it "works when passing an existing output as a parameter to to_html"
235
+ end
236
+
237
+ describe '#widget' do
238
+
239
+ it "caches rendered widgets" do
240
+ Family.new.to_html
241
+ @cache[Cash, {:name => "Johnny"}].to_s.should == "<p>Johnny Cash</p>"
242
+ @cache[Cash, {:name => "June"}].to_s.should == "<p>June Cash</p>"
243
+ end
244
+
245
+ it "uses the cached value" do
246
+ @cache[Cash, {:name => "Johnny"}] = "JOHNNY CACHED"
247
+ Family.new.to_html.should == "JOHNNY CACHED<p>June Cash</p>"
248
+ end
249
+
250
+ class WidgetWithBlock < Erector::Widget
251
+ def content
252
+ call_block
253
+ end
254
+ end
255
+
256
+ it "doesn't cache widgets initialized with a block (yet)" do
257
+ erector {
258
+ w = WidgetWithBlock.new do
259
+ text "in block"
260
+ end
261
+ widget w
262
+ }.should == "in block"
263
+ @cache[WidgetWithBlock].should be_nil
264
+ end
265
+
266
+ end
267
+ end
@@ -0,0 +1,258 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+
3
+ describe Erector::Convenience do
4
+ include Erector::Mixin
5
+
6
+ describe "#to_pretty" do
7
+ it "calls to_html with :prettyprint => true" do
8
+ widget = Erector.inline do
9
+ div "foo"
10
+ end
11
+ mock(widget).to_html({:prettyprint => true})
12
+ widget.to_pretty
13
+ end
14
+
15
+ it "passes extra options through to to_html" do
16
+ widget = Erector.inline do
17
+ div "foo"
18
+ end
19
+ mock(widget).to_html({:prettyprint => true, :extra => "yay"})
20
+ widget.to_pretty(:extra => "yay")
21
+ end
22
+ end
23
+
24
+ describe "#to_s" do
25
+ it "returns html" do
26
+ capturing_stderr do
27
+ Erector.inline do
28
+ div "foo"
29
+ end.to_s.should == "<div>foo</div>"
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "#to_html" do
35
+ it "returns html" do
36
+ Erector.inline do
37
+ div "foo"
38
+ end.to_html.should == "<div>foo</div>"
39
+ end
40
+ end
41
+
42
+ describe "#to_text" do
43
+ it "strips tags" do
44
+ Erector.inline do
45
+ div "foo"
46
+ end.to_text.should == "foo"
47
+ end
48
+
49
+ it "unescapes named entities" do
50
+ s = "my \"dog\" has fleas & <ticks>"
51
+ Erector.inline do
52
+ text s
53
+ end.to_text.should == s
54
+ end
55
+
56
+ it "ignores >s inside attribute strings" do
57
+ Erector.inline do
58
+ a "foo", :href => "http://example.com/x>y"
59
+ end.to_text.should == "foo"
60
+ end
61
+
62
+ def with_prettyprint_default(value = true)
63
+ old_default = Erector::Widget.new.prettyprint_default
64
+ begin
65
+ Erector::Widget.prettyprint_default = value
66
+ yield
67
+ ensure
68
+ Erector::Widget.prettyprint_default = old_default
69
+ end
70
+ end
71
+
72
+ it "doesn't inherit unwanted pretty-printed whitespace (i.e. it turns off prettyprinting)" do
73
+ with_prettyprint_default(true) do
74
+ Erector.inline do
75
+ div { div { div "foo" } }
76
+ end.to_text.should == "foo"
77
+ end
78
+ end
79
+
80
+ it "passes extra attributes through to to_s" do
81
+ class Funny < Erector::Widget
82
+ def content
83
+ div "foo"
84
+ end
85
+
86
+ def funny
87
+ div "haha"
88
+ end
89
+ end
90
+ Funny.new.to_text(:content_method_name => :funny).should == "haha"
91
+ end
92
+
93
+ it "doesn't turn a p into a newline if it's at the beginning of the string" do
94
+ Erector.inline do
95
+ p "hi"
96
+ end.to_text.should == "hi\n"
97
+ end
98
+
99
+ it "puts a blank line (two newlines) after a /p tag" do
100
+ Erector.inline do
101
+ p "first paragraph"
102
+ p "second paragraph"
103
+ end.to_text.should == "first paragraph\n\nsecond paragraph\n"
104
+ end
105
+
106
+ it "separates p tags with attributes" do
107
+ Erector.inline do
108
+ p "first paragraph", :class => "first"
109
+ p "second paragraph", :class => "second"
110
+ end.to_text.should == "first paragraph\n\nsecond paragraph\n"
111
+ end
112
+
113
+ it "puts a newline after a br tag" do
114
+ Erector.inline do
115
+ text "first line"
116
+ br
117
+ text "second line"
118
+ end.to_text.should == "first line\nsecond line"
119
+ end
120
+
121
+ it "formats a UL (unordered list) using asterisks for bullets" do
122
+ Erector.inline do
123
+ ul do
124
+ li "vanilla"
125
+ li "chocolate"
126
+ li "strawberry"
127
+ end
128
+ end.to_text.should == "\n* vanilla\n* chocolate\n* strawberry\n"
129
+ end
130
+
131
+ # it's too hard to keep track of numbers with a regexp munger, so just use asterisks for bullets
132
+ # todo: integrate text output into core rendering code
133
+ it "formats an OL (ordered list)" do
134
+ Erector.inline do
135
+ ol do
136
+ li "vanilla"
137
+ li "chocolate"
138
+ li "strawberry"
139
+ end
140
+ end.to_text.should == "\n* vanilla\n* chocolate\n* strawberry\n"
141
+ end
142
+ end
143
+
144
+ describe "#join" do
145
+ it "empty array means nothing to join" do
146
+ erector do
147
+ join [], Erector::Widget.new { text "x" }
148
+ end.should == ""
149
+ end
150
+
151
+ it "larger example with two tabs" do
152
+ erector do
153
+ tab1 =
154
+ Erector.inline do
155
+ a "Upload document", :href => "/upload"
156
+ end
157
+ tab2 =
158
+ Erector.inline do
159
+ a "Logout", :href => "/logout"
160
+ end
161
+ join [tab1, tab2],
162
+ Erector::Widget.new { text nbsp(" |"); text " " }
163
+ end.should ==
164
+ '<a href="/upload">Upload document</a>&#160;| <a href="/logout">Logout</a>'
165
+ end
166
+
167
+ it "plain string as join separator means pass it to text" do
168
+ erector do
169
+ join [
170
+ Erector::Widget.new { text "x" },
171
+ Erector::Widget.new { text "y" }
172
+ ], "<>"
173
+ end.should == "x&lt;&gt;y"
174
+ end
175
+
176
+ it "plain string as item to join means pass it to text" do
177
+ erector do
178
+ join [
179
+ "<",
180
+ "&"
181
+ ], Erector::Widget.new { text " + " }
182
+ end.should == "&lt; + &amp;"
183
+ end
184
+ end
185
+
186
+ describe "#css" do
187
+ it "makes a link when passed a string" do
188
+ erector do
189
+ css "erector.css"
190
+ end.should == "<link href=\"erector.css\" rel=\"stylesheet\" type=\"text/css\" />"
191
+ end
192
+
193
+ it "accepts a media attribute" do
194
+ erector do
195
+ css "print.css", :media => "print"
196
+ end.should == "<link href=\"print.css\" media=\"print\" rel=\"stylesheet\" type=\"text/css\" />"
197
+ end
198
+
199
+ it "passes extra attributes through" do
200
+ erector { css "foo.css", :title => 'Foo' }.should == "<link href=\"foo.css\" rel=\"stylesheet\" title=\"Foo\" type=\"text/css\" />"
201
+ end
202
+ end
203
+
204
+ describe "#url" do
205
+ it "renders an anchor tag with the same href and text" do
206
+ erector do
207
+ url "http://example.com"
208
+ end.should == "<a href=\"http://example.com\">http://example.com</a>"
209
+ end
210
+
211
+ it "accepts extra attributes" do
212
+ erector do
213
+ url "http://example.com", :onclick=>"alert('foo')"
214
+ end.should == "<a href=\"http://example.com\" onclick=\"alert('foo')\">http://example.com</a>"
215
+ end
216
+
217
+ end
218
+
219
+ describe "#dom_id" do
220
+ class DOMIDWidget < Erector::Widget
221
+ def content
222
+ div :id => dom_id
223
+ end
224
+ end
225
+
226
+ it "makes a unique id based on the widget's class name and object id" do
227
+ widget = DOMIDWidget.new
228
+ widget.dom_id.should include("#{widget.object_id}")
229
+ widget.dom_id.should include("DOMIDWidget")
230
+ end
231
+
232
+ it "can be used as an HTML id" do
233
+ widget = DOMIDWidget.new
234
+ widget.to_html.should == "<div id=\"#{widget.dom_id}\"></div>"
235
+ end
236
+
237
+ describe 'for a namespaced widget class' do
238
+
239
+ module ::ErectorConvenienceSpec
240
+ class NestedWidget < Erector::Widget
241
+ end
242
+ end
243
+
244
+ it 'is colon escaped' do
245
+ g = ErectorConvenienceSpec::NestedWidget.new
246
+ g.dom_id.should_not =~ /:/
247
+ end
248
+
249
+ it 'combines all parent namespaces' do
250
+ g = ErectorConvenienceSpec::NestedWidget.new
251
+ g.dom_id.should == "ErectorConvenienceSpec_NestedWidget_#{g.object_id}"
252
+ end
253
+
254
+ end
255
+
256
+ end
257
+ end
258
+