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