erector 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+