style_cop 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.pairs +10 -0
- data/.rspec +2 -0
- data/README.md +45 -1
- data/lib/style_cop/capybara_webkit.rb +9 -0
- data/lib/style_cop/helpers.rb +14 -0
- data/lib/style_cop/match_styleguide_matcher.rb +16 -0
- data/lib/style_cop/selector.rb +68 -0
- data/lib/style_cop/selector_difference.rb +56 -0
- data/lib/style_cop/style_guide.rb +15 -0
- data/lib/style_cop.rb +7 -8
- data/spec/lib/match_styleguide_matcher_spec.rb +56 -0
- data/spec/lib/selector_difference_spec.rb +168 -0
- data/spec/lib/selector_spec.rb +89 -0
- data/spec/lib/style_cop_spec.rb +27 -0
- data/spec/lib/style_guide_spec.rb +23 -0
- data/spec/spec_helper.rb +10 -7
- data/spec/support/fake_page.rb +21 -0
- data/spec/support/html_helpers.rb +18 -0
- data/style_cop.gemspec +7 -5
- metadata +50 -25
- data/Gemfile.lock +0 -38
- data/lib/style_cop/assert_style.rb +0 -37
- data/lib/style_cop/public_methods.rb +0 -23
- data/lib/style_cop/register_style.rb +0 -17
- data/spec/lib/style_cop/assert_style_spec.rb +0 -52
- data/spec/lib/style_cop/public_methods_spec.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ccd9358deff944371ff06fcc84ad663b589c577
|
4
|
+
data.tar.gz: e293278f633c29acef072db79cac6880e4f7b9cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6005e11b8f026750264a673e4943c3c01e5c91553949c7c7257633ed9d5ba3932a53645110789b3e810aef7ad307bb3bfa89893dd91cc7eecf15695054f1b87
|
7
|
+
data.tar.gz: f532b4948694b8b5215b206c136bf5701ec6637c817db760ea78a202453ea337737fdc70e5d37b13de86d8c255463026ec295f173b9fe72f8b8ce935309a11ff
|
data/.gitignore
ADDED
data/.pairs
ADDED
data/.rspec
ADDED
data/README.md
CHANGED
@@ -1,2 +1,46 @@
|
|
1
1
|
# style_cop
|
2
|
-
|
2
|
+
Style Cop helps you enforce markup structure for front-end patterns via Rspec and Capybara. You spent all that time making your CSS play nicely. If all developers use the markup correctly, then it will render beautifully!
|
3
|
+
|
4
|
+
## Hypothesis
|
5
|
+
|
6
|
+
1. We believe developers / designers have a problem sticiking w established DOM patterns, rather than page-overriding to tweak style.
|
7
|
+
2. We can fix it by creating momentum to "use or improve" front-end CSS patterns by breaking the build if a pattern is used incorrectly
|
8
|
+
3. We know we are right if a team thinks this is a good idea and uses it on a project, and they tweet a link to the repo.
|
9
|
+
|
10
|
+
## MVP
|
11
|
+
|
12
|
+
What's the least amount of work we can do to in/vadidate this hypothesis?
|
13
|
+
|
14
|
+
* A gem that targets Rails projects that use Hologram
|
15
|
+
* It automagically "enforces" DOM and style as defined in the Hologram live style guide
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Note: StyleCop depends on CapybaraWebkit so you will need to make sure that installs first: https://github.com/thoughtbot/capybara-webkit
|
20
|
+
|
21
|
+
Put into your gemfile
|
22
|
+
|
23
|
+
gem style_cop
|
24
|
+
|
25
|
+
Bundle
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
## Getting Started
|
30
|
+
|
31
|
+
To test a selector on your page matches the same selector on your styleguide
|
32
|
+
you will need to do two things:
|
33
|
+
|
34
|
+
1. Add the class 'style-cop-pattern' to the selector in your styleguide
|
35
|
+
|
36
|
+
2. Add code like this somewhere in an integration test:
|
37
|
+
|
38
|
+
'''
|
39
|
+
context "some context", style_cop: true do
|
40
|
+
it "tests something" do
|
41
|
+
visit "/some/path"
|
42
|
+
selector = page.find(".your-selector")
|
43
|
+
expect(selector).to match_styleguide(styleguide_page("/your/styleguide/path"))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
'''
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module StyleCop
|
2
|
+
module Helpers
|
3
|
+
def styleguide_page(path)
|
4
|
+
old_session_name = Capybara.session_name
|
5
|
+
Capybara.session_name = "styleguide"
|
6
|
+
Capybara.visit path
|
7
|
+
Capybara.page.tap { Capybara.session_name = old_session_name }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.include(StyleCop::Helpers, style_cop: true)
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
RSpec::Matchers.define :match_styleguide do |capybara_styleguide|
|
2
|
+
match do |capybara_selector|
|
3
|
+
selector = StyleCop::Selector.new(capybara_selector)
|
4
|
+
styleguide = StyleCop::StyleGuide.new(capybara_styleguide)
|
5
|
+
styleguide_selector = styleguide.find(selector.key)
|
6
|
+
StyleCop::SelectorDifference.new(selector, styleguide_selector).empty?
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message_for_should do |capybara_selector|
|
10
|
+
selector = StyleCop::Selector.new(capybara_selector)
|
11
|
+
styleguide = StyleCop::StyleGuide.new(capybara_styleguide)
|
12
|
+
styleguide_selector = styleguide.find(selector.key)
|
13
|
+
StyleCop::SelectorDifference.new(selector, styleguide_selector).error_message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module StyleCop
|
2
|
+
class Selector
|
3
|
+
EXCLUDED_KEYS = ["width", "height", "top", "bottom", "right", "left"]
|
4
|
+
|
5
|
+
def initialize(selector)
|
6
|
+
@selector = selector
|
7
|
+
end
|
8
|
+
|
9
|
+
def key
|
10
|
+
if selector['class']
|
11
|
+
".#{selector['class'].gsub(' ', '.')}"
|
12
|
+
elsif selector['id']
|
13
|
+
"##{selector['id']}"
|
14
|
+
else
|
15
|
+
selector.tag_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def representation
|
20
|
+
clean_key = key.gsub(".style-cop-pattern", "")
|
21
|
+
return { clean_key => computed_style } if children.empty?
|
22
|
+
children_hash = children.map(&:representation).inject({}) { |hash, h| hash.merge!(h) }
|
23
|
+
Hash[children_hash.map { |key, value| ["#{clean_key} #{key}", value] }].merge(
|
24
|
+
clean_key => computed_style
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :selector
|
31
|
+
|
32
|
+
def computed_style
|
33
|
+
style_hash.tap do |hash|
|
34
|
+
EXCLUDED_KEYS.each { |key| hash.delete(key) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def children
|
39
|
+
selector.all(:xpath, "#{selector.path}/*").map do |child|
|
40
|
+
Selector.new(child)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def style_hash
|
45
|
+
Hash[css.split(/\s*;\s*/).map { |s| s.split(/\s*:\s*/) }]
|
46
|
+
end
|
47
|
+
|
48
|
+
def css
|
49
|
+
if computed_style = session.evaluate_script(computed_style_script)
|
50
|
+
computed_style["cssText"]
|
51
|
+
else
|
52
|
+
raise RuntimeError.new("Can't find css for #{selector.key}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def session
|
57
|
+
selector.session
|
58
|
+
end
|
59
|
+
|
60
|
+
def computed_style_script
|
61
|
+
%{
|
62
|
+
var node = document.evaluate("/#{selector.path}",
|
63
|
+
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue
|
64
|
+
window.getComputedStyle(node);
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module StyleCop
|
2
|
+
class SelectorDifference
|
3
|
+
MISSING_CSS_TEXT = "element is missing the following css"
|
4
|
+
EXTRA_CSS_TEXT = "element has the following extra css"
|
5
|
+
MISSING_STRUCTURE_TEXT = "element is missing the following structure piece"
|
6
|
+
EXTRA_STRUCTURE_TEXT = "element has the following extra structure piece"
|
7
|
+
|
8
|
+
def initialize(selector, other_selector)
|
9
|
+
@selector = selector
|
10
|
+
@other_selector = other_selector
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
selector.representation == other_selector.representation
|
15
|
+
end
|
16
|
+
|
17
|
+
def error_message
|
18
|
+
(css_errors + structure_errors).join(", ")
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :selector, :other_selector
|
24
|
+
|
25
|
+
def structure_errors
|
26
|
+
errors = []
|
27
|
+
extra_elements = selector.representation.keys - other_selector.representation.keys
|
28
|
+
missing_elements = other_selector.representation.keys - selector.representation.keys
|
29
|
+
errors << "The #{selector.key} #{EXTRA_STRUCTURE_TEXT}: #{extra_elements.join(", ")}" if extra_elements.any?
|
30
|
+
errors << "The #{selector.key} #{MISSING_STRUCTURE_TEXT}: #{missing_elements.join(", ")}" if missing_elements.any?
|
31
|
+
errors
|
32
|
+
end
|
33
|
+
|
34
|
+
def css_errors
|
35
|
+
errors = []
|
36
|
+
css_difference(other_selector, selector).each do |path, extra_css|
|
37
|
+
errors << "The #{selector.key} #{EXTRA_CSS_TEXT}: #{extra_css.map{|k,v| "#{k}: #{v}"}.join(', ')}" unless extra_css.empty?
|
38
|
+
end
|
39
|
+
css_difference(selector, other_selector).each do |path, extra_css|
|
40
|
+
errors << "The #{selector.key} #{MISSING_CSS_TEXT}: #{extra_css.map{|k,v| "#{k}: #{v}"}.join(', ')}" unless extra_css.empty?
|
41
|
+
end
|
42
|
+
errors
|
43
|
+
end
|
44
|
+
|
45
|
+
def css_difference(selector1, selector2)
|
46
|
+
difference = {}
|
47
|
+
selector2_representation = selector2.representation
|
48
|
+
selector1.representation.each do |path, selector1_css|
|
49
|
+
if selector2_css = selector2_representation[path]
|
50
|
+
difference[path] = Hash[selector2_css.to_a - selector1_css.to_a]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
difference
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module StyleCop
|
2
|
+
class StyleGuide
|
3
|
+
def initialize(styleguide)
|
4
|
+
@styleguide = styleguide
|
5
|
+
end
|
6
|
+
|
7
|
+
def find(selector_key)
|
8
|
+
Selector.new(styleguide.find("#{selector_key}.style-cop-pattern"))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :styleguide
|
14
|
+
end
|
15
|
+
end
|
data/lib/style_cop.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require "style_cop/capybara_webkit"
|
2
|
+
require "style_cop/selector"
|
3
|
+
require "style_cop/style_guide"
|
4
|
+
require "style_cop/match_styleguide_matcher"
|
5
|
+
require "style_cop/selector_difference"
|
6
|
+
require "style_cop/helpers"
|
6
7
|
|
7
|
-
|
8
|
-
c.include StyleCop::PublicMethods, type: :feature
|
8
|
+
module StyleCop
|
9
9
|
end
|
10
|
-
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module StyleCop
|
4
|
+
describe "MatchStyleguideMatcher" do
|
5
|
+
it "includes the matcher" do
|
6
|
+
expect(self).to respond_to(:match_styleguide)
|
7
|
+
end
|
8
|
+
|
9
|
+
context "when the selectors matches the styleguide" do
|
10
|
+
let(:html) do
|
11
|
+
create_html({
|
12
|
+
body: %(
|
13
|
+
<div class='selector'></div>
|
14
|
+
<div class='selector style-cop-pattern'></div>
|
15
|
+
)
|
16
|
+
})
|
17
|
+
end
|
18
|
+
let(:page) { FakePage.new(html) }
|
19
|
+
let(:selector) { page.all(".selector").first }
|
20
|
+
|
21
|
+
it "passes" do
|
22
|
+
expect(selector).to match_styleguide(page)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when the selector doesn't match the styleguide" do
|
27
|
+
let(:html) do
|
28
|
+
create_html({
|
29
|
+
body: %(
|
30
|
+
<div class='selector'></div>
|
31
|
+
<div class='selector style-cop-pattern'><div class='wrong'></div></div>
|
32
|
+
)
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:page) { FakePage.new(html) }
|
37
|
+
let(:selector) { page.all(".selector").first }
|
38
|
+
let(:styleguide_selector) { page.find(".selector.style-cop-pattern") }
|
39
|
+
let(:selector_difference) { double(SelectorDifference, empty?: false) }
|
40
|
+
|
41
|
+
it "doesn't pass" do
|
42
|
+
expect(selector).to_not match_styleguide(page)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "displays error messages when matcher fails" do
|
46
|
+
allow(SelectorDifference).to receive(:new).
|
47
|
+
and_return(selector_difference)
|
48
|
+
expect(selector_difference).to receive(:error_message) { "selector to match" }
|
49
|
+
|
50
|
+
expect {
|
51
|
+
expect(selector).to match_styleguide(page)
|
52
|
+
}.to raise_error(/selector to match/)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module StyleCop
|
4
|
+
describe SelectorDifference do
|
5
|
+
let(:selector) { double }
|
6
|
+
let(:styleguide_selector) { double }
|
7
|
+
subject { SelectorDifference.new(selector, styleguide_selector) }
|
8
|
+
|
9
|
+
describe "#error_message" do
|
10
|
+
before do
|
11
|
+
allow(selector).to receive(:key).and_return(".structure")
|
12
|
+
allow(selector).to receive(:representation).and_return(selector_representation)
|
13
|
+
allow(styleguide_selector).to receive(:representation).and_return(styleguide_representation)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "css" do
|
17
|
+
context "missing styleguide css" do
|
18
|
+
let(:selector_representation) { { ".selector" => {"a"=>"b"} } }
|
19
|
+
let(:styleguide_representation) { { ".selector" => {"a"=>"b", "font-size"=>"100px"} } }
|
20
|
+
|
21
|
+
it "shows what css the element is missing" do
|
22
|
+
message = "The .structure element is missing the following css: font-size: 100px"
|
23
|
+
expect(subject.error_message).to eq message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "extra css not in styleguide" do
|
28
|
+
let(:selector_representation) { { ".selector" => {"a"=>"b", "font-size"=>"100px"} } }
|
29
|
+
let(:styleguide_representation) { { ".selector" => {"a"=>"b"} } }
|
30
|
+
|
31
|
+
it "shows what extra css is present" do
|
32
|
+
message = "The .structure element has the following extra css: font-size: 100px"
|
33
|
+
expect(subject.error_message).to eq message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "missing css in the styleguide and has extra css not in the styleguide" do
|
38
|
+
let(:selector_representation) { {".selector" => { "font-size"=>"100px"} } }
|
39
|
+
let(:styleguide_representation) { {".selector" => { "font-size"=>"80px"} } }
|
40
|
+
|
41
|
+
it "shows what extra css is present" do
|
42
|
+
message = "The .structure element has the following extra css: font-size: 100px, The .structure element is missing the following css: font-size: 80px"
|
43
|
+
expect(subject.error_message).to include message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "structure" do
|
49
|
+
context "missing styleguide structure" do
|
50
|
+
let(:selector_representation) { { ".selector .child" => {} } }
|
51
|
+
let(:styleguide_representation) { { ".selector .child" => {}, ".selector .child .missing" => {} } }
|
52
|
+
|
53
|
+
it "shows what structure is missing from the element" do
|
54
|
+
message = "The .structure element is missing the following structure piece: .selector .child .missing"
|
55
|
+
expect(subject.error_message).to eq message
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "extra structure not in styleguide" do
|
60
|
+
let(:selector_representation) { { ".selector .child" => {}, ".selector .child .extra" => {} } }
|
61
|
+
let(:styleguide_representation) { { ".selector .child" => {} } }
|
62
|
+
|
63
|
+
it "shows what extra structure is present" do
|
64
|
+
message = "The .structure element has the following extra structure piece: .selector .child .extra"
|
65
|
+
expect(subject.error_message).to eq message
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#empty?" do
|
72
|
+
let(:first_selector) { Selector.new page.all(".selector").first }
|
73
|
+
let(:last_selector) { Selector.new page.all(".selector").last }
|
74
|
+
let(:page) { FakePage.new(html) }
|
75
|
+
subject { SelectorDifference.new(first_selector, last_selector) }
|
76
|
+
|
77
|
+
context "when two selectors have same css" do
|
78
|
+
let(:html) do
|
79
|
+
create_html({
|
80
|
+
body: %{
|
81
|
+
<div class="selector"></div>
|
82
|
+
<div class="selector"></div>
|
83
|
+
}
|
84
|
+
})
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns true" do
|
88
|
+
expect(subject).to be_empty
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when two selectors don't have same css" do
|
93
|
+
let(:html) do
|
94
|
+
create_html({
|
95
|
+
body: %{
|
96
|
+
<div class="selector"></div>
|
97
|
+
<div class="selector" style="font-size: 100px"></div>
|
98
|
+
}
|
99
|
+
})
|
100
|
+
end
|
101
|
+
|
102
|
+
it "returns false" do
|
103
|
+
expect(subject).to_not be_empty
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when two selectors have same structure" do
|
108
|
+
let(:html) do
|
109
|
+
create_html({
|
110
|
+
body: %{
|
111
|
+
<div class="selector"><div class="child2"></div></div>
|
112
|
+
<div class="selector"><div class="child2"></div></div>
|
113
|
+
}
|
114
|
+
})
|
115
|
+
end
|
116
|
+
|
117
|
+
it "returns true" do
|
118
|
+
expect(subject).to be_empty
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "when two selectors don't have same structure" do
|
123
|
+
let(:html) do
|
124
|
+
create_html({
|
125
|
+
body: %{
|
126
|
+
<div class="selector"></div>
|
127
|
+
<div class="selector"><div class="child2"></div></div>
|
128
|
+
}
|
129
|
+
})
|
130
|
+
end
|
131
|
+
|
132
|
+
it "returns false" do
|
133
|
+
expect(subject).to_not be_empty
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "when two selectors children have same css" do
|
138
|
+
let(:html) do
|
139
|
+
create_html({
|
140
|
+
body: %{
|
141
|
+
<div class="selector"><div class="child2"></div></div>
|
142
|
+
<div class="selector"><div class="child2"></div></div>
|
143
|
+
}
|
144
|
+
})
|
145
|
+
end
|
146
|
+
|
147
|
+
it "returns false" do
|
148
|
+
expect(subject).to be_empty
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when two selectors children don't have same css" do
|
153
|
+
let(:html) do
|
154
|
+
create_html({
|
155
|
+
body: %{
|
156
|
+
<div class="selector"><div class="child2"></div></div>
|
157
|
+
<div class="selector"><div class="child2" style="font-size:100px"></div></div>
|
158
|
+
}
|
159
|
+
})
|
160
|
+
end
|
161
|
+
|
162
|
+
it "returns false" do
|
163
|
+
expect(subject).to_not be_empty
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module StyleCop
|
4
|
+
describe Selector, style_cop: true do
|
5
|
+
describe "#key" do
|
6
|
+
let(:page) { FakePage.new(selector_html) }
|
7
|
+
let(:capybara_selector) { page.find("span") }
|
8
|
+
let(:selector) { Selector.new(capybara_selector) }
|
9
|
+
|
10
|
+
context "for a class" do
|
11
|
+
let(:selector_html) do
|
12
|
+
create_html(body: "<span class='selector other-selector' id='ignored'></span>")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns the classes separated by a dot" do
|
16
|
+
expect(selector.key).to eq(".selector.other-selector")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "for an id" do
|
21
|
+
let(:selector_html) do
|
22
|
+
create_html(body: "<span id='selector'></span>")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns the id prefixed by a pound" do
|
26
|
+
expect(selector.key).to eq("#selector")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "for an html element" do
|
31
|
+
let(:selector_html) do
|
32
|
+
create_html(body: "<span></span>")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns html element" do
|
36
|
+
expect(selector.key).to eq("span")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#representation" do
|
42
|
+
let(:html) do
|
43
|
+
create_html({
|
44
|
+
body: %{
|
45
|
+
<div class="selector style-cop-pattern" style="font-size:16px">
|
46
|
+
<div class="child1" style="font-size:24px">
|
47
|
+
<div class="child3 style-cop-pattern" style="font-size:36px"></div>
|
48
|
+
</div>
|
49
|
+
<div class="child2"></div>
|
50
|
+
</div>
|
51
|
+
}
|
52
|
+
})
|
53
|
+
end
|
54
|
+
|
55
|
+
let(:page) { FakePage.new(html) }
|
56
|
+
let(:selector) { Selector.new page.find(".selector") }
|
57
|
+
|
58
|
+
it "returns a hash with structure keys andd css values" do
|
59
|
+
expect(selector.representation.keys.sort).to eq(
|
60
|
+
[".selector", ".selector .child1", ".selector .child1 .child3", ".selector .child2"]
|
61
|
+
)
|
62
|
+
expect(selector.representation[".selector"]["font-size"]).to eq("16px")
|
63
|
+
expect(selector.representation[".selector .child1"]["font-size"]).to eq("24px")
|
64
|
+
expect(selector.representation[".selector .child1 .child3"]["font-size"]).to eq("36px")
|
65
|
+
expect(selector.representation[".selector .child2"]["font-size"]).to eq("16px")
|
66
|
+
end
|
67
|
+
|
68
|
+
context "excluded keys in style" do
|
69
|
+
let(:html) do
|
70
|
+
create_html({
|
71
|
+
body: %{
|
72
|
+
<div class="selector"></div>
|
73
|
+
}
|
74
|
+
})
|
75
|
+
end
|
76
|
+
|
77
|
+
let(:selector) { Selector.new(page.find(".selector")) }
|
78
|
+
subject { selector.representation['.selector'] }
|
79
|
+
|
80
|
+
it { should_not have_key("width") }
|
81
|
+
it { should_not have_key("height") }
|
82
|
+
it { should_not have_key("top") }
|
83
|
+
it { should_not have_key("bottom") }
|
84
|
+
it { should_not have_key("right") }
|
85
|
+
it { should_not have_key("left") }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe StyleCop do
|
4
|
+
context "capybara driver" do
|
5
|
+
it "doesn't change the current driver by default" do
|
6
|
+
expect(Capybara.current_driver).to_not eq(:webkit)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "changes to current driver for style cop specs", style_cop: true do
|
10
|
+
expect(Capybara.current_driver).to eq(:webkit)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "styleguide_page" do
|
15
|
+
it "doesn't add the helper method for non-style cop tests" do
|
16
|
+
expect(self).to_not respond_to(:styleguide_page)
|
17
|
+
end
|
18
|
+
|
19
|
+
context "style cop tests", style_cop: true do
|
20
|
+
it "have a styleguide_page method" do
|
21
|
+
Capybara.app = FakePage::TestApp
|
22
|
+
Capybara.app.set_html("<html></html>")
|
23
|
+
expect(styleguide_page("/")).to_not be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module StyleCop
|
4
|
+
describe StyleGuide do
|
5
|
+
describe "#find" do
|
6
|
+
let(:styleguide_html) do
|
7
|
+
create_html({
|
8
|
+
body: %{
|
9
|
+
<div class="selector first style-cop-pattern"></div>
|
10
|
+
<div class="selector second style-cop-pattern"></div>
|
11
|
+
}
|
12
|
+
})
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:styleguide_page) { FakePage.new(styleguide_html) }
|
16
|
+
let(:styleguide) { StyleGuide.new(styleguide_page) }
|
17
|
+
|
18
|
+
it "finds elements in the styleguide by key" do
|
19
|
+
expect(styleguide.find(".selector.first").key).to eq(".selector.first.style-cop-pattern")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
4
|
+
require "pry"
|
5
|
+
require "style_cop"
|
6
|
+
require "rspec"
|
7
|
+
require "capybara"
|
8
|
+
require "capybara-webkit"
|
9
|
+
require "sinatra"
|
7
10
|
|
8
|
-
ENV[
|
11
|
+
ENV["test_run"] = true.to_s
|
9
12
|
|
10
|
-
Dir[File.join(File.dirname(__FILE__),
|
13
|
+
Dir[File.join(File.dirname(__FILE__), "support", "**", "*.rb")].each do |file|
|
11
14
|
require file
|
12
15
|
end
|
13
16
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class FakePage
|
2
|
+
def initialize(html)
|
3
|
+
@session = Capybara::Session.new(:webkit, TestApp)
|
4
|
+
@session.app.set_html(html)
|
5
|
+
@session.visit "/"
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(method, *args)
|
9
|
+
@session.public_send(method, *args)
|
10
|
+
end
|
11
|
+
|
12
|
+
class TestApp < Sinatra::Base
|
13
|
+
def self.set_html(html)
|
14
|
+
@@html = html
|
15
|
+
end
|
16
|
+
|
17
|
+
get '/' do
|
18
|
+
@@html
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module HtmlHelpers
|
2
|
+
def create_html(options = {})
|
3
|
+
%{<html>
|
4
|
+
<head>
|
5
|
+
<style>
|
6
|
+
#{options[:style]}
|
7
|
+
</style>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
#{options[:body]}
|
11
|
+
</body>
|
12
|
+
</html>}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.include(HtmlHelpers)
|
18
|
+
end
|
data/style_cop.gemspec
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'style_cop'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.2'
|
4
4
|
s.date = '2014-03-03'
|
5
5
|
s.summary = "Gem for testing style"
|
6
6
|
s.description = "Gem for testing style"
|
7
|
-
s.authors = ["Ward Penney", "David Tengdin"]
|
7
|
+
s.authors = ["Ward Penney", "David Tengdin", "Jordi Noguera"]
|
8
8
|
s.email = 'style-cop@googlegroups.com'
|
9
9
|
s.files = `git ls-files`.split("\n")
|
10
10
|
s.homepage = 'http://rubygems.org/gems/style_cop'
|
11
11
|
s.license = 'MIT'
|
12
12
|
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
|
13
|
+
s.add_dependency "rspec"
|
14
|
+
s.add_dependency "capybara-webkit"
|
15
|
+
|
16
|
+
s.add_development_dependency "pry"
|
17
|
+
s.add_development_dependency "sinatra"
|
16
18
|
end
|
metadata
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: style_cop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ward Penney
|
8
8
|
- David Tengdin
|
9
|
+
- Jordi Noguera
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
@@ -15,60 +16,84 @@ dependencies:
|
|
15
16
|
name: rspec
|
16
17
|
requirement: !ruby/object:Gem::Requirement
|
17
18
|
requirements:
|
18
|
-
- -
|
19
|
+
- - ">="
|
19
20
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
21
|
-
type: :
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
22
23
|
prerelease: false
|
23
24
|
version_requirements: !ruby/object:Gem::Requirement
|
24
25
|
requirements:
|
25
|
-
- -
|
26
|
+
- - ">="
|
26
27
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
28
|
+
version: '0'
|
28
29
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
30
|
+
name: capybara-webkit
|
30
31
|
requirement: !ruby/object:Gem::Requirement
|
31
32
|
requirements:
|
32
|
-
- -
|
33
|
+
- - ">="
|
33
34
|
- !ruby/object:Gem::Version
|
34
|
-
version:
|
35
|
-
type: :
|
35
|
+
version: '0'
|
36
|
+
type: :runtime
|
36
37
|
prerelease: false
|
37
38
|
version_requirements: !ruby/object:Gem::Requirement
|
38
39
|
requirements:
|
39
|
-
- -
|
40
|
+
- - ">="
|
40
41
|
- !ruby/object:Gem::Version
|
41
|
-
version:
|
42
|
+
version: '0'
|
42
43
|
- !ruby/object:Gem::Dependency
|
43
44
|
name: pry
|
44
45
|
requirement: !ruby/object:Gem::Requirement
|
45
46
|
requirements:
|
46
|
-
- -
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: sinatra
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
47
62
|
- !ruby/object:Gem::Version
|
48
|
-
version: 0
|
63
|
+
version: '0'
|
49
64
|
type: :development
|
50
65
|
prerelease: false
|
51
66
|
version_requirements: !ruby/object:Gem::Requirement
|
52
67
|
requirements:
|
53
|
-
- -
|
68
|
+
- - ">="
|
54
69
|
- !ruby/object:Gem::Version
|
55
|
-
version: 0
|
70
|
+
version: '0'
|
56
71
|
description: Gem for testing style
|
57
72
|
email: style-cop@googlegroups.com
|
58
73
|
executables: []
|
59
74
|
extensions: []
|
60
75
|
extra_rdoc_files: []
|
61
76
|
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".pairs"
|
79
|
+
- ".rspec"
|
62
80
|
- Gemfile
|
63
|
-
- Gemfile.lock
|
64
81
|
- README.md
|
65
82
|
- lib/style_cop.rb
|
66
|
-
- lib/style_cop/
|
67
|
-
- lib/style_cop/
|
68
|
-
- lib/style_cop/
|
69
|
-
-
|
70
|
-
-
|
83
|
+
- lib/style_cop/capybara_webkit.rb
|
84
|
+
- lib/style_cop/helpers.rb
|
85
|
+
- lib/style_cop/match_styleguide_matcher.rb
|
86
|
+
- lib/style_cop/selector.rb
|
87
|
+
- lib/style_cop/selector_difference.rb
|
88
|
+
- lib/style_cop/style_guide.rb
|
89
|
+
- spec/lib/match_styleguide_matcher_spec.rb
|
90
|
+
- spec/lib/selector_difference_spec.rb
|
91
|
+
- spec/lib/selector_spec.rb
|
92
|
+
- spec/lib/style_cop_spec.rb
|
93
|
+
- spec/lib/style_guide_spec.rb
|
71
94
|
- spec/spec_helper.rb
|
95
|
+
- spec/support/fake_page.rb
|
96
|
+
- spec/support/html_helpers.rb
|
72
97
|
- style_cop.gemspec
|
73
98
|
homepage: http://rubygems.org/gems/style_cop
|
74
99
|
licenses:
|
@@ -80,17 +105,17 @@ require_paths:
|
|
80
105
|
- lib
|
81
106
|
required_ruby_version: !ruby/object:Gem::Requirement
|
82
107
|
requirements:
|
83
|
-
- -
|
108
|
+
- - ">="
|
84
109
|
- !ruby/object:Gem::Version
|
85
110
|
version: '0'
|
86
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
112
|
requirements:
|
88
|
-
- -
|
113
|
+
- - ">="
|
89
114
|
- !ruby/object:Gem::Version
|
90
115
|
version: '0'
|
91
116
|
requirements: []
|
92
117
|
rubyforge_project:
|
93
|
-
rubygems_version: 2.
|
118
|
+
rubygems_version: 2.2.2
|
94
119
|
signing_key:
|
95
120
|
specification_version: 4
|
96
121
|
summary: Gem for testing style
|
data/Gemfile.lock
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
style_cop (0.0.1)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: http://rubygems.org/
|
8
|
-
specs:
|
9
|
-
coderay (1.1.0)
|
10
|
-
diff-lcs (1.2.5)
|
11
|
-
method_source (0.8.2)
|
12
|
-
mini_portile (0.5.2)
|
13
|
-
nokogiri (1.6.1)
|
14
|
-
mini_portile (~> 0.5.0)
|
15
|
-
pry (0.9.12.6)
|
16
|
-
coderay (~> 1.0)
|
17
|
-
method_source (~> 0.8)
|
18
|
-
slop (~> 3.4)
|
19
|
-
rspec (2.14.1)
|
20
|
-
rspec-core (~> 2.14.0)
|
21
|
-
rspec-expectations (~> 2.14.0)
|
22
|
-
rspec-mocks (~> 2.14.0)
|
23
|
-
rspec-core (2.14.8)
|
24
|
-
rspec-expectations (2.14.5)
|
25
|
-
diff-lcs (>= 1.1.3, < 2.0)
|
26
|
-
rspec-mocks (2.14.6)
|
27
|
-
slop (3.4.7)
|
28
|
-
xpath (2.0.0)
|
29
|
-
nokogiri (~> 1.3)
|
30
|
-
|
31
|
-
PLATFORMS
|
32
|
-
ruby
|
33
|
-
|
34
|
-
DEPENDENCIES
|
35
|
-
pry (~> 0.9.12.6)
|
36
|
-
rspec (~> 2.14.0)
|
37
|
-
style_cop!
|
38
|
-
xpath (~> 2.0.0)
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module StyleCop
|
2
|
-
class AssertStyle
|
3
|
-
def initialize(selector, rules, test_context)
|
4
|
-
@selector = selector
|
5
|
-
@rules = rules
|
6
|
-
@test_context = test_context
|
7
|
-
end
|
8
|
-
|
9
|
-
def has_correct_structure
|
10
|
-
selector_present ? fulfills_all_rules : raise_error
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
attr_reader :selector, :test_context, :rules
|
16
|
-
|
17
|
-
def selector_present
|
18
|
-
test_context.has_css?(selector)
|
19
|
-
end
|
20
|
-
|
21
|
-
def raise_error
|
22
|
-
raise StandardError, "Selector: #{selector} is not present."
|
23
|
-
end
|
24
|
-
|
25
|
-
def fulfills_all_rules
|
26
|
-
test_context.page.all(selector).each do |element|
|
27
|
-
has(element)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def has(element)
|
32
|
-
rules.each do |node|
|
33
|
-
test_context.expect(element).to test_context.have_selector(node)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module StyleCop
|
2
|
-
module PublicMethods
|
3
|
-
def has_child(selector)
|
4
|
-
RegisterStyle.add_rule("> #{selector}")
|
5
|
-
end
|
6
|
-
|
7
|
-
def has_nested_children(*args)
|
8
|
-
rule = ""
|
9
|
-
args.each do |arg|
|
10
|
-
rule = "#{rule}> #{arg} "
|
11
|
-
end
|
12
|
-
RegisterStyle.add_rule(rule.strip)
|
13
|
-
end
|
14
|
-
|
15
|
-
def register_style_structure_for(selector, &block)
|
16
|
-
block ? RegisterStyle.new(selector, &block) : (raise(StandardError, "No block given."))
|
17
|
-
end
|
18
|
-
|
19
|
-
def assert_style_structure_for(selector)
|
20
|
-
AssertStyle.new(selector, RegisterStyle.rules, self).has_correct_structure
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe StyleCop::AssertStyle do
|
4
|
-
subject{ StyleCop::AssertStyle.new(selector, rules, test_context) }
|
5
|
-
let(:selector) { '.selector' }
|
6
|
-
let(:rules) { double('rules') }
|
7
|
-
let(:test_context) { double('test_context') }
|
8
|
-
|
9
|
-
describe "has_correct_structure" do
|
10
|
-
before do
|
11
|
-
allow(test_context).to receive(:has_css?) { has_selector }
|
12
|
-
end
|
13
|
-
|
14
|
-
context "selector present" do
|
15
|
-
let(:has_selector) { true }
|
16
|
-
|
17
|
-
let(:rule1) { double('rule1') }
|
18
|
-
let(:rule2) { double('rule2') }
|
19
|
-
let(:element1) { double('element1') }
|
20
|
-
let(:element2) { double('element2') }
|
21
|
-
let(:node1) { double('node1') }
|
22
|
-
let(:node2) { double('node2') }
|
23
|
-
let(:rules) { [rule1, rule2] }
|
24
|
-
|
25
|
-
before do
|
26
|
-
test_context.stub_chain(:page, :all) { [element1, element2] }
|
27
|
-
end
|
28
|
-
|
29
|
-
it "calls an expectation on elements" do
|
30
|
-
expect(test_context).to receive(:expect).with(element1).twice { node1 }
|
31
|
-
expect(test_context).to receive(:expect).with(element2).twice { node2 }
|
32
|
-
allow(node1).to receive(:to)
|
33
|
-
allow(node2).to receive(:to)
|
34
|
-
expect(test_context).to receive(:have_selector).with(rule1).twice
|
35
|
-
expect(test_context).to receive(:have_selector).with(rule2).twice
|
36
|
-
|
37
|
-
subject.has_correct_structure
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
context "selector not present" do
|
42
|
-
let(:has_selector) { false }
|
43
|
-
|
44
|
-
it "raises a StandardError" do
|
45
|
-
expect { subject.has_correct_structure }.to raise_error { |error|
|
46
|
-
expect(error.message).to eq "Selector: .selector is not present."
|
47
|
-
error.should be_a(StandardError)
|
48
|
-
}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe StyleCop::PublicMethods do
|
4
|
-
include StyleCop::PublicMethods
|
5
|
-
let(:selector) {'.foo'}
|
6
|
-
|
7
|
-
describe 'has_child' do
|
8
|
-
|
9
|
-
it 'calls add_rule on RegisterStyle' do
|
10
|
-
expect(StyleCop::RegisterStyle).to receive(:add_rule).with('> .foo')
|
11
|
-
has_child selector
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
describe 'has_nested_children' do
|
16
|
-
let(:selector1) {'.foo'}
|
17
|
-
let(:selector2) {'.bar'}
|
18
|
-
|
19
|
-
it 'calls add_rule on RegisterStyle' do
|
20
|
-
expect(StyleCop::RegisterStyle).to receive(:add_rule).with('> .foo > .bar')
|
21
|
-
has_nested_children(selector1, selector2)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
describe 'register_style_structure_for' do
|
26
|
-
let(:block) { lambda{} }
|
27
|
-
|
28
|
-
context 'block is given' do
|
29
|
-
it 'calls a new RegisterStyle' do
|
30
|
-
expect(StyleCop::RegisterStyle).to receive(:new).with(selector, &block)
|
31
|
-
register_style_structure_for(selector, &block)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context 'no block is given' do
|
36
|
-
it 'raises a no block error' do
|
37
|
-
expect { register_style_structure_for(selector) }.to raise_error { |error|
|
38
|
-
expect(error.message).to eq "No block given."
|
39
|
-
error.should be_a(StandardError)
|
40
|
-
}
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
describe 'assert_style_structure_for' do
|
46
|
-
let(:rules) { double(:rules) }
|
47
|
-
let(:assert_style_class) { double(:assert_style_class) }
|
48
|
-
|
49
|
-
before do
|
50
|
-
allow(StyleCop::RegisterStyle).to receive(:rules) { rules }
|
51
|
-
end
|
52
|
-
|
53
|
-
it "creates an AssertStyle object and calls has_correct_structure on it" do
|
54
|
-
expect(StyleCop::AssertStyle).to receive(:new).with(selector, rules, self) { assert_style_class }
|
55
|
-
expect(assert_style_class).to receive(:has_correct_structure)
|
56
|
-
|
57
|
-
assert_style_structure_for(selector)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|