hensel 0.0.1
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 +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +20 -0
- data/Gemfile +4 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +337 -0
- data/Rakefile +9 -0
- data/hensel.gemspec +29 -0
- data/lib/hensel/builder/item.rb +43 -0
- data/lib/hensel/builder/node.rb +53 -0
- data/lib/hensel/builder.rb +105 -0
- data/lib/hensel/configuration.rb +37 -0
- data/lib/hensel/filters.rb +47 -0
- data/lib/hensel/helpers/tag_helpers.rb +113 -0
- data/lib/hensel/helpers.rb +9 -0
- data/lib/hensel/version.rb +3 -0
- data/lib/hensel.rb +29 -0
- data/spec/builder_spec.rb +202 -0
- data/spec/configuration_spec.rb +18 -0
- data/spec/helpers_spec.rb +8 -0
- data/spec/item_spec.rb +40 -0
- data/spec/node_spec.rb +75 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/tag_helpers_spec.rb +41 -0
- metadata +188 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
module Hensel
|
2
|
+
class Configuration
|
3
|
+
# Define the accessor as boolean method
|
4
|
+
def self.attr_boolean_accessor(*keys)
|
5
|
+
keys.each do |key|
|
6
|
+
attr_accessor key
|
7
|
+
define_method("#{key}?"){ !!__send__(key) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_boolean_accessor :bootstrap
|
12
|
+
attr_boolean_accessor :escape_html
|
13
|
+
attr_boolean_accessor :indentation
|
14
|
+
attr_boolean_accessor :last_item_link
|
15
|
+
|
16
|
+
attr_accessor :attr_wrapper, :whitespace, :parent_element, :richsnippet
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@bootstrap = false
|
20
|
+
@escape_html = true
|
21
|
+
@indentation = true
|
22
|
+
@last_item_link = false
|
23
|
+
@richsnippet = :microdata # [:microdata, :rdfa, :nil]
|
24
|
+
@attr_wrapper = "'"
|
25
|
+
@whitespace = " "
|
26
|
+
@parent_element = :ul
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
instance_variable_get(:"@#{key}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def []=(key, value)
|
34
|
+
instance_variable_set(:"@#{key}", value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Hensel
|
2
|
+
module Filters
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def register(key, name, block)
|
6
|
+
filters[key][name] = block
|
7
|
+
end
|
8
|
+
|
9
|
+
def filters
|
10
|
+
@filters ||= { filters: {}, richsnippets: {} }
|
11
|
+
end
|
12
|
+
|
13
|
+
register :richsnippets, :microdata, ->(this){
|
14
|
+
return if this.renderer
|
15
|
+
this.renderer = ->(that){
|
16
|
+
node(:li, options.merge(itemtype: "http://data-vocabulary.org/Breadcrumb", itemscope: true)) do
|
17
|
+
if !Hensel.configuration.last_item_link? && item.last?
|
18
|
+
node(:span, itemprop: :title){ item.text }
|
19
|
+
else
|
20
|
+
node(:a, href: item.url, itemprop: :url) do
|
21
|
+
node(:span, itemprop: :title){ item.text }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
register :richsnippets, :rdfa, ->(this){
|
29
|
+
return if this.renderer
|
30
|
+
append_attribute(:"xmlns:v", "http://rdf.data-vocabulary.org/#", this.parent.options)
|
31
|
+
this.renderer = ->(that){
|
32
|
+
node(:li, options.merge(typeof: "v:Breadcrumb")) do
|
33
|
+
if !Hensel.configuration.last_item_link? && item.last?
|
34
|
+
node(:span, property: "v:title"){ item.text }
|
35
|
+
else
|
36
|
+
node(:a, href: item.url, rel: "v:url", property: "v:title"){ item.text }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
register :filters, :bootstrap, ->(this){
|
43
|
+
append_attribute(:class, "breadcrumb", options)
|
44
|
+
append_attribute(:class, "active", items.last.options)
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Hensel
|
2
|
+
module Helpers
|
3
|
+
module TagHelpers
|
4
|
+
ESCAPE_VALUES = {
|
5
|
+
"'" => "'",
|
6
|
+
"&" => "&",
|
7
|
+
"<" => "<",
|
8
|
+
">" => ">",
|
9
|
+
'"' => """
|
10
|
+
}
|
11
|
+
ESCAPE_REGEXP = Regexp.union(*ESCAPE_VALUES.keys)
|
12
|
+
BOOLEAN_ATTRIBUTES = [
|
13
|
+
:autoplay,
|
14
|
+
:autofocus,
|
15
|
+
:formnovalidate,
|
16
|
+
:checked,
|
17
|
+
:disabled,
|
18
|
+
:hidden,
|
19
|
+
:loop,
|
20
|
+
:multiple,
|
21
|
+
:muted,
|
22
|
+
:readonly,
|
23
|
+
:required,
|
24
|
+
:selected,
|
25
|
+
:declare,
|
26
|
+
:defer,
|
27
|
+
:ismap,
|
28
|
+
:itemscope,
|
29
|
+
:noresize,
|
30
|
+
:novalidate
|
31
|
+
]
|
32
|
+
|
33
|
+
def content_tag(name, content = nil, **options, &block)
|
34
|
+
base = tag(name, options)
|
35
|
+
content = instance_eval(&block) if !content && block_given?
|
36
|
+
if indentation?
|
37
|
+
indent = options.fetch(:indent, 0)
|
38
|
+
base << "\n"
|
39
|
+
base << (content.strip.start_with?("<") ? content : (whitespace(indent + 1)) + content)
|
40
|
+
base << "\n"
|
41
|
+
base << "#{whitespace(indent)}</#{name}>"
|
42
|
+
else
|
43
|
+
"#{base}#{content}</#{name}>"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def tag(name, indent: 0, **options)
|
48
|
+
"#{indentation? ? whitespace(indent) : ""}<#{name}#{tag_attributes(options)}>"
|
49
|
+
end
|
50
|
+
|
51
|
+
def append_attribute(attribute, value, hash)
|
52
|
+
if hash[attribute]
|
53
|
+
unless hash[attribute].to_s.split(" ").include?(value)
|
54
|
+
hash[attribute] = (Array(hash[attribute]) << value) * " "
|
55
|
+
end
|
56
|
+
else
|
57
|
+
hash.merge!(attribute => value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def tag_attributes(options)
|
64
|
+
return '' if options.nil?
|
65
|
+
attributes = options.map do |k, v|
|
66
|
+
next if v.nil? || v == false
|
67
|
+
if v.is_a?(Hash)
|
68
|
+
nested_values(k, v)
|
69
|
+
elsif BOOLEAN_ATTRIBUTES.include?(k)
|
70
|
+
%(#{k}=#{attr_wrapper}#{k}#{attr_wrapper})
|
71
|
+
else
|
72
|
+
%(#{k}=#{attr_wrapper}#{h(v)}#{attr_wrapper})
|
73
|
+
end
|
74
|
+
end.compact
|
75
|
+
attributes.empty? ? '' : " #{attributes * ' '}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def nested_values(attribute, hash)
|
79
|
+
hash.map do |k, v|
|
80
|
+
if v.is_a?(Hash)
|
81
|
+
nested_values("#{attribute}-#{k.to_s}", v)
|
82
|
+
else
|
83
|
+
%(#{attribute}-#{k.to_s}=#{attr_wrapper}#{h(v)}#{attr_wrapper}")
|
84
|
+
end
|
85
|
+
end * ' '
|
86
|
+
end
|
87
|
+
|
88
|
+
def h(string)
|
89
|
+
Hensel.configuration.escape_html? ? escape_html(string) : string
|
90
|
+
end
|
91
|
+
|
92
|
+
def escape_html(string)
|
93
|
+
string.to_s.gsub(ESCAPE_REGEXP) { |c| ESCAPE_VALUES[c] }
|
94
|
+
end
|
95
|
+
|
96
|
+
def attr_wrapper
|
97
|
+
@attr_wrapper ||= Hensel.configuration.attr_wrapper
|
98
|
+
end
|
99
|
+
|
100
|
+
def whitespace(indent = nil)
|
101
|
+
if indent.nil?
|
102
|
+
@whitespace ||= Hensel.configuration.whitespace
|
103
|
+
else
|
104
|
+
whitespace * indent
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def indentation?
|
109
|
+
@indentation ||= Hensel.configuration.indentation?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/hensel.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "hensel/version"
|
2
|
+
require "hensel/configuration"
|
3
|
+
require "hensel/builder"
|
4
|
+
require "hensel/helpers"
|
5
|
+
|
6
|
+
module Hensel
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Yields Hensel configuration block
|
10
|
+
# @example
|
11
|
+
# Hensel.configure do |config|
|
12
|
+
# config.attr_wrapper = '"'
|
13
|
+
# end
|
14
|
+
# @see Hensel::Configuration
|
15
|
+
def configure
|
16
|
+
yield configuration
|
17
|
+
configuration
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns Hensel configuration
|
21
|
+
def configuration
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# Resets Hensel configuration
|
26
|
+
def reset_configuration!
|
27
|
+
@configuration = nil
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hensel::Builder do
|
4
|
+
let(:builder){ Hensel::Builder.new }
|
5
|
+
|
6
|
+
describe "#add" do
|
7
|
+
context "with basic usage" do
|
8
|
+
it "returns an instance of Builder::Item" do
|
9
|
+
expect(builder.add("Index", "/")).to be_an_instance_of(Hensel::Builder::Item)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "adds an instance of Builder::Item to items" do
|
13
|
+
builder.add("Boom", "/boom")
|
14
|
+
expect(builder.items.last).to be_an_instance_of(Hensel::Builder::Item)
|
15
|
+
expect(builder.items.last.text).to eq("Boom")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "allows to set options as the attribute of Builder::Item" do
|
19
|
+
builder.add("Foo", "/foo", class: "optional-class", id: "foo-id")
|
20
|
+
expect(builder.render).to have_tag(:li, class: "optional-class", id: "foo-id")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with optional usage" do
|
25
|
+
it "returns an instance of Builder::Item" do
|
26
|
+
expect(builder.add(text: "Index", url: "/")).to be_an_instance_of(Hensel::Builder::Item)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "adds an instance of Builder::Item to items" do
|
30
|
+
builder.add(text: "Boom", url: "/boom")
|
31
|
+
expect(builder.items.last).to be_an_instance_of(Hensel::Builder::Item)
|
32
|
+
expect(builder.items.last.text).to eq("Boom")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "allows to set options as the attribute of Builder::Item" do
|
36
|
+
builder.add(text: "Foo", url: "/foo", class: "optional-class", id: "foo-id")
|
37
|
+
expect(builder.render).to have_tag(:li, class: "optional-class", id: "foo-id")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#remove" do
|
43
|
+
context "with text" do
|
44
|
+
it "removes the item by text from items" do
|
45
|
+
tested = builder.add("Tested", "/tested")
|
46
|
+
builder.add("Sample", "/sample")
|
47
|
+
builder.remove("Tested")
|
48
|
+
expect(builder.items.any?{|x| x.text == tested.text }).to be_false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with block" do
|
53
|
+
it "removes the item by result of block from items" do
|
54
|
+
tested = builder.add("Tested", "/tested")
|
55
|
+
sample = builder.add("Sample", "/sample")
|
56
|
+
builder.remove{|x| x.url == "/sample" }
|
57
|
+
expect(builder.items.any?{|x| x.text == tested.text }).to be_true
|
58
|
+
expect(builder.items.any?{|x| x.text == sample.text }).to be_false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#render" do
|
64
|
+
context "with bootstrap" do
|
65
|
+
before { Hensel.configuration.bootstrap = true }
|
66
|
+
before(:each) do
|
67
|
+
builder.add("Index", "/", class: "dummy-class")
|
68
|
+
builder.add("Dummy", "/dummy", class: "dummy-class")
|
69
|
+
end
|
70
|
+
subject { builder.render }
|
71
|
+
|
72
|
+
it "respects the bootstrap style" do
|
73
|
+
expect(subject).to have_tag(:ul, with: { class: "breadcrumb" }) do
|
74
|
+
with_tag "li:last-child", class: "active"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "respects the microdata rule" do
|
79
|
+
expect(subject).to have_tag(:ul, with: { class: "breadcrumb" }) do
|
80
|
+
with_tag :li, with: { itemtype: "http://data-vocabulary.org/Breadcrumb", itemscope: "itemscope" }
|
81
|
+
with_tag "li > a", with: { href: "/", itemprop: "url" }
|
82
|
+
with_tag "li > a > span", with: { itemprop: "title" }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
after { Hensel.configuration.bootstrap = false }
|
87
|
+
end
|
88
|
+
|
89
|
+
context "with bootstrap and without richsnippet" do
|
90
|
+
before(:all) do
|
91
|
+
Hensel.configuration.richsnippet = nil
|
92
|
+
Hensel.configuration.bootstrap = true
|
93
|
+
end
|
94
|
+
before(:each) do
|
95
|
+
builder.add("Index", "/", class: "dummy-class")
|
96
|
+
builder.add("Dummy", "/dummy", class: "dummy-class")
|
97
|
+
end
|
98
|
+
subject { builder.render }
|
99
|
+
|
100
|
+
it "respects the bootstrap style" do
|
101
|
+
expect(subject).to have_tag(:ul, with: { class: "breadcrumb" }) do
|
102
|
+
with_tag "li:last-child", class: "active"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "does not respect the microdata rule" do
|
107
|
+
expect(subject).not_to have_tag(:li, with: { itemtype: "http://data-vocabulary.org/Breadcrumb", itemscope: "itemscope" })
|
108
|
+
expect(subject).not_to have_tag("li > a", text: "Index", with: { href: "/", itemprop: "url" })
|
109
|
+
expect(subject).not_to have_tag("li > a", text: "Dummy", with: { href: "/dummy", itemprop: "url" })
|
110
|
+
end
|
111
|
+
|
112
|
+
after { Hensel.configuration.bootstrap = false }
|
113
|
+
end
|
114
|
+
|
115
|
+
context "without bootstrap" do
|
116
|
+
before(:each) do
|
117
|
+
builder.add("Index", "/", class: "dummy-class")
|
118
|
+
builder.add("Dummy", "/dummy", class: "dummy-class")
|
119
|
+
end
|
120
|
+
subject { builder.render }
|
121
|
+
it "does not respect the bootstrap style" do
|
122
|
+
expect(subject).not_to have_tag(:ul, with: { class: "breadcrumb" })
|
123
|
+
expect(subject).not_to have_tag("li:last-child", with: { class: "active" })
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "attr_wrapper" do
|
128
|
+
before { Hensel.configuration.attr_wrapper = '"' }
|
129
|
+
before(:each){ builder.add("Index", "/", class: "dummy-class") }
|
130
|
+
subject { builder.render }
|
131
|
+
it { should_not match(/'/) }
|
132
|
+
it { should match(/"/) }
|
133
|
+
after { Hensel.reset_configuration! }
|
134
|
+
end
|
135
|
+
|
136
|
+
context "whitespace" do
|
137
|
+
before do
|
138
|
+
Hensel.configuration.richsnippet = nil
|
139
|
+
Hensel.configuration.whitespace = " "
|
140
|
+
end
|
141
|
+
before(:each){ builder.add("Index", "/", class: "dummy-class") }
|
142
|
+
let(:fixture){
|
143
|
+
<<-FIXTURE.chomp
|
144
|
+
<ul>
|
145
|
+
<li class='dummy-class'>
|
146
|
+
<span>
|
147
|
+
Index
|
148
|
+
</span>
|
149
|
+
</li>
|
150
|
+
</ul>
|
151
|
+
FIXTURE
|
152
|
+
}
|
153
|
+
subject { builder.render }
|
154
|
+
it { should eq(fixture) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "customized breadcrumbs" do
|
159
|
+
context "use customizable renderer instead of default renderer" do
|
160
|
+
before do
|
161
|
+
builder.add("Index", "/")
|
162
|
+
builder.add("Dummy", "/dummy")
|
163
|
+
end
|
164
|
+
subject do
|
165
|
+
builder.render do
|
166
|
+
node(:custom, href: item.url){ item.text }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it "can create the html of breadcrumb correctly" do
|
171
|
+
builder.add("Index", "/")
|
172
|
+
actual_html = builder.render do
|
173
|
+
node(:span){ "text: #{item.text}, url: #{item.url}" }
|
174
|
+
end
|
175
|
+
expect(subject).to have_tag(:ul) do
|
176
|
+
with_tag :custom, href: "/"
|
177
|
+
with_tag :custom, href: "/dummy"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "parent element is not ul but div" do
|
183
|
+
before do
|
184
|
+
Hensel.configuration.parent_element = :div
|
185
|
+
Hensel.configuration.bootstrap = false
|
186
|
+
builder.add("Index", "/")
|
187
|
+
end
|
188
|
+
subject { builder.render }
|
189
|
+
|
190
|
+
it "can set parent element for customizable breadcrumb" do
|
191
|
+
expect(subject).to have_tag(:div, { class: "breadcrumb" }) do
|
192
|
+
with_tag "li", class: "active"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
after do
|
197
|
+
Hensel.configuration.parent_element = :ul
|
198
|
+
Hensel.configuration.bootstrap = false
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hensel::Configuration do
|
4
|
+
describe ".attr_boolean_accessor" do
|
5
|
+
let(:configuration){ Hensel::Configuration.new }
|
6
|
+
it "can define an accessor and boolean methods" do
|
7
|
+
expect(configuration.respond_to?(:sample)).to be_false
|
8
|
+
Hensel::Configuration.attr_boolean_accessor :sample
|
9
|
+
expect(configuration.respond_to?(:sample)).to be_true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#[]" do
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#[]=" do
|
17
|
+
end
|
18
|
+
end
|
data/spec/item_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hensel::Builder::Item do
|
4
|
+
let(:builder){ Hensel::Builder.new }
|
5
|
+
|
6
|
+
context "with escape_html" do
|
7
|
+
before(:all) do
|
8
|
+
Hensel.configuration.escape_html = true
|
9
|
+
Hensel.configuration.indentation = false
|
10
|
+
end
|
11
|
+
before(:each){ builder.add('\'&"<>', '/') }
|
12
|
+
subject { builder.items.last.text }
|
13
|
+
it { should eq(''&"<>') }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "without escape_html" do
|
17
|
+
before(:all) do
|
18
|
+
Hensel.configuration.escape_html = false
|
19
|
+
Hensel.configuration.indentation = false
|
20
|
+
end
|
21
|
+
before(:each){ builder.add('\'&"<>', '/') }
|
22
|
+
subject { builder.items.last.text }
|
23
|
+
it { should eq('\'&"<>') }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#render" do
|
27
|
+
let(:item) { Hensel::Builder::Item.new("index", "/") }
|
28
|
+
subject { item.render }
|
29
|
+
context "basic usage" do
|
30
|
+
it { should have_tag(:li){ with_tag(:a, href: "/") } }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with customized renderer" do
|
34
|
+
it "can be set to renderer" do
|
35
|
+
item.renderer = ->(this){ node(:custom, data: "sample"){ item.text } }
|
36
|
+
should have_tag(:custom, text: "index", data: "sample")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/node_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hensel::Builder::Node do
|
4
|
+
let(:item) { Hensel::Builder::Item.new("index", "/") }
|
5
|
+
before { Hensel.configuration.indentation = false }
|
6
|
+
|
7
|
+
describe "#render" do
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#node" do
|
11
|
+
context "with block" do
|
12
|
+
subject { item.render }
|
13
|
+
it "can build html correctly" do
|
14
|
+
item.renderer = ->(this){ node(:div, class: "hey"){ "sample text" }}
|
15
|
+
should have_tag(:div, text: "sample text", class: "hey")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can build html correctly even when node is nested" do
|
19
|
+
item.renderer = ->(this){ node(:div, class: "hey"){ node(:span){ "nested text" } }}
|
20
|
+
should have_tag(:div, class: "hey") do
|
21
|
+
with_tag(:span, text: "nested text")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should support same hierarchy elements" do
|
26
|
+
item.renderer = ->(this){
|
27
|
+
node(:div) do
|
28
|
+
node(:span){ "one1" }
|
29
|
+
node(:span){ "one2" }
|
30
|
+
end
|
31
|
+
}
|
32
|
+
should have_tag(:div) do
|
33
|
+
with_tag(:span, text: "one1")
|
34
|
+
with_tag(:span, text: "one2")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "without block" do
|
40
|
+
subject { item.render }
|
41
|
+
it "can build html correctly" do
|
42
|
+
item.renderer = ->(this){ node(:div, "sample text", class: "hey") }
|
43
|
+
should have_tag(:div, content: "sample text", class: "hey")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "can build html correctly even when node is nested" do
|
47
|
+
item.renderer = ->(this){ node(:div, class: "hey"){ node(:span, "nested text") }}
|
48
|
+
should have_tag(:div, class: "hey") do
|
49
|
+
with_tag(:span, content: "nested text")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should support same hierarchy elements" do
|
54
|
+
item.renderer = ->(this){
|
55
|
+
node(:div) do
|
56
|
+
node(:span, "one1")
|
57
|
+
node(:span, "one2")
|
58
|
+
end
|
59
|
+
}
|
60
|
+
should have_tag(:div) do
|
61
|
+
with_tag(:span, text: "one1")
|
62
|
+
with_tag(:span, text: "one2")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#item" do
|
69
|
+
subject { item.render }
|
70
|
+
it "can be referred from block" do
|
71
|
+
item.renderer = ->(this){ node(:div, item.text) }
|
72
|
+
should have_tag(:div, text: "index")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hensel::Helpers::TagHelpers do
|
4
|
+
let(:helpers){ Object.new }
|
5
|
+
before { helpers.extend Hensel::Helpers::TagHelpers }
|
6
|
+
|
7
|
+
describe "#contegt_tag" do
|
8
|
+
context "with block" do
|
9
|
+
subject { helpers.content_tag(:div, class: "dummy-class"){ "hello" }}
|
10
|
+
it { should have_tag(:div, content: "hello", class: "dummy-class") }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "without block" do
|
14
|
+
subject { helpers.content_tag(:div, "hello", class: "dummy-class")}
|
15
|
+
it { should have_tag(:div, content: "hello", class: "dummy-class") }
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with indentation" do
|
19
|
+
before { Hensel.configuration.indentation = true }
|
20
|
+
subject { helpers.content_tag(:div, "hello", indent: 2, class: "dummy-class")}
|
21
|
+
it { expect(subject.start_with?(" ")).to be_true }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "without indentation" do
|
25
|
+
before { Hensel.configuration.indentation = false }
|
26
|
+
subject { helpers.content_tag(:div, "hello", indent: 2, class: "dummy-class")}
|
27
|
+
it { expect(subject.start_with?("<div")).to be_true }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#tag" do
|
32
|
+
subject { helpers.tag(:img, class: "sample-image", src: "sample.jpg", alt: "sample image") }
|
33
|
+
it { should have_tag(:img, class: "sample-image", src: "sample.jpg", alt: "sample image") }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#append_attribute" do
|
37
|
+
subject { Hash.new }
|
38
|
+
before { helpers.append_attribute(:key, :value, subject) }
|
39
|
+
it { subject[:key].should eq(:value) }
|
40
|
+
end
|
41
|
+
end
|