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,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
+ pending "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
+ improve("<html></html>").internal_subset.to_xml.should == "<!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
+ html.scan('DOCTYPE').size.should == 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
+ improve(html).internal_subset.to_xml.strip.should == dtd
34
+ end
35
+ end
36
+
37
+ describe "basic HTML structure" do
38
+ it "inserts a <html> element as the root" do
39
+ improve("<h1>Hey!</h1>").should have_selector("html h1")
40
+ improve("<html></html>").css('html').size.should == 1
41
+ end
42
+
43
+ it "inserts <head> if not present" do
44
+ improve('<html><body></body></html>').should have_selector('html > head + body')
45
+ improve('<html></html>').should have_selector('html > head')
46
+ improve('Foo').should have_selector('html > head')
47
+ improve('<html><head></head></html>').css('head').size.should == 1
48
+ end
49
+
50
+ it "inserts <body> if not present" do
51
+ improve('<h1>Hey!</h1>').should have_selector('html > body > h1')
52
+ improve('<html><h1>Hey!</h1></html>').should have_selector('html > body > h1')
53
+ improve('<html><body><h1>Hey!</h1></body></html>').css('body').size.should == 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
+ dom.should have_selector('head meta')
62
+ meta = dom.at_css('head meta')
63
+ meta['http-equiv'].should == 'Content-Type'
64
+ meta['content'].should == 'text/html; charset=UTF-8'
65
+ end
66
+
67
+ it "is left alone when predefined" do
68
+ improve(<<-HTML).xpath('//meta').should 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
+ stylesheet.should_not be_nil
11
+ stylesheet.name.should == "(null)"
12
+ stylesheet.should have(0).blocks
13
+ stylesheet.to_s.should 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,81 @@
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
+ provider.find_stylesheet("foo.css").to_s.should include "foo"
20
+ provider.find_stylesheet("bar.css").to_s.should include "bar"
21
+ provider.find_stylesheet("baz.css").should be_nil
22
+ end
23
+
24
+ it "is enumerable" do
25
+ provider.should be_kind_of(Enumerable)
26
+ provider.should respond_to(:each)
27
+ provider.each.to_a.should == [test_provider]
28
+ end
29
+
30
+ it "has a size" do
31
+ provider.size.should == 1
32
+ end
33
+
34
+ it "can have providers pushed and popped" do
35
+ other = double "Some other provider"
36
+
37
+ expect {
38
+ provider.push other
39
+ provider << other
40
+ }.to change(provider, :size).by(2)
41
+
42
+ expect {
43
+ provider.pop.should == other
44
+ }.to change(provider, :size).by(-1)
45
+ end
46
+
47
+ it "can have providers shifted and unshifted" do
48
+ other = double "Some other provider"
49
+
50
+ expect {
51
+ provider.unshift other
52
+ }.to change(provider, :size).by(1)
53
+
54
+ expect {
55
+ provider.shift.should == other
56
+ }.to change(provider, :size).by(-1)
57
+ end
58
+
59
+ describe "wrapping" do
60
+ it "creates provider lists with the arguments" do
61
+ ProviderList.wrap(test_provider).should be_instance_of(ProviderList)
62
+ ProviderList.wrap(test_provider, test_provider).size.should == 2
63
+ end
64
+
65
+ it "flattens arrays" do
66
+ ProviderList.wrap([test_provider, test_provider], test_provider).size.should == 3
67
+ ProviderList.wrap([test_provider, test_provider]).size.should == 2
68
+ end
69
+
70
+ it "combines with providers from other lists" do
71
+ other_list = ProviderList.new([test_provider, test_provider])
72
+ ProviderList.wrap(test_provider, other_list).size.should == 3
73
+ end
74
+
75
+ it "returns the passed list if only a single ProviderList is passed" do
76
+ other_list = ProviderList.new([test_provider])
77
+ ProviderList.wrap(other_list).should eql other_list
78
+ end
79
+ end
80
+ end
81
+ end
@@ -19,13 +19,10 @@ module Roadie
19
19
  p:link
20
20
  p:target
21
21
  p:visited
22
- p:-ms-input-placeholder
23
- p:-moz-placeholder
24
22
  p:before
25
23
  p:after
26
- p:enabled
27
- p:disabled
28
- p:checked
24
+ p:-ms-input-placeholder
25
+ p:-moz-placeholder
29
26
  ].each do |bad_selector|
30
27
  Selector.new(bad_selector).should_not be_inlinable
31
28
  end
@@ -46,6 +43,11 @@ module Roadie
46
43
  Selector.new(selector).specificity.should == CssParser.calculate_specificity(selector)
47
44
  end
48
45
 
46
+ it "can be told about the specificity at initialization" do
47
+ selector = "html p.active.nice #main.deep-selector"
48
+ Selector.new(selector, 1337).specificity.should == 1337
49
+ end
50
+
49
51
  it "is equal to other selectors when they match the same things" do
50
52
  Selector.new("foo").should == Selector.new("foo ")
51
53
  Selector.new("foo").should_not == "foo"
