roadie 2.4.3 → 3.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +9 -14
  4. data/.yardopts +1 -1
  5. data/Changelog.md +22 -10
  6. data/Gemfile +3 -0
  7. data/Guardfile +11 -1
  8. data/README.md +165 -163
  9. data/Rakefile +2 -19
  10. data/lib/roadie.rb +14 -69
  11. data/lib/roadie/asset_provider.rb +7 -58
  12. data/lib/roadie/asset_scanner.rb +92 -0
  13. data/lib/roadie/document.rb +103 -0
  14. data/lib/roadie/errors.rb +57 -0
  15. data/lib/roadie/filesystem_provider.rb +21 -62
  16. data/lib/roadie/inliner.rb +71 -218
  17. data/lib/roadie/markup_improver.rb +88 -0
  18. data/lib/roadie/null_provider.rb +13 -0
  19. data/lib/roadie/null_url_rewriter.rb +12 -0
  20. data/lib/roadie/provider_list.rb +67 -0
  21. data/lib/roadie/rspec.rb +1 -0
  22. data/lib/roadie/rspec/asset_provider.rb +49 -0
  23. data/lib/roadie/selector.rb +42 -18
  24. data/lib/roadie/style_block.rb +33 -0
  25. data/lib/roadie/style_properties.rb +29 -0
  26. data/lib/roadie/style_property.rb +93 -0
  27. data/lib/roadie/stylesheet.rb +65 -0
  28. data/lib/roadie/url_generator.rb +126 -0
  29. data/lib/roadie/url_rewriter.rb +84 -0
  30. data/lib/roadie/version.rb +1 -1
  31. data/roadie.gemspec +6 -10
  32. data/spec/fixtures/big_em.css +1 -0
  33. data/spec/fixtures/stylesheets/green.css +1 -0
  34. data/spec/integration_spec.rb +125 -95
  35. data/spec/lib/roadie/asset_scanner_spec.rb +153 -0
  36. data/spec/lib/roadie/css_not_found_spec.rb +16 -0
  37. data/spec/lib/roadie/document_spec.rb +123 -0
  38. data/spec/lib/roadie/filesystem_provider_spec.rb +25 -72
  39. data/spec/lib/roadie/inliner_spec.rb +105 -537
  40. data/spec/lib/roadie/markup_improver_spec.rb +78 -0
  41. data/spec/lib/roadie/null_provider_spec.rb +21 -0
  42. data/spec/lib/roadie/null_url_rewriter_spec.rb +19 -0
  43. data/spec/lib/roadie/provider_list_spec.rb +81 -0
  44. data/spec/lib/roadie/selector_spec.rb +7 -5
  45. data/spec/lib/roadie/style_block_spec.rb +35 -0
  46. data/spec/lib/roadie/style_properties_spec.rb +61 -0
  47. data/spec/lib/roadie/style_property_spec.rb +82 -0
  48. data/spec/lib/roadie/stylesheet_spec.rb +41 -0
  49. data/spec/lib/roadie/test_provider_spec.rb +29 -0
  50. data/spec/lib/roadie/url_generator_spec.rb +120 -0
  51. data/spec/lib/roadie/url_rewriter_spec.rb +79 -0
  52. data/spec/shared_examples/asset_provider.rb +11 -0
  53. data/spec/shared_examples/url_rewriter.rb +23 -0
  54. data/spec/spec_helper.rb +5 -60
  55. data/spec/support/have_node_matcher.rb +2 -2
  56. data/spec/support/have_selector_matcher.rb +1 -1
  57. data/spec/support/have_styling_matcher.rb +48 -14
  58. data/spec/support/test_provider.rb +13 -0
  59. metadata +73 -177
  60. data/Appraisals +0 -15
  61. data/gemfiles/rails_3.0.gemfile +0 -7
  62. data/gemfiles/rails_3.0.gemfile.lock +0 -123
  63. data/gemfiles/rails_3.1.gemfile +0 -7
  64. data/gemfiles/rails_3.1.gemfile.lock +0 -126
  65. data/gemfiles/rails_3.2.gemfile +0 -7
  66. data/gemfiles/rails_3.2.gemfile.lock +0 -124
  67. data/gemfiles/rails_4.0.gemfile +0 -7
  68. data/gemfiles/rails_4.0.gemfile.lock +0 -119
  69. data/lib/roadie/action_mailer_extensions.rb +0 -95
  70. data/lib/roadie/asset_pipeline_provider.rb +0 -28
  71. data/lib/roadie/css_file_not_found.rb +0 -22
  72. data/lib/roadie/railtie.rb +0 -39
  73. data/lib/roadie/style_declaration.rb +0 -42
  74. data/spec/fixtures/app/assets/stylesheets/integration.css +0 -10
  75. data/spec/fixtures/public/stylesheets/integration.css +0 -10
  76. data/spec/fixtures/views/integration_mailer/marketing.html.erb +0 -2
  77. data/spec/fixtures/views/integration_mailer/notification.html.erb +0 -8
  78. data/spec/fixtures/views/integration_mailer/notification.text.erb +0 -6
  79. data/spec/lib/roadie/action_mailer_extensions_spec.rb +0 -227
  80. data/spec/lib/roadie/asset_pipeline_provider_spec.rb +0 -65
  81. data/spec/lib/roadie/css_file_not_found_spec.rb +0 -29
  82. data/spec/lib/roadie/style_declaration_spec.rb +0 -49
  83. data/spec/lib/roadie_spec.rb +0 -101
  84. data/spec/shared_examples/asset_provider_examples.rb +0 -11
  85. data/spec/support/anonymous_mailer.rb +0 -21
  86. data/spec/support/change_url_options.rb +0 -5
  87. data/spec/support/parse_styling.rb +0 -25
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'roadie/rspec'
3
+
4
+ describe TestProvider do
5
+ subject(:provider) { TestProvider.new }
6
+
7
+ it_behaves_like "roadie asset provider", valid_name: "existing.css", invalid_name: "invalid.css" do
8
+ subject { TestProvider.new "existing.css" => "" }
9
+ end
10
+
11
+ it "finds styles from a predefined hash" do
12
+ provider = TestProvider.new({
13
+ "foo.css" => "a { color: red; }",
14
+ "bar.css" => "body { color: green; }",
15
+ })
16
+ provider.find_stylesheet("foo.css").to_s.should_not include("body")
17
+ provider.find_stylesheet("bar.css").to_s.should include("body")
18
+ provider.find_stylesheet("baz.css").should be_nil
19
+ end
20
+
21
+ it "can have a default for missing entries" do
22
+ provider = TestProvider.new({
23
+ "foo.css" => "a { color: red; }",
24
+ :default => "body { color: green; }",
25
+ })
26
+ provider.find_stylesheet("foo.css").to_s.should_not include("body")
27
+ provider.find_stylesheet("bar.css").to_s.should include("body")
28
+ end
29
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ module Roadie
5
+ describe UrlGenerator do
6
+ it "is initialized with URL options" do
7
+ UrlGenerator.new(host: "foo.com").url_options.should == {host: "foo.com"}
8
+ end
9
+
10
+ it "raises an argument error if no URL options are specified" do
11
+ expect {
12
+ UrlGenerator.new(nil)
13
+ }.to raise_error ArgumentError, /url options/i
14
+ end
15
+
16
+ it "raises an argument error if no host is specified" do
17
+ expect {
18
+ UrlGenerator.new(port: 3000)
19
+ }.to raise_error ArgumentError, /host/i
20
+ end
21
+
22
+ it "raises an argument error if unknown option is passed" do
23
+ expect {
24
+ UrlGenerator.new(host: "localhost", secret: true)
25
+ }.to raise_error ArgumentError, /secret/
26
+ end
27
+
28
+ describe "generating URLs" do
29
+ def url(path, options = {})
30
+ UrlGenerator.new(options).generate_url(path)
31
+ end
32
+
33
+ it "uses the given host" do
34
+ url("/hello.jpg", host: "goats.com").should == "http://goats.com/hello.jpg"
35
+ end
36
+
37
+ it "uses the given port" do
38
+ url("/", host: "example.com", port: 1337).should == "http://example.com:1337/"
39
+ end
40
+
41
+ it "uses the given scheme" do
42
+ url("/", host: "example.com", scheme: "https").should == "https://example.com/"
43
+ end
44
+
45
+ it "regards :protocol as an alias for scheme" do
46
+ url("/", host: "example.com", protocol: "https").should == "https://example.com/"
47
+ end
48
+
49
+ it "strips extra characters from the scheme" do
50
+ url("/", host: "example.com", scheme: "https://").should == "https://example.com/"
51
+ end
52
+
53
+ it "uses the given path as a prefix" do
54
+ url("/my_file", host: "example.com", path: "/my_app").should ==
55
+ "http://example.com/my_app/my_file"
56
+ end
57
+
58
+ it "returns the original URL if it is absolute" do
59
+ url("http://foo.com/", host: "bar.com").should == "http://foo.com/"
60
+ end
61
+
62
+ it "returns the base URL for blank paths" do
63
+ url("", host: "foo.com").should == "http://foo.com"
64
+ url(nil, host: "foo.com").should == "http://foo.com"
65
+ end
66
+
67
+ it "raises an error on invalid path" do
68
+ expect {
69
+ url("://", host: "example.com")
70
+ }.to raise_error InvalidUrlPath, %r{://}
71
+ end
72
+
73
+ it "accepts base paths without a slash in the beginning" do
74
+ url("/bar", host: "example.com", path: "foo").should == "http://example.com/foo/bar"
75
+ url("/bar/", host: "example.com", path: "foo/").should == "http://example.com/foo/bar/"
76
+ end
77
+
78
+ it "accepts input paths without a slash in the beginning" do
79
+ url("bar", host: "example.com", path: "/foo").should == "http://example.com/foo/bar"
80
+ url("bar", host: "example.com", path: "/foo/").should == "http://example.com/foo/bar"
81
+ end
82
+
83
+ it "does not touch data: URIs" do
84
+ url("data:deadbeef", host: "example.com").should == "data:deadbeef"
85
+ end
86
+ end
87
+
88
+ # URLs in resources that are not based inside the root requires that we may
89
+ # specify a "custom base" to properly handle relative paths. Here's an
90
+ # example:
91
+ #
92
+ # # /
93
+ # # document.html
94
+ # # images/
95
+ # # bg.png
96
+ # # stylesheets/
97
+ # # my_style.css
98
+ #
99
+ # # stylesheets/my_style.css
100
+ # body { background-image: url(../images/bg.png); }
101
+ #
102
+ # In this example, the stylesheet refers to /images/bg.png by using a
103
+ # relative path from /stylesheets/. In order to understand these cases, we
104
+ # need to specify where the "base" is in relation to the root.
105
+ describe "generating URLs with custom base" do
106
+ it "resolves relative paths" do
107
+ generator = UrlGenerator.new(host: "foo.com")
108
+ generator.generate_url("../images/bg.png", "/stylesheets").should == "http://foo.com/images/bg.png"
109
+ generator.generate_url("../images/bg.png", "email/stylesheets").should == "http://foo.com/email/images/bg.png"
110
+ generator.generate_url("images/bg.png", "email/").should == "http://foo.com/email/images/bg.png"
111
+ end
112
+
113
+ it "does not use the base when presented with a root-based path" do
114
+ generator = UrlGenerator.new(host: "foo.com")
115
+ generator.generate_url("/images/bg.png", "/stylesheets").should == "http://foo.com/images/bg.png"
116
+ generator.generate_url("/images/bg.png", "email/stylesheets").should == "http://foo.com/images/bg.png"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+ require 'shared_examples/url_rewriter'
3
+
4
+ module Roadie
5
+ describe UrlRewriter do
6
+ let(:generator) { double("URL generator") }
7
+ subject(:rewriter) { UrlRewriter.new(generator) }
8
+
9
+ it_behaves_like "url rewriter"
10
+
11
+ describe "transforming DOM trees" do
12
+ def dom_document(html); Nokogiri::HTML.parse html; end
13
+
14
+ it "rewrites all a[href]" do
15
+ generator.should_receive(:generate_url).with("some/path").and_return "http://foo.com/"
16
+ dom = dom_document <<-HTML
17
+ <body>
18
+ <a href="some/path">Some path</a>
19
+ </body>
20
+ HTML
21
+
22
+ expect {
23
+ rewriter.transform_dom dom
24
+ }.to change { dom.at_css("a")["href"] }.to "http://foo.com/"
25
+ end
26
+
27
+ it "rewrites relative img[src]" do
28
+ generator.should_receive(:generate_url).with("some/path.jpg").and_return "http://foo.com/image.jpg"
29
+ dom = dom_document <<-HTML
30
+ <body>
31
+ <img src="some/path.jpg">
32
+ </body>
33
+ HTML
34
+
35
+ expect {
36
+ rewriter.transform_dom dom
37
+ }.to change { dom.at_css("img")["src"] }.to "http://foo.com/image.jpg"
38
+ end
39
+
40
+ it "rewrites url() directives inside style attributes" do
41
+ generator.should_receive(:generate_url).with("some/path.jpg").and_return "http://foo.com/image.jpg"
42
+ dom = dom_document <<-HTML
43
+ <body>
44
+ <div style="background-image: url(&quot;some/path.jpg&quot;);">
45
+ </body>
46
+ HTML
47
+
48
+ expect {
49
+ rewriter.transform_dom dom
50
+ }.to change { dom.at_css("div")["style"] }.to 'background-image: url("http://foo.com/image.jpg");'
51
+ end
52
+ end
53
+
54
+ describe "transforming css" do
55
+ it "rewrites all url() directives" do
56
+ generator.should_receive(:generate_url).with("some/path.jpg").and_return "http://foo.com/image.jpg"
57
+ css = "body { background: top url(some/path.jpg) #eee; }"
58
+ expect {
59
+ rewriter.transform_css css
60
+ }.to change { css }.to "body { background: top url(http://foo.com/image.jpg) #eee; }"
61
+ end
62
+
63
+ it "correctly identifies URLs with single quotes" do
64
+ generator.should_receive(:generate_url).with("images/foo.png").and_return "x"
65
+ rewriter.transform_css "url('images/foo.png')"
66
+ end
67
+
68
+ it "correctly identifies URLs with double quotes" do
69
+ generator.should_receive(:generate_url).with("images/foo.png").and_return "x"
70
+ rewriter.transform_css 'url("images/foo.png")'
71
+ end
72
+
73
+ it "correctly identifies URLs with parenthesis inside them" do
74
+ generator.should_receive(:generate_url).with("images/map_(large_(extra)).png").and_return "x"
75
+ rewriter.transform_css 'url(images/map_(large_(extra)).png)'
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,11 @@
1
+ shared_examples_for "asset provider role" do
2
+ it "responds to #find_stylesheet" do
3
+ subject.should respond_to(:find_stylesheet)
4
+ subject.method(:find_stylesheet).arity.should == 1
5
+ end
6
+
7
+ it "responds to #find_stylesheet!" do
8
+ subject.should respond_to(:find_stylesheet!)
9
+ subject.method(:find_stylesheet!).arity.should == 1
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ shared_examples_for "url rewriter" do
2
+ it "is constructed with a generator" do
3
+ generator = double "URL generator"
4
+ expect {
5
+ described_class.new(generator)
6
+ }.to_not raise_error
7
+ end
8
+
9
+ it "has a #transform_dom(dom) method that returns nil" do
10
+ subject.should respond_to(:transform_dom)
11
+ subject.method(:transform_dom).arity.should == 1
12
+
13
+ dom = Nokogiri::HTML.parse "<body></body>"
14
+ subject.transform_dom(dom).should be_nil
15
+ end
16
+
17
+ it "has a #transform_css(css) method that returns nil" do
18
+ subject.should respond_to(:transform_css)
19
+ subject.method(:transform_css).arity.should == 1
20
+
21
+ subject.transform_css("").should be_nil
22
+ end
23
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,67 +1,12 @@
1
- $: << File.dirname(__FILE__) + '/../lib'
2
-
3
- require 'ostruct'
4
- require 'rubygems'
5
- require 'bundler'
6
-
7
- begin
8
- Bundler.setup(:default, :development)
9
- rescue Bundler::BundlerError => e
10
- $stderr.puts e.message
11
- $stderr.puts "Run `bundle install` to install missing gems"
12
- exit e.status_code
13
- end
14
-
15
- require 'rspec'
16
- require 'rails'
17
- require 'sprockets'
18
-
19
- require 'roadie/railtie'
20
- require 'action_mailer/railtie'
21
-
22
- FIXTURES_PATH = Pathname.new(File.dirname(__FILE__)).join('fixtures')
23
-
24
- class TestApplication < Rails::Application
25
- def config
26
- @config
27
- end
28
-
29
- def assets
30
- env = Sprockets::Environment.new
31
- env.append_path root.join('app','assets','stylesheets')
32
- env
33
- end
34
-
35
- def root
36
- FIXTURES_PATH
37
- end
38
-
39
- def reset_test_config
40
- @config = OpenStruct.new({
41
- :action_mailer => OpenStruct.new(:default_url_options => {}),
42
- :assets => OpenStruct.new(:enabled => false),
43
- :roadie => OpenStruct.new(:provider => nil, :enabled => true),
44
- })
45
- change_default_url_options(:host => "example.com")
46
- end
1
+ if ENV['CI']
2
+ require 'coveralls'
3
+ Coveralls.wear!
47
4
  end
48
5
 
49
- if Roadie::Railtie.respond_to?(:run_initializers)
50
- # Rails >= 3.1
51
- ActionMailer::Railtie.run_initializers(:default, Rails.application)
52
- Roadie::Railtie.run_initializers(:default, Rails.application)
53
- else
54
- # Rails 3.0
55
- Rails.application.config.active_support.deprecation = :log
56
- Rails.logger = Logger.new('/dev/null')
57
- Rails.application.initialize!
58
- end
6
+ $: << File.dirname(__FILE__) + '/../lib'
7
+ require 'roadie'
59
8
 
60
9
  RSpec.configure do |config|
61
- config.before(:each) do
62
- Rails.application.reset_test_config
63
- end
64
-
65
10
  config.treat_symbols_as_metadata_keys_with_true_values = true
66
11
  config.run_all_when_everything_filtered = true
67
12
  end
@@ -3,9 +3,9 @@ RSpec::Matchers.define :have_node do |selector|
3
3
  match do |document|
4
4
  node = document.at_css(selector)
5
5
  if @attributes
6
- node.present? and match_attributes(node.attributes)
6
+ node && match_attributes(node.attributes)
7
7
  else
8
- node.present?
8
+ node
9
9
  end
10
10
  end
11
11
 
@@ -1,5 +1,5 @@
1
1
  RSpec::Matchers.define :have_selector do |selector|
2
- match { |document| document.css(selector).present? }
2
+ match { |document| !document.css(selector).empty? }
3
3
  failure_message_for_should { "expected document to #{name_to_sentence}#{expected_to_sentence}"}
4
4
  failure_message_for_should_not { "expected document to not #{name_to_sentence}#{expected_to_sentence}"}
5
5
  end
@@ -1,25 +1,59 @@
1
1
  RSpec::Matchers.define :have_styling do |rules|
2
2
  @selector = 'body > *:first'
3
+ normalized_rules = StylingExpectation.new(rules)
3
4
 
4
- chain :at_selector do |selector|
5
- @selector = selector
5
+ chain(:at_selector) { |selector| @selector = selector }
6
+ match { |document| normalized_rules == styles_at_selector(document) }
7
+
8
+ description {
9
+ "have styles #{normalized_rules.inspect} at selector #{@selector.inspect}"
10
+ }
11
+
12
+ failure_message_for_should { |document|
13
+ "expected styles at #{@selector.inspect} to be:\n#{normalized_rules}\nbut was:\n#{styles_at_selector(document)}"
14
+ }
15
+
16
+ failure_message_for_should_not {
17
+ "expected styles at #{@selector.inspect} to not be:\n#{normalized_rules}"
18
+ }
19
+
20
+ def styles_at_selector(document)
21
+ document.should have_selector(@selector)
22
+ StylingExpectation.new document.at_css(@selector)['style']
6
23
  end
24
+ end
7
25
 
8
- match do |document|
9
- if rules.nil?
10
- parsed_styles(document).blank?
11
- else
12
- rules.to_a.should == parsed_styles(document)
26
+ class StylingExpectation
27
+ def initialize(styling)
28
+ case styling
29
+ when String then @rules = parse_rules(styling)
30
+ when Array then @rules = styling
31
+ when Hash then @rules = styling.to_a
32
+ else fail "I don't understand #{styling.inspect}!"
13
33
  end
14
34
  end
15
35
 
16
- description { "have styles #{rules.inspect} at selector #{@selector.inspect}" }
17
- failure_message_for_should { |document| "expected styles at #{@selector.inspect} to be #{rules.inspect} but was #{parsed_styles(document).inspect}" }
18
- failure_message_for_should_not { "expected styles at #{@selector.inspect} to not be #{rules.inspect}" }
36
+ def ==(other)
37
+ rules == other.rules
38
+ end
39
+
40
+ def to_s() rules.to_s end
19
41
 
20
- def parsed_styles(document)
21
- node = document.css(@selector).first
22
- SpecHelpers.styling_of_node(node)
42
+ protected
43
+ attr_reader :rules
44
+
45
+ private
46
+ def parse_rules(css)
47
+ css.split(';').map { |property| parse_property(property) }
48
+ end
49
+
50
+ def parse_property(property)
51
+ rule, value = property.split(':', 2).map(&:strip)
52
+ [rule, normalize_quotes(value)]
23
53
  end
24
- end
25
54
 
55
+ # JRuby's Nokogiri encodes quotes
56
+ def normalize_quotes(string)
57
+ string.gsub '%22', '"'
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ class TestProvider
2
+ include Roadie::AssetProvider
3
+
4
+ def initialize(files = {})
5
+ @files = files
6
+ @default = files[:default]
7
+ end
8
+
9
+ def find_stylesheet(name)
10
+ contents = @files.fetch(name, @default)
11
+ Roadie::Stylesheet.new name, contents if contents
12
+ end
13
+ end