roadie 2.4.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +10 -14
  4. data/.yardopts +1 -1
  5. data/Changelog.md +38 -5
  6. data/Gemfile +3 -4
  7. data/Guardfile +12 -1
  8. data/README.md +168 -164
  9. data/Rakefile +2 -19
  10. data/lib/roadie.rb +15 -68
  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 +30 -60
  16. data/lib/roadie/inliner.rb +72 -217
  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 +71 -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 +43 -18
  24. data/lib/roadie/style_attribute_builder.rb +25 -0
  25. data/lib/roadie/style_block.rb +32 -0
  26. data/lib/roadie/style_property.rb +93 -0
  27. data/lib/roadie/stylesheet.rb +65 -0
  28. data/lib/roadie/upgrade_guide.rb +36 -0
  29. data/lib/roadie/url_generator.rb +126 -0
  30. data/lib/roadie/url_rewriter.rb +84 -0
  31. data/lib/roadie/version.rb +1 -1
  32. data/roadie.gemspec +8 -11
  33. data/spec/fixtures/big_em.css +1 -0
  34. data/spec/fixtures/stylesheets/green.css +1 -0
  35. data/spec/integration_spec.rb +125 -95
  36. data/spec/lib/roadie/asset_scanner_spec.rb +153 -0
  37. data/spec/lib/roadie/css_not_found_spec.rb +17 -0
  38. data/spec/lib/roadie/document_spec.rb +123 -0
  39. data/spec/lib/roadie/filesystem_provider_spec.rb +44 -68
  40. data/spec/lib/roadie/inliner_spec.rb +105 -537
  41. data/spec/lib/roadie/markup_improver_spec.rb +78 -0
  42. data/spec/lib/roadie/null_provider_spec.rb +21 -0
  43. data/spec/lib/roadie/null_url_rewriter_spec.rb +19 -0
  44. data/spec/lib/roadie/provider_list_spec.rb +89 -0
  45. data/spec/lib/roadie/selector_spec.rb +15 -10
  46. data/spec/lib/roadie/style_attribute_builder_spec.rb +29 -0
  47. data/spec/lib/roadie/style_block_spec.rb +35 -0
  48. data/spec/lib/roadie/style_property_spec.rb +82 -0
  49. data/spec/lib/roadie/stylesheet_spec.rb +41 -0
  50. data/spec/lib/roadie/test_provider_spec.rb +29 -0
  51. data/spec/lib/roadie/url_generator_spec.rb +121 -0
  52. data/spec/lib/roadie/url_rewriter_spec.rb +79 -0
  53. data/spec/shared_examples/asset_provider.rb +11 -0
  54. data/spec/shared_examples/url_rewriter.rb +23 -0
  55. data/spec/spec_helper.rb +6 -60
  56. data/spec/support/have_attribute_matcher.rb +2 -2
  57. data/spec/support/have_node_matcher.rb +4 -4
  58. data/spec/support/have_selector_matcher.rb +3 -3
  59. data/spec/support/have_styling_matcher.rb +51 -15
  60. data/spec/support/test_provider.rb +13 -0
  61. metadata +86 -175
  62. data/Appraisals +0 -15
  63. data/gemfiles/rails_3.0.gemfile +0 -7
  64. data/gemfiles/rails_3.0.gemfile.lock +0 -123
  65. data/gemfiles/rails_3.1.gemfile +0 -7
  66. data/gemfiles/rails_3.1.gemfile.lock +0 -126
  67. data/gemfiles/rails_3.2.gemfile +0 -7
  68. data/gemfiles/rails_3.2.gemfile.lock +0 -124
  69. data/gemfiles/rails_4.0.gemfile +0 -7
  70. data/gemfiles/rails_4.0.gemfile.lock +0 -119
  71. data/lib/roadie/action_mailer_extensions.rb +0 -95
  72. data/lib/roadie/asset_pipeline_provider.rb +0 -28
  73. data/lib/roadie/css_file_not_found.rb +0 -22
  74. data/lib/roadie/railtie.rb +0 -39
  75. data/lib/roadie/style_declaration.rb +0 -42
  76. data/spec/fixtures/app/assets/stylesheets/integration.css +0 -10
  77. data/spec/fixtures/public/stylesheets/integration.css +0 -10
  78. data/spec/fixtures/views/integration_mailer/marketing.html.erb +0 -2
  79. data/spec/fixtures/views/integration_mailer/notification.html.erb +0 -8
  80. data/spec/fixtures/views/integration_mailer/notification.text.erb +0 -6
  81. data/spec/lib/roadie/action_mailer_extensions_spec.rb +0 -227
  82. data/spec/lib/roadie/asset_pipeline_provider_spec.rb +0 -65
  83. data/spec/lib/roadie/css_file_not_found_spec.rb +0 -29
  84. data/spec/lib/roadie/style_declaration_spec.rb +0 -49
  85. data/spec/lib/roadie_spec.rb +0 -101
  86. data/spec/shared_examples/asset_provider_examples.rb +0 -11
  87. data/spec/support/anonymous_mailer.rb +0 -21
  88. data/spec/support/change_url_options.rb +0 -5
  89. data/spec/support/parse_styling.rb +0 -25
