style_cop 0.0.1 → 0.0.2
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.
- 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
|