roadie 2.4.3 → 3.0.0.pre1

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 (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