@@ -0,0 +1,78 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ module Roadie
5
+ describe MarkupImprover do
6
+ def improve(html)
7
+ dom = Nokogiri::HTML.parse html
8
+ MarkupImprover.new(dom, html).improve
9
+ dom
10
+ end
11
+
12
+ # JRuby up to at least 1.6.0 has a bug where the doctype of a document cannot be changed.
13
+ # See https://github.com/sparklemotion/nokogiri/issues/984
14
+ def pending_for_buggy_jruby
15
+ # No reason to check for version yet since no existing version has a fix.
16
+ skip "Pending until Nokogiri issue #984 is fixed and released" if defined?(JRuby)
17
+ end
18
+
19
+ describe "automatic doctype" do
20
+ it "inserts a HTML5 doctype if no doctype is present" do
21
+ pending_for_buggy_jruby
22
+ expect(improve("<html></html>").internal_subset.to_xml).to eq("<!DOCTYPE html>")
23
+ end
24
+
25
+ it "does not insert duplicate doctypes" do
26
+ html = improve('<!DOCTYPE html><html><body></body></html>').to_html
27
+ expect(html.scan('DOCTYPE').size).to eq(1)
28
+ end
29
+
30
+ it "leaves other doctypes alone" do
31
+ dtd = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">"
32
+ html = "#{dtd}<html></html>"
33
+ expect(improve(html).internal_subset.to_xml.strip).to eq(dtd)
34
+ end
35
+ end
36
+
37
+ describe "basic HTML structure" do
38
+ it "inserts a <html> element as the root" do
39
+ expect(improve("<h1>Hey!</h1>")).to have_selector("html h1")
40
+ expect(improve("<html></html>").css('html').size).to eq(1)
41
+ end
42
+
43
+ it "inserts <head> if not present" do
44
+ expect(improve('<html><body></body></html>')).to have_selector('html > head + body')
45
+ expect(improve('<html></html>')).to have_selector('html > head')
46
+ expect(improve('Foo')).to have_selector('html > head')
47
+ expect(improve('<html><head></head></html>').css('head').size).to eq(1)
48
+ end
49
+
50
+ it "inserts <body> if not present" do
51
+ expect(improve('<h1>Hey!</h1>')).to have_selector('html > body > h1')
52
+ expect(improve('<html><h1>Hey!</h1></html>')).to have_selector('html > body > h1')
53
+ expect(improve('<html><body><h1>Hey!</h1></body></html>').css('body').size).to eq(1)
54
+ end
55
+ end
56
+
57
+ describe "charset declaration" do
58
+ it "is inserted if missing" do
59
+ dom = improve('<html><head></head><body></body></html>')
60
+
61
+ expect(dom).to have_selector('head meta')
62
+ meta = dom.at_css('head meta')
63
+ expect(meta['http-equiv']).to eq('Content-Type')
64
+ expect(meta['content']).to eq('text/html; charset=UTF-8')
65
+ end
66
+
67
+ it "is left alone when predefined" do
68
+ expect(improve(<<-HTML).xpath('//meta')).to have(1).item
69
+ <html>
70
+ <head>
71
+ <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
72
+ </head>
73
+ </html>
74
+ HTML
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'shared_examples/asset_provider'
4
+
5
+ module Roadie
6
+ describe NullProvider do
7
+ it_behaves_like "asset provider role"
8
+
9
+ def expect_empty_stylesheet(stylesheet)
10
+ expect(stylesheet).not_to be_nil
11
+ expect(stylesheet.name).to eq("(null)")
12
+ expect(stylesheet).to have(0).blocks
13
+ expect(stylesheet.to_s).to be_empty
14
+ end
15
+
16
+ it "finds an empty stylesheet for every name" do
17
+ expect_empty_stylesheet NullProvider.new.find_stylesheet("omg wtf bbq")
18
+ expect_empty_stylesheet NullProvider.new.find_stylesheet!("omg wtf bbq")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'shared_examples/url_rewriter'
4
+
5
+ module Roadie
6
+ describe NullUrlRewriter do
7
+ let(:generator) { double "URL generator" }
8
+ subject(:rewriter) { NullUrlRewriter.new(generator) }
9
+
10
+ it_behaves_like "url rewriter"
11
+
12
+ it "does nothing when transforming DOM" do
13
+ dom = double "DOM tree"
14
+ expect {
15
+ NullUrlRewriter.new(generator).transform_dom dom
16
+ }.to_not raise_error
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,89 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'roadie/rspec'
4
+
5
+ module Roadie
6
+ describe ProviderList do
7
+ let(:test_provider) { TestProvider.new }
8
+ subject(:provider) { ProviderList.new([test_provider]) }
9
+
10
+ it_behaves_like "roadie asset provider", valid_name: "valid", invalid_name: "invalid" do
11
+ let(:test_provider) { TestProvider.new "valid" => "" }
12
+ end
13
+
14
+ it "finds using all given providers" do
15
+ first = TestProvider.new "foo.css" => "foo { color: green; }"
16
+ second = TestProvider.new "bar.css" => "bar { color: green; }"
17
+ provider = ProviderList.new [first, second]
18
+
19
+ expect(provider.find_stylesheet("foo.css").to_s).to include "foo"
20
+ expect(provider.find_stylesheet("bar.css").to_s).to include "bar"
21
+ expect(provider.find_stylesheet("baz.css")).to be_nil
22
+ end
23
+
24
+ it "is enumerable" do
25
+ expect(provider).to be_kind_of(Enumerable)
26
+ expect(provider).to respond_to(:each)
27
+ expect(provider.each.to_a).to eq([test_provider])
28
+ end
29
+
30
+ it "has a size" do
31
+ expect(provider.size).to eq(1)
32
+ expect(provider).not_to be_empty
33
+ end
34
+
35
+ it "has a first and a last element" do
36
+ providers = [double("1"), double("2"), double("3")]
37
+ list = ProviderList.new(providers)
38
+ expect(list.first).to eq(providers.first)
39
+ expect(list.last).to eq(providers.last)
40
+ end
41
+
42
+ it "can have providers pushed and popped" do
43
+ other = double "Some other provider"
44
+
45
+ expect {
46
+ provider.push other
47
+ provider << other
48
+ }.to change(provider, :size).by(2)
49
+
50
+ expect {
51
+ expect(provider.pop).to eq(other)
52
+ }.to change(provider, :size).by(-1)
53
+ end
54
+
55
+ it "can have providers shifted and unshifted" do
56
+ other = double "Some other provider"
57
+
58
+ expect {
59
+ provider.unshift other
60
+ }.to change(provider, :size).by(1)
61
+
62
+ expect {
63
+ expect(provider.shift).to eq(other)
64
+ }.to change(provider, :size).by(-1)
65
+ end
66
+
67
+ describe "wrapping" do
68
+ it "creates provider lists with the arguments" do
69
+ expect(ProviderList.wrap(test_provider)).to be_instance_of(ProviderList)
70
+ expect(ProviderList.wrap(test_provider, test_provider).size).to eq(2)
71
+ end
72
+
73
+ it "flattens arrays" do
74
+ expect(ProviderList.wrap([test_provider, test_provider], test_provider).size).to eq(3)
75
+ expect(ProviderList.wrap([test_provider, test_provider]).size).to eq(2)
76
+ end
77
+
78
+ it "combines with providers from other lists" do
79
+ other_list = ProviderList.new([test_provider, test_provider])
80
+ expect(ProviderList.wrap(test_provider, other_list).size).to eq(3)
81
+ end
82
+
83
+ it "returns the passed list if only a single ProviderList is passed" do
84
+ other_list = ProviderList.new([test_provider])
85
+ expect(ProviderList.wrap(other_list)).to eql other_list
86
+ end
87
+ end
88
+ end
89
+ end
@@ -4,11 +4,11 @@ require 'spec_helper'
4
4
  module Roadie
