domino 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -2
- data/README.md +94 -0
- data/Rakefile +2 -1
- data/domino.gemspec +1 -1
- data/lib/domino.rb +10 -60
- data/lib/domino/attribute.rb +50 -0
- data/lib/domino/form.rb +82 -0
- data/lib/domino/form/boolean_field.rb +13 -0
- data/lib/domino/form/field.rb +44 -0
- data/lib/domino/form/select_field.rb +37 -0
- data/test/domino_form_test.rb +206 -0
- data/test/domino_test.rb +2 -55
- data/test/test_application.rb +142 -0
- data/test/test_helper.rb +8 -0
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3059a81fc3233b83f748c35d895915d477778062
|
4
|
+
data.tar.gz: 5ba1c3b35b0b12919712ad789dd3a50d8b4d2fea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 629bc9d4a700d9161ba1cc46953556fb42ad6d447a5d90ee64a5d410eb416a6095ef57c9a5bf94dd96ba664d335a4e095ee4e245fec6c69298aa4211672d0e84
|
7
|
+
data.tar.gz: 9edb66f4135a320f85836e9e2c04af3f8eee8245cf8ea6c040e96da86fdf1ac30139c27c61be933bdfe63b83467b15a160ddc538a189c133207cbe1e2cfbb104
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -74,6 +74,100 @@ Dom::Post.find_by_title('First Post').delete
|
|
74
74
|
assert_nil Dom::Post.find_by_title('First Post')
|
75
75
|
```
|
76
76
|
|
77
|
+
## Domino::Form
|
78
|
+
|
79
|
+
Domino makes it easy to model your forms for testing with `Domino::Form`.
|
80
|
+
To create a basic form, simply inherit from `Domino::Form` and define a
|
81
|
+
selector, a key (optional), and a set of fields.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
module Dom
|
85
|
+
class PersonForm < Domino::Form
|
86
|
+
selector 'form.person'
|
87
|
+
|
88
|
+
# For forms with names like `person[age]`, no need to define the
|
89
|
+
# locator on each field. Define a key to automatically generate
|
90
|
+
# locators based on the field name.
|
91
|
+
key 'person'
|
92
|
+
|
93
|
+
# Define a custom selector to click to submit the form
|
94
|
+
submit_with "input[type='submit']" # this is the default
|
95
|
+
|
96
|
+
# locate field by label
|
97
|
+
field :first_name, 'First Name'
|
98
|
+
|
99
|
+
# locate field by automatically generated name (uses key, person[last_name])
|
100
|
+
field :last_name
|
101
|
+
|
102
|
+
# locate field by fully qualified name
|
103
|
+
field :biography, 'person[bio]'
|
104
|
+
|
105
|
+
# locate select field by label, acts as select
|
106
|
+
# callback mapper operates on selected option nodes
|
107
|
+
field :favorite_color, 'Favorite Color', as: :select, &:text
|
108
|
+
|
109
|
+
# automatically handles select[multiple]
|
110
|
+
# callback mapper operates on selected option nodes: &:value by default
|
111
|
+
field :allergies, as: :select
|
112
|
+
|
113
|
+
# locate by id, convert value via callback
|
114
|
+
field :age, 'person_age', &:to_i
|
115
|
+
|
116
|
+
# use a custom field type for unusual or composite fields
|
117
|
+
field :vehicles, '.input.vehicles', as: CheckBoxesField
|
118
|
+
|
119
|
+
# locate a field with a name that doesn't use the key
|
120
|
+
field :is_human, 'is_human', as: :boolean
|
121
|
+
|
122
|
+
# still supports attributes for non-input nodes
|
123
|
+
attribute :action, "&[action]"
|
124
|
+
attribute :submit_method, "&[method]"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
In the above example, you can define a field to get a reader and writer
|
130
|
+
method for the field. A form will also provide a mass-assignment writer
|
131
|
+
and a save method to submit the form.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
person = Dom::PersonForm.find!
|
135
|
+
person.age #=> 25
|
136
|
+
person.vehicles #=> ["Car", "Bike"]
|
137
|
+
person.is_human #=> true
|
138
|
+
person.favorite_color #=> Blue
|
139
|
+
|
140
|
+
person.age = 35
|
141
|
+
person.age #=> 35
|
142
|
+
|
143
|
+
person.set(vehicles: ["Car", "Van"], first_name: "Jessica", last_name: "Jones")
|
144
|
+
person.attributes #=> { first_name: "Jessica", last_name: "Jones", biography: "", favorite_color: "Blue", age: 35, vehicles: ["Car", "Van"], is_human: true }
|
145
|
+
```
|
146
|
+
|
147
|
+
`Domino::Form` provides basic field types for text inputs and textareas,
|
148
|
+
single-selects, and boolean fields. You can create custom field types
|
149
|
+
for more complex form inputs by subclassing `Domino::Form::Field` and
|
150
|
+
overriding the `read` and `write` methods. For example, if you have a
|
151
|
+
collection of check boxes, this might suit your needs:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
class CheckBoxesField < Domino::Form::Field
|
155
|
+
def read(node)
|
156
|
+
node.find(locator).all('input[type=checkbox]').select(&:checked?).map(&:value)
|
157
|
+
end
|
158
|
+
|
159
|
+
def write(node, value)
|
160
|
+
value = Array(value)
|
161
|
+
node.find(locator).all('input[type=checkbox]').each do |box|
|
162
|
+
box.set(value.include?(box.value))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
Provide your custom class using the `:as` option when defining your field,
|
169
|
+
as shown in the example above.
|
170
|
+
|
77
171
|
## Integration with capybara
|
78
172
|
|
79
173
|
Domino uses capybara internally to search html for nodes and
|
data/Rakefile
CHANGED
@@ -2,7 +2,8 @@ require 'bundler/gem_tasks'
|
|
2
2
|
|
3
3
|
desc "Run the tests"
|
4
4
|
task :test do
|
5
|
-
require File.join(File.dirname(__FILE__), 'test', '
|
5
|
+
require File.join(File.dirname(__FILE__), 'test', 'test_helper.rb')
|
6
|
+
Dir[File.join(File.dirname(__FILE__), "test", "**", "*.rb")].each { |f| require f }
|
6
7
|
end
|
7
8
|
|
8
9
|
task :default => :test
|
data/domino.gemspec
CHANGED
data/lib/domino.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'capybara/dsl'
|
2
|
-
require 'set'
|
3
2
|
# To create a basic Domino class, inherit from Domino and
|
4
3
|
# define a selector and attributes:
|
5
4
|
#
|
@@ -44,6 +43,9 @@ class Domino
|
|
44
43
|
include Capybara::DSL
|
45
44
|
extend Capybara::DSL
|
46
45
|
|
46
|
+
require 'domino/attribute'
|
47
|
+
require 'domino/form'
|
48
|
+
|
47
49
|
# Namespaced Domino::Error
|
48
50
|
class Error < StandardError; end
|
49
51
|
|
@@ -136,14 +138,13 @@ class Domino
|
|
136
138
|
|
137
139
|
attribute_definitions[attribute] = Attribute.new(attribute, selector, &callback)
|
138
140
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
}
|
141
|
+
define_method :"#{attribute}" do
|
142
|
+
self.class.attribute_definitions[attribute].value(node)
|
143
|
+
end
|
144
|
+
|
145
|
+
define_singleton_method :"find_by_#{attribute}" do |value|
|
146
|
+
find_by_attribute(attribute, value)
|
147
|
+
end
|
147
148
|
end
|
148
149
|
|
149
150
|
private
|
@@ -194,55 +195,4 @@ class Domino
|
|
194
195
|
def initialize(node)
|
195
196
|
@node = node
|
196
197
|
end
|
197
|
-
|
198
|
-
class Attribute
|
199
|
-
attr_reader :name, :selector, :callback
|
200
|
-
|
201
|
-
def initialize(name, selector = nil, &callback)
|
202
|
-
@callback = callback
|
203
|
-
@name = name
|
204
|
-
@selector = selector || %(.#{name.to_s.tr('_', '-')})
|
205
|
-
end
|
206
|
-
|
207
|
-
def value(node)
|
208
|
-
val = value_before_typecast(node)
|
209
|
-
|
210
|
-
if val && callback.is_a?(Proc)
|
211
|
-
callback.call(val)
|
212
|
-
else
|
213
|
-
val
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Get the text of the first dom element matching a selector
|
218
|
-
#
|
219
|
-
# Dom::Post.all.first.attribute('.title')
|
220
|
-
def value_before_typecast(node)
|
221
|
-
if combinator?
|
222
|
-
node[node_attribute_key] || node.matches_css?(combinator)
|
223
|
-
else
|
224
|
-
node.find(selector).text
|
225
|
-
end
|
226
|
-
rescue Capybara::ElementNotFound
|
227
|
-
nil
|
228
|
-
end
|
229
|
-
|
230
|
-
def match_value?(node, value)
|
231
|
-
value === value(node)
|
232
|
-
end
|
233
|
-
|
234
|
-
private
|
235
|
-
|
236
|
-
def combinator?
|
237
|
-
selector[0] == "&".freeze
|
238
|
-
end
|
239
|
-
|
240
|
-
def combinator
|
241
|
-
@combinator ||= selector.sub(/&/, "") if combinator?
|
242
|
-
end
|
243
|
-
|
244
|
-
def node_attribute_key
|
245
|
-
@node_attribute_key ||= combinator.match(/(?<=\[).+?(?=\])/) { |m| m[0] }
|
246
|
-
end
|
247
|
-
end
|
248
198
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Domino::Attribute
|
2
|
+
attr_reader :name, :selector, :callback
|
3
|
+
|
4
|
+
def initialize(name, selector = nil, &callback)
|
5
|
+
@callback = callback
|
6
|
+
@name = name
|
7
|
+
@selector = selector || %(.#{name.to_s.tr('_', '-')})
|
8
|
+
end
|
9
|
+
|
10
|
+
def value(node)
|
11
|
+
val = value_before_typecast(node)
|
12
|
+
|
13
|
+
if val && callback.is_a?(Proc)
|
14
|
+
callback.call(val)
|
15
|
+
else
|
16
|
+
val
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the text of the first dom element matching a selector
|
21
|
+
#
|
22
|
+
# Dom::Post.all.first.attribute('.title')
|
23
|
+
def value_before_typecast(node)
|
24
|
+
if combinator?
|
25
|
+
node[node_attribute_key] || node.matches_css?(combinator)
|
26
|
+
else
|
27
|
+
node.find(selector).text
|
28
|
+
end
|
29
|
+
rescue Capybara::ElementNotFound
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def match_value?(node, value)
|
34
|
+
value === value(node)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def combinator?
|
40
|
+
selector[0] == '&'.freeze
|
41
|
+
end
|
42
|
+
|
43
|
+
def combinator
|
44
|
+
@combinator ||= selector.sub(/&/, '') if combinator?
|
45
|
+
end
|
46
|
+
|
47
|
+
def node_attribute_key
|
48
|
+
@node_attribute_key ||= combinator.match(/(?<=\[).+?(?=\])/) { |m| m[0] }
|
49
|
+
end
|
50
|
+
end
|
data/lib/domino/form.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
class Domino::Form < Domino
|
2
|
+
require 'domino/form/field'
|
3
|
+
require 'domino/form/select_field'
|
4
|
+
require 'domino/form/boolean_field'
|
5
|
+
|
6
|
+
FIELD_TYPES = {
|
7
|
+
select: SelectField,
|
8
|
+
boolean: BooleanField
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def self.key(k)
|
12
|
+
@key = k
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.fields
|
16
|
+
field_definitions.keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.field_definitions
|
20
|
+
@field_definitions ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.submit_with(submitter)
|
24
|
+
@submitter = submitter
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.submitter
|
28
|
+
@submitter ||= "input[type='submit']"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.field(*args, &callback)
|
32
|
+
options = args.last.is_a?(::Hash) ? args.pop : {}
|
33
|
+
attribute, locator = *args
|
34
|
+
|
35
|
+
locator ||= !@key.to_s.empty? ? "#{@key}[#{attribute}]" : attribute
|
36
|
+
|
37
|
+
field_type = options.delete(:as)
|
38
|
+
field_class = field_type.is_a?(Class) && field_type.ancestors.include?(Field) ? field_type : FIELD_TYPES[field_type] || Field
|
39
|
+
|
40
|
+
field_definitions[attribute] = field_class.new(attribute, locator, options, &callback)
|
41
|
+
|
42
|
+
define_method :"#{attribute}" do
|
43
|
+
self.class.field_definitions[attribute].value(node)
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method :"#{attribute}=" do |value|
|
47
|
+
self.class.field_definitions[attribute].write(node, value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.create(attributes = {})
|
52
|
+
find!.create(attributes)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.update(attributes = {})
|
56
|
+
find!.update(attributes)
|
57
|
+
end
|
58
|
+
|
59
|
+
def create(attributes = {})
|
60
|
+
set(attributes)
|
61
|
+
save
|
62
|
+
end
|
63
|
+
|
64
|
+
def update(attributes = {})
|
65
|
+
set(attributes)
|
66
|
+
save
|
67
|
+
end
|
68
|
+
|
69
|
+
def set(attributes = {})
|
70
|
+
attributes.each { |k, v| send("#{k}=", v) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def save
|
74
|
+
find(self.class.submitter).click
|
75
|
+
end
|
76
|
+
|
77
|
+
def fields
|
78
|
+
self.class.fields.each_with_object({}) do |field, memo|
|
79
|
+
memo[field] = send(field)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Domino::Form::Field
|
2
|
+
attr_reader :name, :locator, :options, :callback
|
3
|
+
|
4
|
+
def initialize(name, locator, options = {}, &callback)
|
5
|
+
@name = name
|
6
|
+
@locator = locator
|
7
|
+
@options = options
|
8
|
+
@callback = callback
|
9
|
+
extract_field_options
|
10
|
+
end
|
11
|
+
|
12
|
+
# Delete any options for your field type that shouldn't be passed to
|
13
|
+
# the field locator.
|
14
|
+
# Default: noop
|
15
|
+
def extract_field_options
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convert the value from `#read` via callback if provided.
|
19
|
+
def value(node)
|
20
|
+
val = read(node)
|
21
|
+
if val && callback.is_a?(Proc)
|
22
|
+
callback.call(val)
|
23
|
+
else
|
24
|
+
val
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Locate the field using the locator and options
|
29
|
+
def field(node)
|
30
|
+
node.find_field(locator, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Value that will be passed to the callback.
|
34
|
+
# Default: field_node.value
|
35
|
+
def read(node)
|
36
|
+
field(node).value
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets the value on the field node.
|
40
|
+
# Default: node.fill_in for text fields.
|
41
|
+
def write(node, value)
|
42
|
+
node.fill_in(locator, with: value, **options)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Domino::Form::SelectField < Domino::Form::Field
|
2
|
+
# Returns the set of selected options that can be processed in the callback.
|
3
|
+
def read(node)
|
4
|
+
s = field(node)
|
5
|
+
selected = s.all("option[selected]")
|
6
|
+
s.multiple? ? selected : selected.first
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(node, value)
|
10
|
+
s = field(node)
|
11
|
+
values = Array(value)
|
12
|
+
|
13
|
+
s.all('option').each do |o|
|
14
|
+
if values.include?(o.text) || values.include?(o.value)
|
15
|
+
o.select_option
|
16
|
+
elsif s.multiple?
|
17
|
+
o.unselect_option
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def value(node)
|
23
|
+
val = read(node)
|
24
|
+
return val unless callback
|
25
|
+
if field(node).multiple?
|
26
|
+
val.map { |opt| callback.call(opt) }
|
27
|
+
else
|
28
|
+
callback.call(val)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Any callback mapping on a select will recieve one or more option nodes.
|
33
|
+
# Applying to one item or an Enumerable.
|
34
|
+
def callback
|
35
|
+
@callback ||= :value.to_proc
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
class DominoFormTest < Minitest::Test
|
2
|
+
include Capybara::DSL
|
3
|
+
|
4
|
+
module Dom
|
5
|
+
class CheckBoxesField < Domino::Form::Field
|
6
|
+
def read(node)
|
7
|
+
node.find(locator).all('input[type=checkbox]').select(&:checked?).map(&:value)
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(node, value)
|
11
|
+
value = Array(value)
|
12
|
+
node.find(locator).all('input[type=checkbox]').each do |box|
|
13
|
+
box.set(value.include?(box.value))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class PersonForm < Domino::Form
|
19
|
+
selector 'form.person'
|
20
|
+
key 'person'
|
21
|
+
|
22
|
+
field :name, 'First Name'
|
23
|
+
field :last_name
|
24
|
+
field :biography, 'person[bio]'
|
25
|
+
field :favorite_color, 'Favorite Color', as: :select, &:text
|
26
|
+
field :age, 'person_age', &:to_i
|
27
|
+
field :vehicles, '.input.vehicles', as: CheckBoxesField
|
28
|
+
field :is_human, 'is_human', as: :boolean
|
29
|
+
|
30
|
+
attribute :action, '&[action]'
|
31
|
+
attribute :submit_method, '&[method]'
|
32
|
+
end
|
33
|
+
|
34
|
+
class PersonFormB < Domino::Form
|
35
|
+
selector 'form.person'
|
36
|
+
|
37
|
+
field :is_human, as: :boolean
|
38
|
+
field :allergies, as: :select
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def setup
|
43
|
+
visit '/people/23/edit'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_form_field_with_label_locator
|
47
|
+
assert_equal 'Alice', Dom::PersonForm.find!.name
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_form_field_with_default_locator_and_form_key
|
51
|
+
assert_equal 'Cooper', Dom::PersonForm.find!.last_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_form_field_with_name_locator
|
55
|
+
assert_equal 'Alice is fun', Dom::PersonForm.find!.biography
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_form_field_as_select_field_type
|
59
|
+
assert_equal 'Blue', Dom::PersonForm.find!.favorite_color
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_form_field_as_multiple_select_field_type
|
63
|
+
formb = Dom::PersonFormB.find!
|
64
|
+
assert_equal [], formb.allergies
|
65
|
+
formb.allergies = %w[Peanut Corn]
|
66
|
+
assert_equal %w[peanut corn], formb.allergies
|
67
|
+
formb.allergies = ['corn']
|
68
|
+
assert_equal ['corn'], formb.allergies
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_form_field_with_id_locator_and_callback
|
72
|
+
assert_equal 23, Dom::PersonForm.find!.age
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_form_field_with_custom_field_type
|
76
|
+
assert_equal [], Dom::PersonForm.find!.vehicles
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_form_field_with_boolean_field_type
|
80
|
+
assert_equal false, Dom::PersonForm.find!.is_human
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_form_set_multiple_attributes
|
84
|
+
person = Dom::PersonForm.find!
|
85
|
+
person.set name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Red', age: 25, vehicles: %w[Bike Car], is_human: true
|
86
|
+
|
87
|
+
assert_equal 'Marie', person.name
|
88
|
+
assert_equal 'Curie', person.last_name
|
89
|
+
assert_equal 'Scientific!', person.biography
|
90
|
+
assert_equal 'Red', person.favorite_color
|
91
|
+
assert_equal 25, person.age
|
92
|
+
assert_equal %w[Bike Car], person.vehicles
|
93
|
+
assert_equal true, person.is_human
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_form_fields
|
97
|
+
person = Dom::PersonForm.find!
|
98
|
+
assert_equal({ name: 'Alice', last_name: 'Cooper', biography: 'Alice is fun', favorite_color: 'Blue', age: 23, vehicles: [], is_human: false }, person.fields)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_form_set_nil_clears_field
|
102
|
+
person = Dom::PersonForm.find!
|
103
|
+
person.name = nil
|
104
|
+
assert_equal '', person.name
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_form_set_by_attribute_writer
|
108
|
+
person = Dom::PersonForm.find!
|
109
|
+
assert_equal 23, person.age
|
110
|
+
person.age = 66
|
111
|
+
assert_equal 66, person.age
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_form_default_selector_without_key
|
115
|
+
formb = Dom::PersonFormB.find!
|
116
|
+
assert_equal false, formb.is_human
|
117
|
+
formb.is_human = true
|
118
|
+
assert_equal true, formb.is_human
|
119
|
+
formb.is_human = false
|
120
|
+
assert_equal false, formb.is_human
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_save_submits_form
|
124
|
+
person = Dom::PersonForm.find!
|
125
|
+
refute page.has_content?('Person updated successfully.')
|
126
|
+
|
127
|
+
person.set name: 'Marie', last_name: 'Curie', biography: 'Scientific!', age: 25, favorite_color: 'Green', vehicles: %w[Bike Car], is_human: true
|
128
|
+
|
129
|
+
person.save
|
130
|
+
|
131
|
+
assert page.has_content?('Person updated successfully.')
|
132
|
+
|
133
|
+
updated_person = Dom::PersonForm.find!
|
134
|
+
assert_equal 'Marie', updated_person.name
|
135
|
+
assert_equal 'Curie', updated_person.last_name
|
136
|
+
assert_equal 'Scientific!', updated_person.biography
|
137
|
+
assert_equal 'Green', updated_person.favorite_color
|
138
|
+
assert_equal 25, updated_person.age
|
139
|
+
assert_equal %w[Bike Car], updated_person.vehicles
|
140
|
+
assert_equal true, updated_person.is_human
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_update_fills_and_submits_form
|
144
|
+
person = Dom::PersonForm.find!
|
145
|
+
refute page.has_content?('Person updated successfully.')
|
146
|
+
|
147
|
+
person.update name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true
|
148
|
+
|
149
|
+
assert page.has_content?('Person updated successfully.')
|
150
|
+
|
151
|
+
updated_person = Dom::PersonForm.find!
|
152
|
+
assert_equal({ name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true }, updated_person.fields)
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_create_fills_and_submits_form
|
156
|
+
person = Dom::PersonForm.find!
|
157
|
+
refute page.has_content?('Person updated successfully.')
|
158
|
+
|
159
|
+
person.create name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true
|
160
|
+
|
161
|
+
assert page.has_content?('Person updated successfully.')
|
162
|
+
|
163
|
+
updated_person = Dom::PersonForm.find!
|
164
|
+
assert_equal({ name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true }, updated_person.fields)
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_static_update_fills_and_submits_form
|
168
|
+
refute page.has_content?('Person updated successfully.')
|
169
|
+
|
170
|
+
Dom::PersonForm.create name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true
|
171
|
+
|
172
|
+
assert page.has_content?('Person updated successfully.')
|
173
|
+
|
174
|
+
updated_person = Dom::PersonForm.find!
|
175
|
+
assert_equal({ name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true }, updated_person.fields)
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_static_create_fills_and_submits_form
|
179
|
+
refute page.has_content?('Person updated successfully.')
|
180
|
+
|
181
|
+
Dom::PersonForm.create name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true
|
182
|
+
|
183
|
+
assert page.has_content?('Person updated successfully.')
|
184
|
+
|
185
|
+
updated_person = Dom::PersonForm.find!
|
186
|
+
assert_equal({ name: 'Marie', last_name: 'Curie', biography: 'Scientific!', favorite_color: 'Green', age: 25, vehicles: %w[Bike Car], is_human: true }, updated_person.fields)
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_static_create_with_no_matches
|
190
|
+
visit "/"
|
191
|
+
assert_raises Capybara::ElementNotFound do
|
192
|
+
Dom::PersonForm.create name: 'Marie', last_name: 'Curie'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_static_update_with_no_matches
|
197
|
+
visit "/"
|
198
|
+
assert_raises Capybara::ElementNotFound do
|
199
|
+
Dom::PersonForm.update name: 'Marie', last_name: 'Curie'
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_supports_normal_attributes
|
204
|
+
assert_equal({ action: '/people/23', submit_method: 'post' }, Dom::PersonForm.find!.attributes)
|
205
|
+
end
|
206
|
+
end
|
data/test/domino_test.rb
CHANGED
@@ -1,59 +1,6 @@
|
|
1
|
-
|
2
|
-
unless ENV['CI']
|
3
|
-
require 'simplecov'
|
4
|
-
SimpleCov.start
|
5
|
-
end
|
6
|
-
Bundler.require
|
7
|
-
require 'minitest/autorun'
|
8
|
-
require 'minitest/mock'
|
9
|
-
|
10
|
-
class TestApplication
|
11
|
-
def call(_env)
|
12
|
-
[200, { 'Content-Type' => 'text/plain' }, [%(
|
13
|
-
<html>
|
14
|
-
<body>
|
15
|
-
<h1>Here are people and animals</h1>
|
16
|
-
<div id='people'>
|
17
|
-
<div class='person active' data-rank="1" data-uuid="e94bb2d3-71d2-4efb-abd4-ebc0cb58d19f">
|
18
|
-
<h2 class='name'>Alice</h2>
|
19
|
-
<p class='last-name'>Cooper</p>
|
20
|
-
<p class='bio'>Alice is fun</p>
|
21
|
-
<p class='fav-color'>Blue</p>
|
22
|
-
<p class='age'>23</p>
|
23
|
-
</div>
|
24
|
-
<div class='person' data-rank="3" data-uuid="05bf319e-8d6a-43c2-be37-2dad8ddbe5af">
|
25
|
-
<h2 class='name'>Bob</h2>
|
26
|
-
<p class='last-name'>Marley</p>
|
27
|
-
<p class='bio'>Bob is smart</p>
|
28
|
-
<p class='fav-color'>Red</p>
|
29
|
-
<p class='age'>52</p>
|
30
|
-
</div>
|
31
|
-
<div class='person' data-rank="2" data-uuid="4abcdeff-1d36-44a9-a05e-8fc57564d2c4">
|
32
|
-
<h2 class='name'>Charlie</h2>
|
33
|
-
<p class='last-name'>Murphy</p>
|
34
|
-
<p class='bio'>Charlie is wild</p>
|
35
|
-
<p class='fav-color'>Red</p>
|
36
|
-
</div>
|
37
|
-
<div class='person' data-rank="7" data-blocked data-uuid="2afccde0-5d13-41c7-ab01-7f37fb2fe3ee">
|
38
|
-
<h2 class='name'>Donna</h2>
|
39
|
-
<p class='last-name'>Summer</p>
|
40
|
-
<p class='bio'>Donna is quiet</p>
|
41
|
-
</div>
|
42
|
-
</div>
|
43
|
-
<div id='animals'></div>
|
44
|
-
<div id='receipts'>
|
45
|
-
<div class='receipt' id='receipt-72' data-store='ACME'></div>
|
46
|
-
</div>
|
47
|
-
</body>
|
48
|
-
</html>
|
49
|
-
)]]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
Capybara.app = TestApplication.new
|
54
|
-
|
55
|
-
class DominoTest < MiniTest::Unit::TestCase
|
1
|
+
class DominoTest < Minitest::Test
|
56
2
|
include Capybara::DSL
|
3
|
+
|
57
4
|
module Dom
|
58
5
|
class Person < Domino
|
59
6
|
selector '#people .person'
|
@@ -0,0 +1,142 @@
|
|
1
|
+
class TestApplication
|
2
|
+
def call(env)
|
3
|
+
[200, { 'Content-Type' => 'text/plain' }, [response(env)]]
|
4
|
+
end
|
5
|
+
|
6
|
+
def response(env)
|
7
|
+
case env.fetch('PATH_INFO')
|
8
|
+
when '/'
|
9
|
+
root
|
10
|
+
when '/people/23/edit'
|
11
|
+
params = {
|
12
|
+
'person' => {
|
13
|
+
'id' => 23,
|
14
|
+
'name' => 'Alice',
|
15
|
+
'last_name' => 'Cooper',
|
16
|
+
'bio' => 'Alice is fun',
|
17
|
+
'fav_color' => 'blue',
|
18
|
+
'age' => 23,
|
19
|
+
'vehicles' => []
|
20
|
+
}, 'is_human' => false
|
21
|
+
}
|
22
|
+
edit params
|
23
|
+
when '/people/23'
|
24
|
+
params = Rack::Utils.parse_nested_query(env.fetch('rack.input').read)
|
25
|
+
edit params.merge(flash: "Person updated successfully.")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def root
|
30
|
+
<<-HTML
|
31
|
+
<html>
|
32
|
+
<body>
|
33
|
+
<h1>Here are people and animals</h1>
|
34
|
+
<div id='people'>
|
35
|
+
<div class='person active' data-rank="1" data-uuid="e94bb2d3-71d2-4efb-abd4-ebc0cb58d19f">
|
36
|
+
<h2 class='name'>Alice</h2>
|
37
|
+
<p class='last-name'>Cooper</p>
|
38
|
+
<p class='bio'>Alice is fun</p>
|
39
|
+
<p class='fav-color'>Blue</p>
|
40
|
+
<p class='age'>23</p>
|
41
|
+
</div>
|
42
|
+
<div class='person' data-rank="3" data-uuid="05bf319e-8d6a-43c2-be37-2dad8ddbe5af">
|
43
|
+
<h2 class='name'>Bob</h2>
|
44
|
+
<p class='last-name'>Marley</p>
|
45
|
+
<p class='bio'>Bob is smart</p>
|
46
|
+
<p class='fav-color'>Red</p>
|
47
|
+
<p class='age'>52</p>
|
48
|
+
</div>
|
49
|
+
<div class='person' data-rank="2" data-uuid="4abcdeff-1d36-44a9-a05e-8fc57564d2c4">
|
50
|
+
<h2 class='name'>Charlie</h2>
|
51
|
+
<p class='last-name'>Murphy</p>
|
52
|
+
<p class='bio'>Charlie is wild</p>
|
53
|
+
<p class='fav-color'>Red</p>
|
54
|
+
</div>
|
55
|
+
<div class='person' data-rank="7" data-blocked data-uuid="2afccde0-5d13-41c7-ab01-7f37fb2fe3ee">
|
56
|
+
<h2 class='name'>Donna</h2>
|
57
|
+
<p class='last-name'>Summer</p>
|
58
|
+
<p class='bio'>Donna is quiet</p>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
<div id='animals'></div>
|
62
|
+
<div id='receipts'>
|
63
|
+
<div class='receipt' id='receipt-72' data-store='ACME'></div>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
67
|
+
HTML
|
68
|
+
end
|
69
|
+
|
70
|
+
def edit(params = { 'person' => {} })
|
71
|
+
person = params['person']
|
72
|
+
<<-HTML
|
73
|
+
<html>
|
74
|
+
<body>
|
75
|
+
<div class="flash">#{params[:flash]}</div>
|
76
|
+
<h1>Edit Person</h1>
|
77
|
+
|
78
|
+
<form action="/people/#{person['id']}" method="post" class="person">
|
79
|
+
<div class="input name">
|
80
|
+
<label for="person_name">First Name</label>
|
81
|
+
<input type="text" id="person_name" name="person[name]" value="#{person['name']}" />
|
82
|
+
</div>
|
83
|
+
|
84
|
+
<div class="input last_name">
|
85
|
+
<label for="person_name">Last Name</label>
|
86
|
+
<input type="text" id="person_last_name" name="person[last_name]" value="#{person['last_name']}" />
|
87
|
+
</div>
|
88
|
+
|
89
|
+
<div class="input bio">
|
90
|
+
<label for="person_bio">Biography</label>
|
91
|
+
<textarea id="person_bio" name="person[bio]">#{person['bio']}</textarea>
|
92
|
+
</div>
|
93
|
+
|
94
|
+
<div class="input fav_color">
|
95
|
+
<label for="person_fav_color">Favorite Color</label>
|
96
|
+
<select id="person_fav_color" name="person[fav_color]">
|
97
|
+
<option value>- Select a Color -</option>
|
98
|
+
<option value="red" #{'selected="selected"' if person['fav_color'] == 'red'}>Red</option>
|
99
|
+
<option value="blue" #{'selected="selected"' if person['fav_color'] == 'blue'}>Blue</option>
|
100
|
+
<option value="green" #{'selected="selected"' if person['fav_color'] == 'green'}>Green</option>
|
101
|
+
</select>
|
102
|
+
</div>
|
103
|
+
|
104
|
+
<div class="input age">
|
105
|
+
<label for="person_age">Biography</label>
|
106
|
+
<input type="number" min="0" step="1" id="person_age" name="person[age]" value="#{person['age']}" />
|
107
|
+
</div>
|
108
|
+
|
109
|
+
<div class="input is_human">
|
110
|
+
<input type="hidden" name="is_human" value="0">
|
111
|
+
<label for="is_a_human">
|
112
|
+
<input id="is_a_human" type="checkbox" name="is_human" value="1" #{'checked' if params['is_human']}>
|
113
|
+
I'm a human
|
114
|
+
</label>
|
115
|
+
</div>
|
116
|
+
|
117
|
+
<div class="input vehicles">
|
118
|
+
<label for="person_vehicles_bike"><input id="person_vehicles_bike" type="checkbox" name="person[vehicles][]" value="Bike" #{'checked' if person['vehicles'].include?('Bike')}>Bike</label>
|
119
|
+
<label for="person_vehicles_car"><input id="person_vehicles_car" type="checkbox" name="person[vehicles][]" value="Car" #{'checked' if person['vehicles'].include?('Car')}>Car</label>
|
120
|
+
</div>
|
121
|
+
|
122
|
+
<div class="input allergies">
|
123
|
+
<label for="allergies">Allergies</label>
|
124
|
+
<select id="allergies" name="allergies" multiple="multiple">
|
125
|
+
<option value>None</option>
|
126
|
+
<option value="peanut" #{'selected="selected"' if Array(params['allergies']).include?('peanut')}>Peanut</option>
|
127
|
+
<option value="corn" #{'selected="selected"' if Array(person['allergies']).include?('corn')}>Corn</option>
|
128
|
+
<option value="wheat" #{'selected="selected"' if Array(person['allergies']).include?('wheat')}>Wheat</option>
|
129
|
+
</select>
|
130
|
+
</div>
|
131
|
+
|
132
|
+
<div class="actions">
|
133
|
+
<input type="submit" name="commit" value="Update Person" />
|
134
|
+
</div>
|
135
|
+
</form>
|
136
|
+
</body>
|
137
|
+
</html>
|
138
|
+
HTML
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
Capybara.app = TestApplication.new
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: domino
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Gauthier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -83,7 +83,15 @@ files:
|
|
83
83
|
- Rakefile
|
84
84
|
- domino.gemspec
|
85
85
|
- lib/domino.rb
|
86
|
+
- lib/domino/attribute.rb
|
87
|
+
- lib/domino/form.rb
|
88
|
+
- lib/domino/form/boolean_field.rb
|
89
|
+
- lib/domino/form/field.rb
|
90
|
+
- lib/domino/form/select_field.rb
|
91
|
+
- test/domino_form_test.rb
|
86
92
|
- test/domino_test.rb
|
93
|
+
- test/test_application.rb
|
94
|
+
- test/test_helper.rb
|
87
95
|
homepage: http://github.com/ngauthier/domino
|
88
96
|
licenses:
|
89
97
|
- MIT
|
@@ -109,4 +117,7 @@ signing_key:
|
|
109
117
|
specification_version: 4
|
110
118
|
summary: View abstraction for integration testing
|
111
119
|
test_files:
|
120
|
+
- test/domino_form_test.rb
|
112
121
|
- test/domino_test.rb
|
122
|
+
- test/test_application.rb
|
123
|
+
- test/test_helper.rb
|