swiss_knife 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Gemfile +9 -0
  2. data/Gemfile.lock +102 -0
  3. data/README.rdoc +183 -0
  4. data/Rakefile +26 -0
  5. data/lib/swiss_knife/action_controller.rb +43 -0
  6. data/lib/swiss_knife/assets.rb +81 -0
  7. data/lib/swiss_knife/dispatcher_js.rb +14 -0
  8. data/lib/swiss_knife/helpers.rb +178 -0
  9. data/lib/swiss_knife/i18n_js.rb +14 -0
  10. data/lib/swiss_knife/jquery.rb +14 -0
  11. data/lib/swiss_knife/jquery_ujs.rb +14 -0
  12. data/lib/swiss_knife/jsmin.rb +205 -0
  13. data/lib/swiss_knife/modernizr.rb +14 -0
  14. data/lib/swiss_knife/railtie.rb +19 -0
  15. data/lib/swiss_knife/rake_tasks.rb +31 -0
  16. data/lib/swiss_knife/rspec/have_tag.rb +115 -0
  17. data/lib/swiss_knife/rspec.rb +3 -0
  18. data/lib/swiss_knife/support/remote_file.rb +11 -0
  19. data/lib/swiss_knife/version.rb +8 -0
  20. data/lib/swiss_knife.rb +12 -0
  21. data/spec/controllers/application_controller_spec.rb +52 -0
  22. data/spec/helpers/helpers_spec.rb +314 -0
  23. data/spec/resources/assets/javascripts/application.js +1 -0
  24. data/spec/resources/assets/javascripts/jquery.js +1 -0
  25. data/spec/resources/assets/javascripts/rails.js +1 -0
  26. data/spec/resources/assets/stylesheets/main.css +1 -0
  27. data/spec/resources/assets/stylesheets/reset.css +1 -0
  28. data/spec/resources/assets/stylesheets/shared.css +1 -0
  29. data/spec/resources/assets.yml +16 -0
  30. data/spec/resources/stylesheets/_shared.less +1 -0
  31. data/spec/resources/stylesheets/main.less +3 -0
  32. data/spec/resources/stylesheets/reset.css +1 -0
  33. data/spec/resources/stylesheets/ui/tab.css +1 -0
  34. data/spec/resources/stylesheets/ui/window.less +1 -0
  35. data/spec/spec_helper.rb +25 -0
  36. data/spec/support/app/controllers/application_controller.rb +2 -0
  37. data/spec/support/config/boot.rb +20 -0
  38. data/spec/support/config/locales/en.yml +7 -0
  39. data/spec/support/log/test.log +8 -0
  40. data/spec/support/rspec/remote_file_shared.rb +21 -0
  41. data/spec/swiss_knife/assets_spec.rb +73 -0
  42. data/spec/swiss_knife/dispatcher_js_spec.rb +8 -0
  43. data/spec/swiss_knife/i18n_js_spec.rb +8 -0
  44. data/spec/swiss_knife/jquery_spec.rb +8 -0
  45. data/spec/swiss_knife/jquery_ujs_spec.rb +8 -0
  46. data/spec/swiss_knife/modernizr_spec.rb +8 -0
  47. metadata +149 -0