5
5
  describe Selector do
6
6
  it "can be coerced into String" do
7
- ("I love " + Selector.new("html")).should == "I love html"
7
+ expect("I love " + Selector.new("html")).to eq("I love html")
8
8
  end
9
9
 
10
10
  it "can be inlined when simple" do
11
- Selector.new("html body #main p.class").should be_inlinable
11
+ expect(Selector.new("html body #main p.class")).to be_inlinable
12
12
  end
13
13
 
14
14
  it "cannot be inlined when containing pseudo functions" do
@@ -27,32 +27,37 @@ module Roadie
27
27
  p:disabled
28
28
  p:checked
29
29
  ].each do |bad_selector|
30
- Selector.new(bad_selector).should_not be_inlinable
30
+ expect(Selector.new(bad_selector)).not_to be_inlinable
31
31
  end
32
32
 
33
- Selector.new('p.active').should be_inlinable
33
+ expect(Selector.new('p.active')).to be_inlinable
34
34
  end
35
35
 
36
36
  it "cannot be inlined when containing pseudo elements" do
37
- Selector.new('p::some-element').should_not be_inlinable
37
+ expect(Selector.new('p::some-element')).not_to be_inlinable
38
38
  end
39
39
 
40
40
  it "cannot be inlined when selector is an at-rule" do
