dolores-cfml 0.1.0
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.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/cfml.rb +8 -0
- data/lib/cfml/liquid_filters.rb +16 -0
- data/lib/cfml/parser.rb +60 -0
- data/lib/cfml/tag.rb +75 -0
- data/lib/cfml/tags/checkbox.rb +31 -0
- data/lib/cfml/tags/checkboxes.rb +28 -0
- data/lib/cfml/tags/group.rb +14 -0
- data/lib/cfml/tags/hidden.rb +13 -0
- data/lib/cfml/tags/radio.rb +18 -0
- data/lib/cfml/tags/radios.rb +28 -0
- data/lib/cfml/tags/ratings.rb +42 -0
- data/lib/cfml/tags/select.rb +16 -0
- data/lib/cfml/tags/text.rb +10 -0
- data/lib/cfml/tags/textarea.rb +10 -0
- data/lib/cfml/tags/unknown.rb +17 -0
- data/spec/complex_spec.rb +53 -0
- data/spec/fixtures/complex.cfml +23 -0
- data/spec/gold_spec.rb +23 -0
- data/spec/sorta_match.rb +41 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tags/checkbox_spec.rb +81 -0
- data/spec/tags/checkboxes_spec.rb +26 -0
- data/spec/tags/group_spec.rb +35 -0
- data/spec/tags/hidden_spec.rb +15 -0
- data/spec/tags/radio_spec.rb +17 -0
- data/spec/tags/radios_spec.rb +26 -0
- data/spec/tags/select_spec.rb +26 -0
- data/spec/tags/text_spec.rb +36 -0
- data/spec/tags/textarea_spec.rb +18 -0
- data/spec/tags/unknown_spec.rb +19 -0
- metadata +103 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Chris Van Pelt
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
= cfml
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but
|
13
|
+
bump version in a commit by itself I can ignore when I pull)
|
14
|
+
* Send me a pull request. Bonus points for topic branches.
|
15
|
+
|
16
|
+
== Copyright
|
17
|
+
|
18
|
+
Copyright (c) 2009 Chris Van Pelt. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "cfml"
|
8
|
+
gem.summary = "CFML is Crowd Flower Marup Language"
|
9
|
+
gem.description = "CFML let's you easily create form elements and custom interfaces for the CrowdFlower Croudsourcing platform"
|
10
|
+
gem.email = "vanpelt@doloreslabs.com"
|
11
|
+
gem.homepage = "http://github.com/dolores/cfml"
|
12
|
+
gem.authors = ["Chris Van Pelt"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
if File.exist?('VERSION.yml')
|
40
|
+
config = YAML.load(File.read('VERSION.yml'))
|
41
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
42
|
+
else
|
43
|
+
version = ""
|
44
|
+
end
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "cfml #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
51
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/cfml.rb
ADDED
data/lib/cfml/parser.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module CFML
|
2
|
+
class Parser
|
3
|
+
def initialize(content, opts = {})
|
4
|
+
@opts = opts
|
5
|
+
@doc = Nokogiri::XML("<root xmlns:cf='http://crowdflower.com'>#{content}</root>")
|
6
|
+
@cftags = @doc.xpath("//cf:*[not(ancestor::cf:*)]")
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert
|
10
|
+
@cftags.each do |t|
|
11
|
+
t.replace(tag_class(t.name).new(t, @opts).convert)
|
12
|
+
end
|
13
|
+
@doc
|
14
|
+
end
|
15
|
+
|
16
|
+
def golds
|
17
|
+
return @golds if @golds
|
18
|
+
@golds = {}
|
19
|
+
@doc.xpath("//cf:gold|//cf:*[@gold]").map do |g|
|
20
|
+
p = g.name == "gold" ? g.parent : g
|
21
|
+
next unless p
|
22
|
+
ref = tag_class(p.name).new(p, @opts).name
|
23
|
+
gold = (g.attributes["gold"] || g.attributes["src"]).to_s
|
24
|
+
gold = "#{ref}_gold" if gold =~ /^(true|\s*)$/
|
25
|
+
@golds[ref] = gold
|
26
|
+
["strict","regex"].each do |attrs|
|
27
|
+
if a = g.attributes[attrs]
|
28
|
+
@golds["_#{gold}_#{attrs}"] = attrs == "regex" ? [a.to_s,g.attributes["flags"].to_s] : a.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@golds
|
33
|
+
end
|
34
|
+
|
35
|
+
def wrap(content)
|
36
|
+
@opts[:no_wrap] ? content : "<div class=\"cfml #{@opts[:prefix]}\">#{content}</div>"
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_html
|
40
|
+
convert
|
41
|
+
wrap(@doc.at("root").inner_html)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.escape( string )
|
45
|
+
string.to_s.gsub( /&/, "&" ).
|
46
|
+
gsub( /</, "<" ).
|
47
|
+
gsub( />/, ">" ).
|
48
|
+
gsub( /"/, """ )
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
#This takes the name of the tag and converts it to the appropriate task
|
54
|
+
def tag_class(name)
|
55
|
+
CFML::Tags.module_eval(name.gsub(/(^|_)(.)/) { $2.upcase })
|
56
|
+
rescue
|
57
|
+
CFML::Tags::Unknown
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/cfml/tag.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module CFML
|
2
|
+
class Tag
|
3
|
+
def initialize(cfml, opts = {})
|
4
|
+
@cfml = cfml
|
5
|
+
@attrs = @cfml.attributes#Hash[*cfml.attributes.map {|k,v| [k.intern,v]}.flatten]
|
6
|
+
@tag = @cfml.name
|
7
|
+
@opts = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def prefix(name)
|
11
|
+
@opts[:prefix] ? "#{@opts[:prefix]}[#{name}]" : "#{name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
(@attrs["name"] || @attrs["label"]).to_s.downcase.gsub(/\s/,"_").gsub(/\W/,"")
|
16
|
+
end
|
17
|
+
|
18
|
+
def wrapper_classes
|
19
|
+
extras = @attrs["only-if"] ? "only-if:#{@attrs["only-if"]}" : ""
|
20
|
+
"#{@tag} #{extras}".strip
|
21
|
+
end
|
22
|
+
|
23
|
+
def classes
|
24
|
+
@classes ||= [name] << validations << @attrs["class"].to_s.split(/ /)
|
25
|
+
@classes.uniq.reject {|c| c.length == 0 }.join(" ").strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def convert
|
29
|
+
Nokogiri::HTML.fragment(to_html).children.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_html
|
33
|
+
@parsed ||= Liquid::Template.parse(self.class::Template).render(data, [LiquidFilters])
|
34
|
+
wrapper(@parsed)
|
35
|
+
end
|
36
|
+
|
37
|
+
def label(label, tag = "label")
|
38
|
+
required = " <span class=\"required\">(required)</span>" if validations.include?("required")
|
39
|
+
"<#{tag}>#{label}#{required}</#{tag}>" if label
|
40
|
+
end
|
41
|
+
|
42
|
+
def legend(label)
|
43
|
+
label(label, "legend") if label
|
44
|
+
end
|
45
|
+
|
46
|
+
def validations
|
47
|
+
@validations ||= @attrs["validations"].to_s.split(/ /).map {|v| "validates-#{v}"}
|
48
|
+
end
|
49
|
+
|
50
|
+
def instructions
|
51
|
+
@instructions ||= @attrs["instructions"] || (@cfml.at("instructions") ? @cfml.at("instructions").inner_html : nil)
|
52
|
+
@instructions ? "<p class=\"instructions\">#{@instructions}</p>" : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def wrapper(parsed, tag = "div")
|
56
|
+
#Deal with a label only wrapper... kinda jank
|
57
|
+
if tag == "label" && instructions
|
58
|
+
tag = "div"
|
59
|
+
parsed = "<label>#{parsed}</label>"
|
60
|
+
end
|
61
|
+
#Inserting instructions here makes it a non-variable input maybe...
|
62
|
+
"<#{tag} class=\"#{wrapper_classes}\">#{parsed}#{instructions}</#{tag}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
def data
|
66
|
+
(@opts[:data] || {}).merge({
|
67
|
+
"name" => prefix(name),
|
68
|
+
"label" => label(@attrs["label"]),
|
69
|
+
"legend" => legend(@attrs["label"]),
|
70
|
+
"classes" => classes,
|
71
|
+
"value" => @attrs["value"].to_s
|
72
|
+
})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Checkbox < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{default}}
|
6
|
+
<input name="{{name}}" type="checkbox" value="{{value}}" class="{{classes}}"/>
|
7
|
+
HTML
|
8
|
+
|
9
|
+
def wrapper(parsed)
|
10
|
+
super("#{parsed} #{@attrs["label"]}", "label")
|
11
|
+
end
|
12
|
+
|
13
|
+
def prefix(name)
|
14
|
+
super(name) + (grouped? ? "[]" : "")
|
15
|
+
end
|
16
|
+
|
17
|
+
def grouped?
|
18
|
+
@cfml.parent && @cfml.parent.name == "checkboxes"
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
default = ""
|
23
|
+
if @attrs["default"]
|
24
|
+
default = Parser.new("<cf:hidden name=\"#{name}\" value=\"#{@attrs["default"]}\"/>", @opts.merge(:no_wrap => true)).to_html
|
25
|
+
end
|
26
|
+
value = @attrs["value"] || (grouped? ? @attrs["label"] : "true")
|
27
|
+
super.merge({"value" => value.to_s, "default" => default})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Checkboxes < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{legend}}
|
6
|
+
{% for checkbox in checkboxes %}
|
7
|
+
{{checkbox}}
|
8
|
+
{% endfor %}
|
9
|
+
HTML
|
10
|
+
|
11
|
+
def data
|
12
|
+
super.merge({"checkboxes" => checkboxes})
|
13
|
+
end
|
14
|
+
|
15
|
+
def checkboxes
|
16
|
+
@cfml.xpath("cf:checkbox").map do |c|
|
17
|
+
c["name"] ||= name
|
18
|
+
c["validations"] ||= @attrs["validations"].to_s
|
19
|
+
Checkbox.new(c, @opts).to_html
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wrapper(parsed)
|
24
|
+
super(parsed,"fieldset")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Radio < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
<input name="{{name}}" type="radio" value="{{value}}" class="{{classes}}"/>
|
6
|
+
HTML
|
7
|
+
|
8
|
+
def wrapper(parsed)
|
9
|
+
super("#{parsed} #{@attrs["label"]}", "label")
|
10
|
+
end
|
11
|
+
|
12
|
+
def data
|
13
|
+
value = @attrs["value"] || @attrs["label"]
|
14
|
+
super.merge({"value" => value.to_s})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Radios < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{legend}}
|
6
|
+
{% for radio in radios %}
|
7
|
+
{{radio}}
|
8
|
+
{% endfor %}
|
9
|
+
HTML
|
10
|
+
|
11
|
+
def data
|
12
|
+
super.merge({"radios" => radios})
|
13
|
+
end
|
14
|
+
|
15
|
+
def radios
|
16
|
+
@cfml.xpath("cf:radio").map do |c|
|
17
|
+
c["name"] ||= name
|
18
|
+
c["validations"] ||= @attrs["validations"].to_s
|
19
|
+
Radio.new(c, @opts).to_html
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wrapper(parsed)
|
24
|
+
super(parsed, "fieldset")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Ratings < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{label}}
|
6
|
+
<table>
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<th></th>
|
10
|
+
{% for rating in ratings limit:10 %}
|
11
|
+
<th>{{forloop.index}}</th>
|
12
|
+
{% endfor %}
|
13
|
+
<th></th>
|
14
|
+
</tr>
|
15
|
+
</thead>
|
16
|
+
<tbody>
|
17
|
+
<tr>
|
18
|
+
<td>{{from}}</td>
|
19
|
+
{% for rating in ratings limit:10 %}
|
20
|
+
<td>{{rating}}</td>
|
21
|
+
{% endfor %}
|
22
|
+
<td>{{to}}</td>
|
23
|
+
</tr>
|
24
|
+
</tbody>
|
25
|
+
</table>
|
26
|
+
HTML
|
27
|
+
|
28
|
+
def data
|
29
|
+
super.merge({"ratings" => ratings, "from" => @attrs["from"].to_s, "to" => @attrs["to"].to_s})
|
30
|
+
end
|
31
|
+
|
32
|
+
def ratings
|
33
|
+
@cfml.xpath("cf:rating").map do |c|
|
34
|
+
c["name"] ||= prefix
|
35
|
+
c["validations"] ||= @attrs["validations"]
|
36
|
+
c["value"] ||= @attrs["label"]
|
37
|
+
Radio.new(c).convert
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Select < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{label}}
|
6
|
+
<select name="{{name}}" class="{{classes}}">{% for option in options %}
|
7
|
+
{{option}}{% endfor %}
|
8
|
+
</select>
|
9
|
+
HTML
|
10
|
+
|
11
|
+
def data
|
12
|
+
super.merge({"options" => @cfml.xpath("option").map {|c| c.to_xhtml}})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Complex" do
|
4
|
+
it "parses" do
|
5
|
+
@p = Parser.new(File.read(File.dirname(__FILE__) + "/fixtures/complex.cfml"), {:prefix => "u12345"})
|
6
|
+
@p.to_html.should sorta_match(<<-HTML)
|
7
|
+
<div class="cfml u12345">
|
8
|
+
<h1>This is my name</h1>
|
9
|
+
<p>This is a description about me</p>
|
10
|
+
<div class="select">
|
11
|
+
<label>Awesome</label>
|
12
|
+
<select name="u12345[awesome]" class="awesome validates-required">
|
13
|
+
<option value="">Choose an option</option>
|
14
|
+
<option>Awesome</option>
|
15
|
+
<option>Cool</option>
|
16
|
+
<option>Rad</option>
|
17
|
+
<option>Neat</option>
|
18
|
+
</select>
|
19
|
+
<p class="instructions">Just be cool about it</p>
|
20
|
+
</div>
|
21
|
+
<div class="text">
|
22
|
+
<label>Opinion</label>
|
23
|
+
<input name="u12345[opinion]" class="opinion validates-minLength:30 large" type="text" value="">
|
24
|
+
</div>
|
25
|
+
<div class="text">
|
26
|
+
<label>Email</label>
|
27
|
+
<input name="u12345[email]" class="email validates-required validates-email" type="text" value="">
|
28
|
+
</div>
|
29
|
+
<div class="rad">
|
30
|
+
<label class="checkbox">
|
31
|
+
<input name="u12345[you_sure]" class="you_sure" type="checkbox" value="yes"> You sure?
|
32
|
+
</label>
|
33
|
+
</div>
|
34
|
+
<div class="group only-if:awesome">
|
35
|
+
|
36
|
+
<fieldset class="checkboxes">
|
37
|
+
<legend>Cool checkboxes</legend>
|
38
|
+
|
39
|
+
<label class="checkbox">
|
40
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Wanna do it?">
|
41
|
+
Wanna do it?</label>
|
42
|
+
|
43
|
+
<label class="checkbox">
|
44
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Sure?">
|
45
|
+
Sure?</label>
|
46
|
+
|
47
|
+
</fieldset>
|
48
|
+
<input type="hidden" name="u12345[secret]" value="cool" class="secret" />
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
HTML
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<h1>This is my name</h1>
|
2
|
+
<p>This is a description about me</p>
|
3
|
+
<cf:select label="Awesome" validations="required" instructions="Just be cool about it">
|
4
|
+
<cf:gold src="awesome_gold" regex="\\.asdf\\." flags="i" strict="true" />
|
5
|
+
<option value="">Choose an option</option>
|
6
|
+
<option>Awesome</option>
|
7
|
+
<option>Cool</option>
|
8
|
+
<option>Rad</option>
|
9
|
+
<option>Neat</option>
|
10
|
+
</cf:select>
|
11
|
+
<cf:text label="Opinion" validations="minLength:30" class="large" />
|
12
|
+
<cf:text label="Email" validations="required email" />
|
13
|
+
<div class="rad">
|
14
|
+
<cf:checkbox label="You sure?" value="yes" gold="true" />
|
15
|
+
</div>
|
16
|
+
<cf:group only-if="awesome">
|
17
|
+
<cf:checkboxes label="Cool checkboxes" name="rad">
|
18
|
+
<cf:instructions>This <i>is</i> great</cf:instructions>
|
19
|
+
<cf:checkbox label="Wanna do it?"/>
|
20
|
+
<cf:checkbox label="Sure?"/>
|
21
|
+
</cf:checkboxes>
|
22
|
+
<cf:hidden name="secret" value="cool"/>
|
23
|
+
</cf:group>
|
data/spec/gold_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Gold" do
|
4
|
+
it "should give me da gold" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:checkboxes label="Checkboxes">
|
7
|
+
<cf:gold src="awesome_gold" regex="\\.asdf\\." flags="i" strict="true" />
|
8
|
+
<cf:checkbox label="Wanna do it?" />
|
9
|
+
<cf:checkbox label="Sure?" />
|
10
|
+
</cf:checkboxes>
|
11
|
+
<cf:text name="cool" gold="true" />
|
12
|
+
<cf:textarea name="sweet" gold="sweetness" />
|
13
|
+
HTML
|
14
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
15
|
+
@p.golds.should == {
|
16
|
+
"_awesome_gold_strict" => "true",
|
17
|
+
"sweet" => "sweetness",
|
18
|
+
"checkboxes" => "awesome_gold",
|
19
|
+
"_awesome_gold_regex" => ["\\.asdf\\.", "i"],
|
20
|
+
"cool" => "cool_gold"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
data/spec/sorta_match.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class SortaMatch
|
2
|
+
def initialize(dom1,dom2)
|
3
|
+
@nodes1 = Nokogiri::HTML(dom1).xpath("//*").to_a
|
4
|
+
@nodes2 = Nokogiri::HTML(dom2).xpath("//*").to_a
|
5
|
+
@children = []
|
6
|
+
@nodes1.each_with_index { |d,i| @children << [d,@nodes2[i]] }
|
7
|
+
end
|
8
|
+
|
9
|
+
def missed
|
10
|
+
[present(@nodes1[@i]), present(@nodes2[@i])]
|
11
|
+
end
|
12
|
+
|
13
|
+
def present(node)
|
14
|
+
node ? "<#{node.name}#{node.attributes.map {|k,v| " #{k}=\"#{v}\""}}>#{node.children.first if first_text?(node)}" : @why
|
15
|
+
end
|
16
|
+
|
17
|
+
def first_text?(node)
|
18
|
+
node.children.first && node.children.first.text?
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?
|
22
|
+
@i = 0
|
23
|
+
@children.detect do |a,b|
|
24
|
+
if a.nil? || b.nil?
|
25
|
+
@why = "missing"
|
26
|
+
elsif !a.text?
|
27
|
+
if a.name != b.name
|
28
|
+
@why = "tag"
|
29
|
+
elsif a.attributes.map {|k,v| [k.to_s,v.to_s]} != b.attributes.map {|k,v| [k.to_s,v.to_s]}
|
30
|
+
@why = "attributes"
|
31
|
+
elsif first_text?(b) && a.children.first.to_s.strip != b.children.first.to_s.strip
|
32
|
+
@why = "text"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@why = nil
|
36
|
+
end
|
37
|
+
@i += 1 unless @why
|
38
|
+
@why
|
39
|
+
end.nil?
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'cfml'
|
4
|
+
require 'spec'
|
5
|
+
require 'sorta_match'
|
6
|
+
#require 'spec/autorun'
|
7
|
+
include CFML
|
8
|
+
|
9
|
+
Spec::Runner.configure do |config|
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
Spec::Matchers.define :sorta_match do |expected|
|
14
|
+
match do |actual|
|
15
|
+
@sm = SortaMatch.new(expected, actual)
|
16
|
+
@sm.matches?
|
17
|
+
end
|
18
|
+
|
19
|
+
failure_message_for_should do |actual|
|
20
|
+
"Expected DOM's to sorta match but they had these differences:\n #{@sm.missed.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def convert(html)
|
25
|
+
Nokogiri::HTML.fragment(html)
|
26
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Radio" do
|
4
|
+
it "should render basic checkbox" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:checkbox label="Check"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<label class="checkbox">
|
12
|
+
<input type="checkbox" value="true" name="u12345[check]" class="check"/> Check
|
13
|
+
</label>
|
14
|
+
</div>
|
15
|
+
HTML
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should render checkbox custom value" do
|
19
|
+
cfml = <<-HTML
|
20
|
+
<cf:checkbox label="Check" value="yes"/>
|
21
|
+
HTML
|
22
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
23
|
+
@p.to_html.should sorta_match(<<-HTML)
|
24
|
+
<div class="cfml u12345">
|
25
|
+
<label class="checkbox">
|
26
|
+
<input type="checkbox" value="yes" name="u12345[check]" class="check"/> Check
|
27
|
+
</label>
|
28
|
+
</div>
|
29
|
+
HTML
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should render default checkbox" do
|
33
|
+
cfml = <<-HTML
|
34
|
+
<cf:checkbox label="Check" default="false"/>
|
35
|
+
HTML
|
36
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
37
|
+
@p.to_html.should sorta_match(<<-HTML)
|
38
|
+
<div class="cfml u12345">
|
39
|
+
<label class="checkbox">
|
40
|
+
<input type="hidden" value="false" name="u12345[check]" class="check"/>
|
41
|
+
<input type="checkbox" value="true" name="u12345[check]" class="check"/> Check
|
42
|
+
</label>
|
43
|
+
</div>
|
44
|
+
HTML
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should render checkbox with instructions attribute" do
|
48
|
+
cfml = <<-HTML
|
49
|
+
<cf:checkbox label="Rad" instructions="Please be careful when you are selecting these."/>
|
50
|
+
HTML
|
51
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
52
|
+
@p.to_html.should sorta_match(<<-HTML)
|
53
|
+
<div class="cfml u12345">
|
54
|
+
<div class="checkbox">
|
55
|
+
<label>
|
56
|
+
<input type="checkbox" value="true" name="u12345[rad]" class="rad"/> Rad
|
57
|
+
</label>
|
58
|
+
<p class="instructions">Please be careful when you are selecting these.</p>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
HTML
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should render checkbox nested" do
|
65
|
+
cfml = <<-HTML
|
66
|
+
<div class="rando">
|
67
|
+
<cf:checkbox label="Rad"/>
|
68
|
+
</div>
|
69
|
+
HTML
|
70
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
71
|
+
@p.to_html.should sorta_match(<<-HTML)
|
72
|
+
<div class="cfml u12345">
|
73
|
+
<div class="rando">
|
74
|
+
<label class="checkbox">
|
75
|
+
<input type="checkbox" value="true" name="u12345[rad]" class="rad"/> Rad
|
76
|
+
</label>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
HTML
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Checkboxes" do
|
4
|
+
it "should render basic checkboxes" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:checkboxes label="Cool checkboxes">
|
7
|
+
<cf:checkbox label="Wanna do it?"/>
|
8
|
+
<cf:checkbox label="Sure?">
|
9
|
+
</cf:checkboxes>
|
10
|
+
HTML
|
11
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
12
|
+
@p.to_html.should sorta_match(<<-HTML)
|
13
|
+
<div class="cfml u12345">
|
14
|
+
<fieldset class="checkboxes">
|
15
|
+
<legend>Cool checkboxes</legend>
|
16
|
+
<label class="checkbox">
|
17
|
+
<input type="checkbox" value="Wanna do it?" name="u12345[cool_checkboxes][]" class="cool_checkboxes"/> Wanna do it?
|
18
|
+
</label>
|
19
|
+
<label class="checkbox">
|
20
|
+
<input type="checkbox" value="Sure?" name="u12345[cool_checkboxes][]" class="cool_checkboxes"/> Sure?
|
21
|
+
</label>
|
22
|
+
</fieldset>
|
23
|
+
</div>
|
24
|
+
HTML
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Select" do
|
4
|
+
it "should render group" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:group only-if="awesome">
|
7
|
+
<cf:checkboxes label="Cool checkboxes" name="rad">
|
8
|
+
<cf:checkbox label="Wanna do it?"/>
|
9
|
+
<cf:checkbox label="Sure?"/>
|
10
|
+
</cf:checkboxes>
|
11
|
+
<cf:text label="Something"/>
|
12
|
+
</cf:group>
|
13
|
+
HTML
|
14
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
15
|
+
@p.to_html.should sorta_match(<<-HTML)
|
16
|
+
<div class="cfml u12345">
|
17
|
+
<div class="group only-if:awesome">
|
18
|
+
<fieldset class="checkboxes">
|
19
|
+
<legend>Cool checkboxes</legend>
|
20
|
+
<label class="checkbox">
|
21
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Wanna do it?" /> Wanna do it?
|
22
|
+
</label>
|
23
|
+
<label class="checkbox">
|
24
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Sure?" /> Sure?
|
25
|
+
</label>
|
26
|
+
</fieldset>
|
27
|
+
<div class="text">
|
28
|
+
<label>Something</label>
|
29
|
+
<input name="u12345[something]" class="something" type="text" value="" />
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
HTML
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Hidden" do
|
4
|
+
it "should render basic hidden field" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:hidden name="secret" value="shit"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<input type="hidden" value="shit" name="u12345[secret]" class="secret"/>
|
12
|
+
</div>
|
13
|
+
HTML
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Radio" do
|
4
|
+
it "should render basic radio" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:radio label="Rad"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<label class="radio">
|
12
|
+
<input type="radio" value="Rad" name="u12345[rad]" class="rad"/> Rad
|
13
|
+
</label>
|
14
|
+
</div>
|
15
|
+
HTML
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Radios" do
|
4
|
+
it "should render basic radios" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:radios label="Cool radios">
|
7
|
+
<cf:radio label="Wanna do it?"/>
|
8
|
+
<cf:radio label="Sure!">
|
9
|
+
</cf:radios>
|
10
|
+
HTML
|
11
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
12
|
+
@p.to_html.should sorta_match(<<-HTML)
|
13
|
+
<div class="cfml u12345">
|
14
|
+
<fieldset class="radios">
|
15
|
+
<legend>Cool radios</legend>
|
16
|
+
<label class="radio">
|
17
|
+
<input type="radio" value="Wanna do it?" name="u12345[cool_radios]" class="cool_radios"/> Wanna do it?
|
18
|
+
</label>
|
19
|
+
<label class="radio">
|
20
|
+
<input type="radio" value="Sure!" name="u12345[cool_radios]" class="cool_radios"/> Sure!
|
21
|
+
</label>
|
22
|
+
</fieldset>
|
23
|
+
</div>
|
24
|
+
HTML
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Select" do
|
4
|
+
it "should render basic select" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:select label="Basic">
|
7
|
+
<option>One</option>
|
8
|
+
<option>Two</option>
|
9
|
+
<option>Three</option>
|
10
|
+
</cf:select>
|
11
|
+
HTML
|
12
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
13
|
+
@p.to_html.should sorta_match(<<-HTML)
|
14
|
+
<div class="cfml u12345">
|
15
|
+
<div class="select">
|
16
|
+
<label>Basic</label>
|
17
|
+
<select name="u12345[basic]" class="basic">
|
18
|
+
<option>One</option>
|
19
|
+
<option>Two</option>
|
20
|
+
<option>Three</option>
|
21
|
+
</select>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
HTML
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Text" do
|
4
|
+
it "should render basic text" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:text label="Coolio"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<div class="text">
|
12
|
+
<label>Coolio</label>
|
13
|
+
<input type="text" value="" name="u12345[coolio]" class="coolio"/>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
HTML
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should render text with classes and instructions tag" do
|
20
|
+
cfml = <<-HTML
|
21
|
+
<cf:text label="Coolio" class="small">
|
22
|
+
<instructions>Just be <b>cool</b> bro!</instructions>
|
23
|
+
</cf:text>
|
24
|
+
HTML
|
25
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
26
|
+
@p.to_html.should sorta_match(<<-HTML)
|
27
|
+
<div class="cfml u12345">
|
28
|
+
<div class="text">
|
29
|
+
<label>Coolio</label>
|
30
|
+
<input type="text" class="coolio small" value="" name="u12345[coolio]" class="coolio"/>
|
31
|
+
<p class="instructions">Just be <b>cool</b> bro!</p>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
HTML
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Textarea" do
|
4
|
+
it "should render basic text" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:textarea label="Sweet dude man!" value="4 sure"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<div class="textarea">
|
12
|
+
<label>Sweet dude man!</label>
|
13
|
+
<textarea name="u12345[sweet_dude_man]" class="sweet_dude_man">4 sure</textarea>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
HTML
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Unknown" do
|
4
|
+
it "should render unknown field" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:crazy name="secret">
|
7
|
+
Something
|
8
|
+
</cf:crazy>
|
9
|
+
HTML
|
10
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
11
|
+
@p.to_html.should sorta_match(<<-HTML)
|
12
|
+
<div class="cfml u12345">
|
13
|
+
<div class="crazy unknown">
|
14
|
+
Something
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
HTML
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dolores-cfml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Van Pelt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-13 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: CFML let's you easily create form elements and custom interfaces for the CrowdFlower Croudsourcing platform
|
17
|
+
email: vanpelt@doloreslabs.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .document
|
27
|
+
- .gitignore
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- lib/cfml.rb
|
33
|
+
- lib/cfml/liquid_filters.rb
|
34
|
+
- lib/cfml/parser.rb
|
35
|
+
- lib/cfml/tag.rb
|
36
|
+
- lib/cfml/tags/checkbox.rb
|
37
|
+
- lib/cfml/tags/checkboxes.rb
|
38
|
+
- lib/cfml/tags/group.rb
|
39
|
+
- lib/cfml/tags/hidden.rb
|
40
|
+
- lib/cfml/tags/radio.rb
|
41
|
+
- lib/cfml/tags/radios.rb
|
42
|
+
- lib/cfml/tags/ratings.rb
|
43
|
+
- lib/cfml/tags/select.rb
|
44
|
+
- lib/cfml/tags/text.rb
|
45
|
+
- lib/cfml/tags/textarea.rb
|
46
|
+
- lib/cfml/tags/unknown.rb
|
47
|
+
- spec/complex_spec.rb
|
48
|
+
- spec/fixtures/complex.cfml
|
49
|
+
- spec/gold_spec.rb
|
50
|
+
- spec/sorta_match.rb
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
- spec/tags/checkbox_spec.rb
|
53
|
+
- spec/tags/checkboxes_spec.rb
|
54
|
+
- spec/tags/group_spec.rb
|
55
|
+
- spec/tags/hidden_spec.rb
|
56
|
+
- spec/tags/radio_spec.rb
|
57
|
+
- spec/tags/radios_spec.rb
|
58
|
+
- spec/tags/select_spec.rb
|
59
|
+
- spec/tags/text_spec.rb
|
60
|
+
- spec/tags/textarea_spec.rb
|
61
|
+
- spec/tags/unknown_spec.rb
|
62
|
+
has_rdoc: false
|
63
|
+
homepage: http://github.com/dolores/cfml
|
64
|
+
licenses:
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options:
|
67
|
+
- --charset=UTF-8
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
version:
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.3.5
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: CFML is Crowd Flower Marup Language
|
89
|
+
test_files:
|
90
|
+
- spec/complex_spec.rb
|
91
|
+
- spec/gold_spec.rb
|
92
|
+
- spec/sorta_match.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
- spec/tags/checkbox_spec.rb
|
95
|
+
- spec/tags/checkboxes_spec.rb
|
96
|
+
- spec/tags/group_spec.rb
|
97
|
+
- spec/tags/hidden_spec.rb
|
98
|
+
- spec/tags/radio_spec.rb
|
99
|
+
- spec/tags/radios_spec.rb
|
100
|
+
- spec/tags/select_spec.rb
|
101
|
+
- spec/tags/text_spec.rb
|
102
|
+
- spec/tags/textarea_spec.rb
|
103
|
+
- spec/tags/unknown_spec.rb
|