@@ -0,0 +1,19 @@
1
+ require "rails/railtie"
2
+
3
+ module SwissKnife
4
+ class Railtie < Rails::Railtie
5
+ generators do
6
+ # require "swiss_knife/generators"
7
+ end
8
+
9
+ rake_tasks do
10
+ require "swiss_knife/rake_tasks"
11
+ end
12
+
13
+ initializer "swiss_knife.initialize" do |app|
14
+ ApplicationController.send :include, SwissKnife::ActionController
15
+ ApplicationController.helper SwissKnife::Helpers
16
+ ApplicationController.helper_method :page_title
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ namespace :swiss_knife do
2
+ namespace :javascripts do
3
+ desc "Update all JavaScripts"
4
+ task :update => %w[ i18njs jquery jquery_ujs modernizr dispatcher ]
5
+
6
+ desc "Update I18n JS"
7
+ task :i18njs => :environment do
8
+ SwissKnife::I18nJs.update
9
+ end
10
+
11
+ desc "Update jQuery"
12
+ task :jquery => :environment do
13
+ SwissKnife::Jquery.update
14
+ end
15
+
16
+ desc "Update jQuery UJS"
17
+ task :jquery_ujs => :environment do
18
+ SwissKnife::JqueryUjs.update
19
+ end
20
+
21
+ desc "Update Modernizr"
22
+ task :modernizr => :environment do
23
+ SwissKnife::Modernizr.update
24
+ end
25
+
26
+ desc "Update Dispatcher JS"
27
+ task :dispatcher => :environment do
28
+ SwissKnife::DispatcherJs.update
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,115 @@
1
+ module SwissKnife
2
+ module RSpec
3
+ module Matchers
4
+ def have_tag(selector, options = {}, &block)
5
+ HaveTag.new(:html, selector, options, &block)
6
+ end
7
+
8
+ def have_node(selector, options = {}, &block)
9
+ HaveTag.new(:xml, selector, options, &block)
10
+ end
11
+
12
+ class HaveTag
13
+ attr_reader :options, :selector, :actual, :actual_count, :doc, :type
14
+
15
+ def initialize(type, selector, options = {}, &block)
16
+ @selector = selector
17
+ @type = type
18
+
19
+ case options
20
+ when Hash
21
+ @options = options
22
+ when Numeric
23
+ @options = {:count => options}
24
+ else
25
+ @options = {:text => options}
26
+ end
27
+ end
28
+
29
+ def doc_for(input)
30
+ engine = type == :xml ? Nokogiri::XML : Nokogiri::HTML
31
+
32
+ if input.respond_to?(:body)
33
+ engine.parse(input.body.to_s)
34
+ elsif Nokogiri::XML::Element === input
35
+ input
36
+ else
37
+ engine.parse(input.to_s)
38
+ end
39
+ end
40
+
41
+ def matches?(actual, &block)
42
+ @actual = actual
43
+ @doc = doc_for(actual)
44
+
45
+ matches = doc.css(selector)
46
+
47
+ return options[:count] == 0 if matches.empty?
48
+ matches = filter_on_inner_text(matches) if options[:text]
49
+ matches = filter_on_nested_expectations(matches, block) if block
50
+
51
+ @actual_count = matches.size
52
+
53
+ return false if not acceptable_count?(actual_count)
54
+
55
+ !matches.empty?
56
+ end
57
+
58
+ def failure_message
59
+ explanation = actual_count ? "but found #{actual_count}" : "but did not"
60
+ "expected\n#{doc.to_s}\nto have #{failure_count_phrase} #{failure_selector_phrase}, #{explanation}"
61
+ end
62
+
63
+ def negative_failure_message
64
+ explanation = actual_count ? "but found #{actual_count}" : "but did"
65
+ "expected\n#{doc.to_s}\nnot to have #{failure_count_phrase} #{failure_selector_phrase}, #{explanation}"
66
+ end
67
+
68
+ private
69
+ def filter_on_inner_text(elements)
70
+ elements.select do |el|
71
+ next(el.inner_text =~ options[:text]) if options[:text].is_a?(Regexp)
72
+ el.inner_text == options[:text]
73
+ end
74
+ end
75
+
76
+ def filter_on_nested_expectations(elements, block)
77
+ elements.select do |el|
78
+ begin
79
+ block.call(el)
80
+ rescue RSpec::Expectations::ExpectationNotMetError
81
+ false
82
+ else
83
+ true
84
+ end
85
+ end
86
+ end
87
+
88
+ def acceptable_count?(count)
89
+ return false unless options[:count] === count if options[:count]
90
+ return false unless count >= options[:minimum] if options[:minimum]
91
+ return false unless count <= options[:maximum] if options[:maximum]
92
+ true
93
+ end
94
+
95
+ def failure_count_phrase
96
+ if options[:count]
97
+ "#{options[:count]} elements matching"
98
+ elsif options[:minimum] || options[:maximum]
99
+ count_explanations = []
100
+ count_explanations << "at least #{options[:minimum]}" if options[:minimum]
101
+ count_explanations << "at most #{options[:maximum]}" if options[:maximum]
102
+ "#{count_explanations.join(' and ')} elements matching"
103
+ else
104
+ "an element matching"
105
+ end
106
+ end
107
+
108
+ def failure_selector_phrase
109
+ phrase = selector.inspect
110
+ phrase << (options[:text] ? " with inner text #{options[:text].inspect}" : "")
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,3 @@
1
+ require "swiss_knife/rspec/have_tag"
2
+
3
+ RSpec.configure {|config| config.include(SwissKnife::RSpec::Matchers)} if defined?(RSpec)
@@ -0,0 +1,11 @@
1
+ module SwissKnife
2
+ module Support
3
+ module RemoteFile
4
+ def update
5
+ File.open(Rails.root.join("public/#{file}"), "w+") do |f|
6
+ f << open(url).read
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module SwissKnife
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ require "open-uri"
2
+ require "swiss_knife/railtie"
3
+ require "swiss_knife/support/remote_file"
4
+ require "swiss_knife/assets"
5
+ require "swiss_knife/dispatcher_js"
6
+ require "swiss_knife/helpers"
7
+ require "swiss_knife/action_controller"
8
+ require "swiss_knife/i18n_js"
9
+ require "swiss_knife/jquery"
10
+ require "swiss_knife/jquery_ujs"
11
+ require "swiss_knife/modernizr"
12
+ require "swiss_knife/railtie"
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe ApplicationController do
4
+ before do
5
+ controller.stub :controller_name => "products"
6
+ end
7
+
8
+ it "should return inline title" do
9
+ controller.page_title "Viewing all products"
10
+ controller.page_title.should == "Viewing all products"
11
+ end
12
+
13
+ it "should return internationalized title" do
14
+ controller.stub :action_name => "index"
15
+ controller.page_title.should == "All products"
16
+ end
17
+
18
+ it "should return internationalized title with interpolation options" do
19
+ controller.page_title :name => "Some product"
20
+ controller.stub :action_name => "edit"
21
+ controller.page_title.should == "Editing Some product"
22
+ end
23
+
24
+ it "should return missing translation" do
25
+ controller.stub :action_name => "details"
26
+
27
+ expect {
28
+ controller.page_title.should match(/translation missing/)
29
+ }.to_not raise_error
30
+ end
31
+
32
+ it "should alias create action" do
33
+ controller.stub :action_name => "create"
34
+ controller.page_title.should == "Add a new product"
35
+ end
36
+
37
+ it "should alias update action" do
38
+ controller.page_title :name => "Some product"
39
+ controller.stub :action_name => "update"
40
+ controller.page_title.should == "Editing Some product"
41
+ end
42
+
43
+ it "should alias remove action" do
44
+ controller.page_title :name => "Some product"
45
+ controller.stub :action_name => "remove"
46
+ controller.page_title.should == "Remove Some product"
47
+ end
48
+
49
+ it "should be added as a helper method" do
50
+ ApplicationController._helper_methods.should include(:page_title)
51
+ end
52
+ end
@@ -0,0 +1,314 @@
1
+ require "spec_helper"
2
+
3
+ describe SwissKnife::Helpers do
4
+ before do
5
+ I18n.locale = :en
6
+ @controller = helper.send(:controller)
7
+ @controller.stub(:controller_name => "sample", :action_name => "index")
8
+ helper.stub(:controller => @controller)
9
+ end
10
+
11
+ describe "#flash_messages" do
12
+ before do
13
+ flash[:notice] = "Notice"
14
+ flash[:warning] = "Warning"
15
+ flash[:error] = "Error"
16
+ end
17
+
18
+ subject { helper.flash_messages }
19
+
20
+ it "should render multiple flash messages" do
21
+ subject.should have_tag("p.message", :count => 3)
22
+ end
23
+
24
+ it "should render error message" do
25
+ subject.should have_tag("p.error", "Error")
26
+ end
27
+
28
+ it "should render warning message" do
29
+ subject.should have_tag("p.warning", "Warning")
30
+ end
31
+
32
+ it "should render notice message" do
33
+ subject.should have_tag("p.notice", "Notice")
34
+ end
35
+ end
36
+
37
+ describe "block wrappers" do
38
+ context "body" do
39
+ it "should use defaults" do
40
+ html = helper.body { "Body" }
41
+
42
+ html.should have_tag("body", :count => 1)
43
+ html.should have_tag("body#sample-page")
44
+ html.should have_tag("body.sample-index")
45
+ html.should have_tag("body.en")
46
+ end
47
+
48
+ it "should use alias for action" do
49
+ @controller.stub(:action_name => "create")
50
+ helper.body { "Body" }.should have_tag("body.sample-new")
51
+
52
+ @controller.stub(:action_name => "update")
53
+ helper.body { "Body" }.should have_tag("body.sample-edit")
54
+
55
+ @controller.stub(:action_name => "destroy")
56
+ helper.body { "Body" }.should have_tag("body.sample-destroy")
57
+ end
58
+
59
+ it "should use custom settings" do
60
+ html = helper.body(:id => "page", :class => "dark", :onload => "init();") { "Body" }
61
+
62
+ html.should have_tag("body#page")
63
+ html.should have_tag("body.dark")
64
+ html.should have_tag("body[onload='init();']")
65
+ end
66
+
67
+ it "should append classes" do
68
+ html = helper.body(:append_class => "more classes") { "Body" }
69
+
70
+ html.should have_tag("body.more")
71
+ html.should have_tag("body.classes")
72
+ html.should have_tag("body.en")
73
+ html.should have_tag("body.sample-index")
74
+ end
75
+
76
+ it "should not have append_class attribute" do
77
+ helper.body(:append_class => "more classes") { "Body" }.should_not have_tag("body[append_class]")
78
+ end
79
+ end
80
+
81
+ it "should wrap content into main div" do
82
+ helper.main { "Main" }.should have_tag("div#main", "Main")
83
+ end
84
+
85
+ it "should wrap content into page div" do
86
+ helper.page { "Page" }.should have_tag("div#page", "Page")
87
+ end
88
+
89
+ it "should wrap content into sidebar" do
90
+ helper.sidebar { "Sidebar" }.should have_tag("sidebar", "Sidebar")
91
+ end
92
+
93
+ it "should wrap content into footer" do
94
+ helper.footer { "Footer" }.should have_tag("footer", "Footer")
95
+ end
96
+
97
+ it "should wrap content into header" do
98
+ helper.header { "Header" }.should have_tag("header", "Header")
99
+ end
100
+
101
+ it "should wrap content into article" do
102
+ helper.article { "Article" }.should have_tag("article", "Article")
103
+ end
104
+
105
+ it "should wrap content into section" do
106
+ helper.section { "Section" }.should have_tag("section", "Section")
107
+ end
108
+
109
+ it "should use other options like css class" do
110
+ html = helper.wrapper(:div, :id => "container", :class => "rounded") { "Some content" }
111
+ html.should have_tag("div#container.rounded", "Some content")
112
+ end
113
+ end
114
+
115
+ describe "#dispatcher_tag" do
116
+ it "should contain meta tag" do
117
+ @controller.class.stub!(:name).and_return("SampleController")
118
+
119
+ html = helper.dispatcher_tag
120
+ html.should have_tag("meta")
121
+ html.should have_tag("meta[name=page][content='sample#index']")
122
+ end
123
+
124
+ it "should return normalized controller name for namespaced controller" do
125
+ @controller.class.stub(:name => "Admin::SampleController")
126
+ helper.dispatcher_tag.should have_tag("meta[name=page][content='admin_sample#index']", :count => 1)
127
+ end
128
+ end
129
+
130
+ describe "#mail_to" do
131
+ subject { helper.mail_to("john@doe.com") }
132
+
133
+ it "should be encrypted" do
134
+ subject.should_not match(/john@doe\.com/)
135
+ end
136
+
137
+ it "should not have plain-text protocol" do
138
+ subject.should_not match(/mailto:/)
139
+ end
140
+
141
+ it "should use provided label" do
142
+ helper.mail_to("john@doe.com", "john's email").should have_tag("a", "john's email")
143
+ end
144
+ end
145
+
146
+ context "assets" do
147
+ before do
148
+ @assets_dir = Pathname.new(File.dirname(__FILE__) + "/../resources/assets")
149
+ SwissKnife::Assets.stub(:public_dir => @assets_dir)
150
+ SwissKnife::Assets.stub(:config_file => File.dirname(__FILE__) + "/../resources/assets.yml")
151
+ end
152
+
153
+ describe "javascript includes" do
154
+ it "should use defaults" do
155
+ html = helper.javascript_includes("application")
156
+
157
+ html.should have_tag("script[type='text/javascript']", :count => 1)
158
+ html.should match(%r{/javascripts/application.js(\?\d+)?})
159
+ end
160
+
161
+ it "should return several includes for bundle when not in production" do
162
+ SwissKnife::Assets.stub(:merge? => false)
163
+ html = helper.javascript_includes(:base)
164
+
165
+ html.should have_tag("script[type='text/javascript']", :count => 3)
166
+ html.should match(%r{/javascripts/application.js(\?\d+)?})
167
+ html.should match(%r{/javascripts/jquery.js(\?\d+)?})
168
+ html.should match(%r{/javascripts/rails.js(\?\d+)?})
169
+ end
170
+
171
+ it "should return bundle only when in production" do
172
+ SwissKnife::Assets.stub(:merge? => true)
173
+ html = helper.javascript_includes(:base)
174
+
175
+ html.should have_tag("script[type='text/javascript']", :count => 1)
176
+ html.should match(%r{/javascripts/base_packaged.js(\?\d+)?})
177
+ end
178
+ end
179
+
180
+ describe "stylesheet includes" do
181
+ it "should use defaults" do
182
+ html = helper.stylesheet_includes("application")
183
+
184
+ html.should have_tag("link[rel='stylesheet']", :count => 1)
185
+ html.should match(%r{/stylesheets/application.css(\?\d+)?})
186
+ end
187
+
188
+ it "should return several includes for bundle when not in production" do
189
+ SwissKnife::Assets.stub(:merge? => false)
190
+ html = helper.stylesheet_includes(:base)
191
+
192
+ html.should have_tag("link[rel='stylesheet']", :count => 2)
193
+ html.should match(%r{/stylesheets/reset.css(\?\d+)?})
194
+ html.should match(%r{/stylesheets/main.css(\?\d+)?})
195
+ end
196
+
197
+ it "should return only bundle when in production" do
198
+ SwissKnife::Assets.stub(:merge? => true)
199
+ html = helper.stylesheet_includes(:base)
200
+
201
+ html.should have_tag("link[rel='stylesheet']", :count => 1)
202
+ html.should match(%r{/stylesheets/base_packaged.css(\?\d+)?})
203
+ end
204
+ end
205
+ end
206
+
207
+ describe "#fieldset" do
208
+ before do
209
+ @html = helper.fieldset("Nice legend", :class => "sample") { "<p>Fieldset</p>" }
210
+ end
211
+
212
+ it "should use provided options" do
213
+ @html.should have_tag("fieldset.sample", :count => 1)
214
+ end
215
+
216
+ it "should use legend as text" do
217
+ @html.should have_tag("fieldset > legend", "Nice legend")
218
+ end
219
+
220
+ it "should use translated legend" do
221
+ I18n.locale = :pt
222
+ I18n.backend.should_receive(:translations).and_return(:pt => {:sample => "Legenda"})
223
+
224
+ @html = helper.fieldset("sample") {}
225
+ @html.should have_tag("fieldset > legend", "Legenda")
226
+ end
227
+
228
+ it "should return content" do
229
+ @html.should have_tag("fieldset > p", "Fieldset")
230
+ end
231
+ end
232
+
233
+ describe "#gravatar_tag" do
234
+ before do
235
+ ActionController::Base.asset_host = "http://example.com"
236
+ @email = "john@doe.com"
237
+ end
238
+
239
+ it "should return an image" do
240
+ helper.gravatar_tag(@email).should have_tag("img.gravatar", :count => 1)
241
+ end
242
+
243
+ it "should use default options" do
244
+ uri = uri_for(helper.gravatar_tag(@email))
245
+ uri.scheme.should == "http"
246
+ uri.host.should == "www.gravatar.com"
247
+ uri.path.should == "/avatar/#{Digest::MD5.hexdigest(@email)}.jpg"
248
+ uri.params.should == {"s" => "32", "r" => "g", "d" => "http://example.com/images/gravatar.jpg"}
249
+ end
250
+
251
+ it "should use gravatar hash" do
252
+ uri = uri_for(helper.gravatar_tag("098f6bcd4621d373cade4e832627b4f6"))
253
+ uri.path.should == "/avatar/098f6bcd4621d373cade4e832627b4f6.jpg"
254
+ end
255
+
256
+ it "should use custom default image" do
257
+ uri = uri_for(helper.gravatar_tag(@email, :default => "custom.jpg"))
258
+ uri.params["d"].should == "http://example.com/images/custom.jpg"
259
+ end
260
+
261
+ it "should use custom default image" do
262
+ uri = uri_for(helper.gravatar_tag(@email, :default => "custom.jpg"))
263
+ uri.params["d"].should == "http://example.com/images/custom.jpg"
264
+ end
265
+
266
+ it "should use predefined default image" do
267
+ uri = uri_for(helper.gravatar_tag(@email, :default => :mm))
268
+ uri.params["d"].should == "mm"
269
+ end
270
+
271
+ it "should use custom size" do
272
+ uri = uri_for(helper.gravatar_tag(@email, :size => 80))
273
+ uri.params["s"].should == "80"
274
+ end
275
+
276
+ it "should use custom rating" do
277
+ uri = uri_for(helper.gravatar_tag(@email, :rating => "pg"))
278
+ uri.params["r"].should == "pg"
279
+ end
280
+
281
+ it "should use secure host when in a secure request" do
282
+ helper.request.should_receive(:ssl?).and_return(true)
283
+ uri = uri_for(helper.gravatar_tag(@email))
284
+ uri.host.should == "secure.gravatar.com"
285
+ uri.scheme.should == "https"
286
+ end
287
+
288
+ it "should use secure host when using :ssl option" do
289
+ uri = uri_for(helper.gravatar_tag(@email, :ssl => true))
290
+ uri.host.should == "secure.gravatar.com"
291
+ uri.scheme.should == "https"
292
+ end
293
+
294
+ it "should use alt" do
295
+ helper.gravatar_tag(@email, :alt => "alt text").should match(/\balt="alt text"/)
296
+ end
297
+
298
+ it "should use title" do
299
+ helper.gravatar_tag(@email, :title => "title text").should match(/\btitle="title text"/)
300
+ end
301
+
302
+ def uri_for(html)
303
+ html = Nokogiri(html)
304
+ uri = URI.parse(html.css("img.gravatar").first["src"])
305
+
306
+ OpenStruct.new({
307
+ :scheme => uri.scheme,
308
+ :params => CGI.parse(uri.query).inject({}) {|buffer, (name, value)| buffer.merge(name => value.first)},
309
+ :host => uri.host,
310
+ :path => uri.path
311
+ })
312
+ end
313
+ end
314
+ end