41
- Selector.new('@keyframes progress-bar-stripes').should_not be_inlinable
41
+ expect(Selector.new('@keyframes progress-bar-stripes')).not_to be_inlinable
42
42
  end
43
43
 
44
44
  it "has a calculated specificity" do
45
45
  selector = "html p.active.nice #main.deep-selector"
46
- Selector.new(selector).specificity.should == CssParser.calculate_specificity(selector)
46
+ expect(Selector.new(selector).specificity).to eq(CssParser.calculate_specificity(selector))
47
+ end
48
+
49
+ it "can be told about the specificity at initialization" do
50
+ selector = "html p.active.nice #main.deep-selector"
51
+ expect(Selector.new(selector, 1337).specificity).to eq(1337)
47
52
  end
48
53
 
49
54
  it "is equal to other selectors when they match the same things" do
50
- Selector.new("foo").should == Selector.new("foo ")
51
- Selector.new("foo").should_not == "foo"
55
+ expect(Selector.new("foo")).to eq(Selector.new("foo "))
56
+ expect(Selector.new("foo")).not_to eq("foo")
52
57
  end
53
58
 
54
59
  it "strips the given selector" do
55
- Selector.new(" foo \n").to_s.should == Selector.new("foo").to_s
60
+ expect(Selector.new(" foo \n").to_s).to eq(Selector.new("foo").to_s)
56
61
  end
