swiss_knife 0.1.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 (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