domino 0.8.0 → 0.9.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.
- 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
|