57
62
  end
58
63
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ module Roadie
4
+ describe StyleAttributeBuilder do
5
+ it "sorts the added properties" do
6
+ builder = StyleAttributeBuilder.new
7
+
8
+ builder << StyleProperty.new("color", "green", true, 1)
9
+ builder << StyleProperty.new("font-size", "110%", false, 15)
10
+ builder << StyleProperty.new("color", "red", false, 15)
11
+
12
+ expect(builder.attribute_string).to eq "font-size:110%;color:red;color:green !important"
13
+ end
14
+
15
+ it "preserves the order of added attributes with the same specificity" do
16
+ builder = StyleAttributeBuilder.new
17
+
18
+ builder << StyleProperty.new("color", "pink", false, 50)
19
+ builder << StyleProperty.new("color", "red", false, 50)
20
+ builder << StyleProperty.new("color", "green", false, 50)
21
+
22
+ # We need one different element to trigger the problem with Ruby's
23
+ # unstable sort
24
+ builder << StyleProperty.new("background", "white", false, 1)
25
+
26
+ expect(builder.attribute_string).to eq "background:white;color:pink;color:red;color:green"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ module Roadie
5
+ describe StyleBlock do
6
+ it "has a selector and a list of properties" do
7
+ properties = []
8
+ selector = double "Selector"
9
+
10
+ block = StyleBlock.new(selector, properties)
11
+ expect(block.selector).to eq(selector)
12
+ expect(block.properties).to eq(properties)
13
+ end
14
+
15
+ it "delegates #specificity to the selector" do
16
+ selector = double "Selector", specificity: 45
17
+ expect(StyleBlock.new(selector, []).specificity).to eq(45)
18
+ end
19
+
20
+ it "delegates #inlinable? to the selector" do
21
+ selector = double "Selector", inlinable?: "maybe"
22
+ expect(StyleBlock.new(selector, []).inlinable?).to eq("maybe")
23
+ end
24
+
25
+ it "delegates #selector_string to selector#to_s" do
26
+ selector = double "Selector", to_s: "yey"
27
+ expect(StyleBlock.new(selector, []).selector_string).to eq("yey")
28
+ end
29
+
30
+ it "has a string representation" do
31
+ properties = [double(to_s: "bar"), double(to_s: "baz")]
32
+ expect(StyleBlock.new(double(to_s: "foo"), properties).to_s).to eq("foo{bar;baz}")
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ module Roadie
4
+ describe StyleProperty do
5
+ it "is initialized with a property, value, if it is marked as important, and the specificity" do
6
+ StyleProperty.new('color', 'green', true, 45).tap do |declaration|
7
+ expect(declaration.property).to eq('color')
8
+ expect(declaration.value).to eq('green')
9
+ expect(declaration).to be_important
10
+ expect(declaration.specificity).to eq(45)
11
+ end
12
+ end
13
+
14
+ describe "string representation" do
15
+ it "is the property and the value joined with a colon" do
16
+ expect(StyleProperty.new('color', 'green', false, 1).to_s).to eq('color:green')
17
+ expect(StyleProperty.new('font-size', '1.1em', false, 1).to_s).to eq('font-size:1.1em')
18
+ end
19
+
20
+ it "contains the !important flag when set" do
21
+ expect(StyleProperty.new('color', 'green', true, 1).to_s).to eq('color:green !important')
22
+ end
23
+ end
24
+
25
+ describe "comparing" do
26
+ def declaration(specificity, important = false)
27
+ StyleProperty.new('color', 'green', important, specificity)
28
+ end
29
+
30
+ it "compares on specificity" do
31
+ expect(declaration(5)).to eq(declaration(5))
32
+ expect(declaration(4)).to be < declaration(5)
33
+ expect(declaration(6)).to be > declaration(5)
34
+ end
35
+
36
+ context "with an important declaration" do
37
+ it "is less than the important declaration regardless of the specificity" do
38
+ expect(declaration(99, false)).to be < declaration(1, true)
39
+ end
40
+
41
+ it "compares like normal when both declarations are important" do
42
+ expect(declaration(5, true)).to eq(declaration(5, true))
43
+ expect(declaration(4, true)).to be < declaration(5, true)
44
+ expect(declaration(6, true)).to be > declaration(5, true)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "parsing" do
50
+ def parsing(declaration, specificity)
51
+ property = StyleProperty.parse(declaration, specificity)
52
+ [property.property, property.value, property.important?, property.specificity]
53
+ end
54
+
55
+ it "understands simple declarations" do
56
+ expect(parsing("color: green", 1)).to eq(["color", "green", false, 1])
57
+ expect(parsing(" color:green; ", 1)).to eq(["color", "green", false, 1])
58
+ expect(parsing("color: green ", 1)).to eq(["color", "green", false, 1])
59
+ expect(parsing("color: green ; ", 1)).to eq(["color", "green", false, 1])
60
+ end
61
+
62
+ it "understands more complex values" do
63
+ expect(parsing("padding:0 1px 5rem 9%;", 89)).to eq(["padding", "0 1px 5rem 9%", false, 89])
64
+ end
65
+
66
+ it "understands more complex names" do
67
+ expect(parsing("font-size: 50%", 10)).to eq(["font-size", "50%", false, 10])
68
+ end
69
+
70
+ it "correctly reads !important declarations" do
71
+ expect(parsing("color: green !important", 1)).to eq(["color", "green", true, 1])
72
+ expect(parsing("color: green !important;", 1)).to eq(["color", "green", true, 1])
73
+ end
74
+
75
+ it "raises an error on unparseable declarations" do
76
+ expect {
77
+ parsing("I want a red apple!", 1)
78
+ }.to raise_error(Roadie::UnparseableDeclaration, /red apple/)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ module Roadie
5
+ describe Stylesheet do
6
+ it "is initialized with a name and CSS" do
7
+ stylesheet = Stylesheet.new("foo.css", "body { color: green; }")
8
+ expect(stylesheet.name).to eq("foo.css")
9
+ end
10
+
11
+ it "has a list of blocks" do
12
+ stylesheet = Stylesheet.new("foo.css", <<-CSS)
13
+ body { color: green !important; font-size: 200%; }
14
+ a, i { color: red; }
15
+ CSS
16
+ expect(stylesheet).to have(3).blocks
17
+ expect(stylesheet.blocks.map(&:to_s)).to eq([
18
+ "body{color:green !important;font-size:200%}",
19
+ "a{color:red}",
20
+ "i{color:red}",
21
+ ])
22
+ end
23
+
24
+ it "can iterate all inlinable blocks" do
25
+ inlinable = double(inlinable?: true, selector: "good", properties: "props")
26
+ bad = double(inlinable?: false, selector: "bad", properties: "props")
27
+
28
+ stylesheet = Stylesheet.new("example.css", "")
29
+ allow(stylesheet).to receive_messages blocks: [bad, inlinable, bad]
30
+
31
+ expect(stylesheet.each_inlinable_block.to_a).to eq([
32
+ ["good", "props"],
33
+ ])
34
+ end
35
+
36
+ it "has a string representation of the contents" do
37
+ stylesheet = Stylesheet.new("example.css", "body { color: green;}a{ color: red; font-size: small }")
38
+ expect(stylesheet.to_s).to eq("body{color:green}\na{color:red;font-size:small}")
39
+ end
40
+ end
41
+ end