@@ -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
+ block.selector.should == selector
12
+ block.properties.should == properties
13
+ end
14
+
15
+ it "delegates #specificity to the selector" do
16
+ selector = double "Selector", specificity: 45
17
+ StyleBlock.new(selector, []).specificity.should == 45
18
+ end
19
+
20
+ it "delegates #inlinable? to the selector" do
21
+ selector = double "Selector", inlinable?: "maybe"
22
+ StyleBlock.new(selector, []).inlinable?.should == "maybe"
23
+ end
24
+
25
+ it "delegates #selector_string to selector#to_s" do
26
+ selector = double "Selector", to_s: "yey"
27
+ StyleBlock.new(selector, []).selector_string.should == "yey"
28
+ end
29
+
30
+ it "has a string representation" do
31
+ properties = [double(to_s: "bar"), double(to_s: "baz")]
32
+ StyleBlock.new(double(to_s: "foo"), properties).to_s.should == "foo{bar;baz}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ module Roadie
5
+ describe StyleProperties do
6
+ it "has a list of properties" do
7
+ property = StyleProperty.new("color", "green", false, 1)
8
+ StyleProperties.new([property]).properties.should == [property]
9
+ end
10
+
11
+ it "can be merged with other properties" do
12
+ old = StyleProperty.new("color", "red", false, 1)
13
+ new = StyleProperty.new("color", "green", false, 5)
14
+ instance = StyleProperties.new([old])
15
+
16
+ instance.merge(StyleProperties.new([new])).properties.should == [old, new]
17
+ instance.merge([new]).properties.should == [old, new]
18
+
19
+ # Original is not mutated
20
+ instance.properties.should == [old]
21
+ end
22
+
23
+ it "can be destructively merged with other properties" do
24
+ old = StyleProperty.new("color", "red", false, 1)
25
+ new = StyleProperty.new("color", "green", false, 5)
26
+ instance = StyleProperties.new([old])
27
+
28
+ instance.merge!([new])
29
+ instance.properties.should == [old, new]
30
+ end
31
+
32
+ describe "string representation" do
33
+ class MockProperty
34
+ attr_reader :sort_value
35
+ include Comparable
36
+
37
+ def initialize(name, sort_value = 0)
38
+ @name, @sort_value = name, sort_value
39
+ end
40
+
41
+ def <=>(other) @sort_value <=> other.sort_value end
42
+ def to_s() @name.to_s end
43
+ end
44
+
45
+ it "joins properties together with semicolons" do
46
+ property = MockProperty.new("foo:bar")
47
+ StyleProperties.new([property, property]).to_s.should == "foo:bar;foo:bar"
48
+ end
49
+
50
+ it "sorts properties" do
51
+ important = MockProperty.new("super important", 100)
52
+ insignificant = MockProperty.new("insignificant", 2)
53
+ common = MockProperty.new("common", 20)
54
+
55
+ StyleProperties.new(
56
+ [important, insignificant, common]
57
+ ).to_s.should == "insignificant;common;super important"
58
+ end
59
+ end
60
+ end
61
+ 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
+ declaration.property.should == 'color'
8
+ declaration.value.should == 'green'
9
+ declaration.should be_important
10
+ declaration.specificity.should == 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
+ StyleProperty.new('color', 'green', false, 1).to_s.should == 'color:green'
17
+ StyleProperty.new('font-size', '1.1em', false, 1).to_s.should == 'font-size:1.1em'
18
+ end
19
+
20
+ it "contains the !important flag when set" do
21
+ StyleProperty.new('color', 'green', true, 1).to_s.should == '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
+ declaration(5).should be == declaration(5)
32
+ declaration(4).should be < declaration(5)
33
+ declaration(6).should 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
+ declaration(99, false).should be < declaration(1, true)
39
+ end
40
+
41
+ it "compares like normal when both declarations are important" do
42
+ declaration(5, true).should be == declaration(5, true)
43
+ declaration(4, true).should be < declaration(5, true)
44
+ declaration(6, true).should 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
+ parsing("color: green", 1).should == ["color", "green", false, 1]
57
+ parsing(" color:green; ", 1).should == ["color", "green", false, 1]
58
+ parsing("color: green ", 1).should == ["color", "green", false, 1]
59
+ parsing("color: green ; ", 1).should == ["color", "green", false, 1]
60
+ end
61
+
62
+ it "understands more complex values" do
63
+ parsing("padding:0 1px 5rem 9%;", 89).should == ["padding", "0 1px 5rem 9%", false, 89]
64
+ end
65
+
66
+ it "understands more complex names" do
67
+ parsing("font-size: 50%", 10).should == ["font-size", "50%", false, 10]
68
+ end
69
+
70
+ it "correctly reads !important declarations" do
71
+ parsing("color: green !important", 1).should == ["color", "green", true, 1]
72
+ parsing("color: green !important;", 1).should == ["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
+ stylesheet.name.should == "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
+ stylesheet.should have(3).blocks
17
+ stylesheet.blocks.map(&:to_s).should == [
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
+ stylesheet.stub blocks: [bad, inlinable, bad]
30
+
31
+ stylesheet.each_inlinable_block.to_a.should == [
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
+ stylesheet.to_s.should == "body{color:green}\na{color:red;font-size:small}"
39
+ end
40
+ end
41
+ end