corner_stones 0.0.1.beta1
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/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.md +84 -0
- data/Rakefile +5 -0
- data/corner_stones.gemspec +25 -0
- data/lib/corner_stones.rb +8 -0
- data/lib/corner_stones/all.rb +5 -0
- data/lib/corner_stones/flash_messages.rb +35 -0
- data/lib/corner_stones/form.rb +57 -0
- data/lib/corner_stones/form/with_inline_errors.rb +33 -0
- data/lib/corner_stones/table.rb +47 -0
- data/lib/corner_stones/table/deletable_rows.rb +15 -0
- data/lib/corner_stones/table/selectable_rows.rb +15 -0
- data/lib/corner_stones/tabs.rb +19 -0
- data/lib/corner_stones/tabs/active_tracking.rb +24 -0
- data/lib/corner_stones/version.rb +3 -0
- data/spec/integration/corner_stones/flash_messages_spec.rb +67 -0
- data/spec/integration/corner_stones/form_spec.rb +154 -0
- data/spec/integration/corner_stones/table_spec.rb +189 -0
- data/spec/integration/corner_stones/tabs_spec.rb +49 -0
- data/spec/integration/spec_helper.rb +12 -0
- data/spec/integration/support/response_macros.rb +12 -0
- data/tasks/test.rake +6 -0
- data/travis.yml +7 -0
- metadata +89 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create ruby-1.9.3@corner_stones
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Corner Stones
|
2
|
+
|
3
|
+
[](http://travis-ci.org/senny/corner_stones)
|
4
|
+
|
5
|
+
assists you in building PageObjects to make your acceptance tests more object oriented.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
``` terminal
|
10
|
+
$ gem install corner_stones
|
11
|
+
```
|
12
|
+
|
13
|
+
or in your **Gemfile**
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
gem 'corner_stones'
|
17
|
+
```
|
18
|
+
|
19
|
+
## Examples
|
20
|
+
|
21
|
+
a lot of examples can be found in the [integration specs](https://github.com/senny/corner_stones/tree/master/spec/integration).
|
22
|
+
Some features of corner_stones are listed below.
|
23
|
+
|
24
|
+
### Tabs
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
tabs = CornerStones::Tabs.new('.tab-navigation')
|
28
|
+
tabs.open('Details') # open a tab
|
29
|
+
```
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
tabs = CornerStones::Tabs.new('.tab-navigation').tap do |t|
|
33
|
+
t.extend(CornerStones::Tabs::ActiveTracking)
|
34
|
+
end
|
35
|
+
|
36
|
+
tabs.open('About') # open a tab and verify that the opened tab is active
|
37
|
+
tabs.assert_current_tab_is('Main') # verify that the tab 'Main' is active
|
38
|
+
```
|
39
|
+
|
40
|
+
### Flash Messages
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
flash = CornerStones::FlashMessages.new
|
44
|
+
flash.assert_flash_is_present(:notice, 'Article saved') # verify that a given flash message is present
|
45
|
+
```
|
46
|
+
|
47
|
+
### Tables
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
table = CornerStones::Table.new('.articles')
|
51
|
+
table.rows # returns an array of rows. Each row is represented as a Hash {header} => {value}
|
52
|
+
table.row('Title' => 'Management') # returns the row-hash for the row with 'Management' in the 'Title' column
|
53
|
+
```
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
table = CornerStones::Table.new('.articles').tap do |t|
|
57
|
+
t.extend(CornerStones::Table::SelectableRows)
|
58
|
+
t.extend(CornerStones::Table::DeletableRows)
|
59
|
+
end
|
60
|
+
table.select_row('Created at' => '01.12.2001') # select the row, which has '01.12.2001' in the 'Created at' column
|
61
|
+
table.delete_row('ID' => '9') # delete the row, which contains '9' in the 'ID' column
|
62
|
+
```
|
63
|
+
|
64
|
+
### Forms
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
form = CornerStones::Form.new('.new-article', :select_fields => ['Author'])
|
68
|
+
form.fill_in_with('Title' => 'Some Article', 'Author' => 'C. J.') # fill out the form
|
69
|
+
form.submit # submit the form using the 'Save' button
|
70
|
+
form.submit(:button => 'Save Article') # submit the form using the 'Save Article' button
|
71
|
+
|
72
|
+
form.process(:fill_in => {'Title' => 'Some Article', 'Author' => 'C. J.'},
|
73
|
+
:button => 'Save Article) # fill out + submit
|
74
|
+
```
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
form = CornerStones::Form.new('.update-article').tap do |f|
|
78
|
+
f.extend(CornerStones::Form::WithInlineErrors)
|
79
|
+
end
|
80
|
+
form.errors # returns an Array of form errors
|
81
|
+
form.assert_has_no_errors # verify that the form was submitted correctly
|
82
|
+
form.submit # verifies that the form has no errors
|
83
|
+
form.submit(:assert_valid => false) # do not veirfy that no errors were present
|
84
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "corner_stones/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "corner_stones"
|
7
|
+
s.version = CornerStones::VERSION
|
8
|
+
s.authors = ["Yves Senn"]
|
9
|
+
s.email = ["yves.senn@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{capybara building blocks for acceptance tests}
|
12
|
+
s.description = %q{This gem makes it easy to build PageObjects and make your acceptance tests more object oriented. It includes a implementations for common elements like tables, tabs, navigations etc.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "corner_stones"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
s.add_runtime_dependency "capybara"
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module CornerStones
|
2
|
+
class FlashMessages
|
3
|
+
|
4
|
+
class FlashMessageMissingError < StandardError; end
|
5
|
+
|
6
|
+
include Capybara::DSL
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def message(type, text)
|
13
|
+
messages[type].detect {|message| message[:text] == text}
|
14
|
+
end
|
15
|
+
|
16
|
+
def messages
|
17
|
+
message_types.inject(Hash.new {|hash, key| hash[key] = []}) do |present_messages, type|
|
18
|
+
all(".#{type} p").map do |message|
|
19
|
+
present_messages[type] << {:text => message.text}
|
20
|
+
end
|
21
|
+
present_messages
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def message_types
|
26
|
+
@options.fetch(:message_types) { [:notice, :error, :alert] }
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_flash_is_present(type, message)
|
30
|
+
unless message(type, message)
|
31
|
+
raise FlashMessageMissingError, "the flash message: '#{message}' with type: #{type} was not found"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'corner_stones/form/with_inline_errors'
|
2
|
+
|
3
|
+
module CornerStones
|
4
|
+
class Form
|
5
|
+
|
6
|
+
include Capybara::DSL
|
7
|
+
|
8
|
+
def initialize(scope, options = {})
|
9
|
+
@scope = scope
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(params)
|
14
|
+
fill_in_with(params[:fill_in])
|
15
|
+
submit(params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def submit(submit_options = {})
|
19
|
+
submit_text = submit_options.fetch(:button) { 'Save' }
|
20
|
+
within @scope do
|
21
|
+
click_button(submit_text)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def fill_in_with(attributes)
|
26
|
+
within @scope do
|
27
|
+
attributes.each do |name, value|
|
28
|
+
if select_fields.include?(name)
|
29
|
+
select(value, :from => name)
|
30
|
+
elsif autocomplete_fields.include?(name)
|
31
|
+
autocomplete(value, :in => name)
|
32
|
+
else
|
33
|
+
fill_in(name, :with => value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def autocomplete(value, options)
|
40
|
+
autocomplete_id = find_field(options[:in])[:id]
|
41
|
+
fill_in(options[:in], :with => value)
|
42
|
+
page.execute_script %Q{ $('##{autocomplete_id}').trigger("focus") }
|
43
|
+
page.execute_script %Q{ $('##{autocomplete_id}').trigger("keydown") }
|
44
|
+
sleep 1
|
45
|
+
page.execute_script %Q{ $('.ui-menu-item a:contains("#{value}")').trigger("mouseenter").trigger("click"); }
|
46
|
+
end
|
47
|
+
|
48
|
+
def select_fields
|
49
|
+
@options.fetch(:select_fields) { [] }
|
50
|
+
end
|
51
|
+
|
52
|
+
def autocomplete_fields
|
53
|
+
@options.fetch(:autocomplete_fields) { [] }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CornerStones
|
2
|
+
class Form
|
3
|
+
module WithInlineErrors
|
4
|
+
|
5
|
+
class FormHasErrorsError < StandardError; end
|
6
|
+
|
7
|
+
def submit(options = {})
|
8
|
+
assert_valid = options.fetch(:assert_valid) { true }
|
9
|
+
super
|
10
|
+
assert_has_no_errors if assert_valid
|
11
|
+
end
|
12
|
+
|
13
|
+
def errors
|
14
|
+
all('.error').map do |container|
|
15
|
+
label = container.all('label').first
|
16
|
+
input = container.all('input, textarea, select').first
|
17
|
+
error = container.all('.help-inline').first
|
18
|
+
|
19
|
+
{ 'Field' => label && label.text,
|
20
|
+
'Value' => input && input.value,
|
21
|
+
'Error' => error && error.text }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_has_no_errors
|
26
|
+
unless errors == []
|
27
|
+
raise FormHasErrorsError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'corner_stones/table/selectable_rows'
|
2
|
+
require 'corner_stones/table/deletable_rows'
|
3
|
+
|
4
|
+
module CornerStones
|
5
|
+
|
6
|
+
class Table
|
7
|
+
include Capybara::DSL
|
8
|
+
|
9
|
+
def initialize(scope, options = {})
|
10
|
+
@scope = scope
|
11
|
+
@data_selector = options.fetch(:data_selector) { 'td' }
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def row(options)
|
16
|
+
rows.detect { |row|
|
17
|
+
identity = row.select { |key, value| options.has_key?(key) }
|
18
|
+
identity == options
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def rows
|
23
|
+
within @scope do
|
24
|
+
all('tbody tr').map do |row|
|
25
|
+
attributes_for_row(row)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers
|
31
|
+
@options[:headers] || detect_table_headers
|
32
|
+
end
|
33
|
+
|
34
|
+
def detect_table_headers
|
35
|
+
all('thead th').map(&:text)
|
36
|
+
end
|
37
|
+
|
38
|
+
def attributes_for_row(row)
|
39
|
+
data = row.all(@data_selector)
|
40
|
+
|
41
|
+
real_data = data[0...headers.size].map(&:text)
|
42
|
+
|
43
|
+
Hash[headers.zip(real_data)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CornerStones
|
2
|
+
class Table
|
3
|
+
module DeletableRows
|
4
|
+
|
5
|
+
def delete_row(options)
|
6
|
+
row(options)['Delete-Link'].click
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes_for_row(row)
|
10
|
+
super.merge('Delete-Link' => row.find('.delete-action a'))
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CornerStones
|
2
|
+
class Table
|
3
|
+
module SelectableRows
|
4
|
+
|
5
|
+
def select_row(options)
|
6
|
+
visit row(options)['Selected-Link']
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes_for_row(row)
|
10
|
+
super.merge('Selected-Link' => row['data-selected-url'])
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'corner_stones/tabs/active_tracking'
|
2
|
+
|
3
|
+
module CornerStones
|
4
|
+
class Tabs
|
5
|
+
|
6
|
+
include Capybara::DSL
|
7
|
+
|
8
|
+
def initialize(element_scope)
|
9
|
+
@element_scope = element_scope
|
10
|
+
end
|
11
|
+
|
12
|
+
def open(tab)
|
13
|
+
within(@element_scope) do
|
14
|
+
click_link(tab)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CornerStones
|
2
|
+
class Tabs
|
3
|
+
module ActiveTracking
|
4
|
+
|
5
|
+
class ActiveTabMismatchError < StandardError; end
|
6
|
+
|
7
|
+
def open(tab)
|
8
|
+
super
|
9
|
+
assert_current_tab_is(tab)
|
10
|
+
end
|
11
|
+
|
12
|
+
def assert_current_tab_is(tab)
|
13
|
+
current_tab = nil
|
14
|
+
wait_until do
|
15
|
+
current_tab = find(@element_scope).find('.active').text
|
16
|
+
current_tab == tab
|
17
|
+
end
|
18
|
+
rescue Capybara::TimeoutError
|
19
|
+
raise ActiveTabMismatchError, "the active tab is '#{current_tab}' instead of '#{tab}'"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'integration/spec_helper'
|
2
|
+
|
3
|
+
require 'corner_stones/flash_messages'
|
4
|
+
|
5
|
+
describe CornerStones::FlashMessages do
|
6
|
+
given_the_html <<-HTML
|
7
|
+
<div class="alert">
|
8
|
+
<p>Article was not saved. Please correct the errors.</p>
|
9
|
+
</div>
|
10
|
+
<div class="notice">
|
11
|
+
<p>Article saved.</p>
|
12
|
+
</div>
|
13
|
+
<div class="notice">
|
14
|
+
<p>Successfully logged in</p>
|
15
|
+
</div>
|
16
|
+
HTML
|
17
|
+
|
18
|
+
subject { CornerStones::FlashMessages.new}
|
19
|
+
|
20
|
+
it 'assebles present messages into a hash' do
|
21
|
+
subject.messages.must_equal(:alert => [{:text => 'Article was not saved. Please correct the errors.'}],
|
22
|
+
:notice => [{:text => 'Article saved.'}, {:text => 'Successfully logged in'}])
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'you can select a message by type and content' do
|
26
|
+
subject.message(:notice, 'Article saved.').must_equal({:text => 'Article saved.'})
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'nil is returned when no message was found' do
|
30
|
+
subject.message(:alert, 'Article saved.').must_equal(nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#assert_flash_is_present' do
|
34
|
+
it 'passes when the flash is present' do
|
35
|
+
subject.assert_flash_is_present(:alert, 'Article was not saved. Please correct the errors.')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'fails when the flash is missing' do
|
39
|
+
lambda do
|
40
|
+
subject.assert_flash_is_present(:notice, 'I am not displayed')
|
41
|
+
end.must_raise(CornerStones::FlashMessages::FlashMessageMissingError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'custom message types' do
|
46
|
+
given_the_html <<-HTML
|
47
|
+
<div class="alert-error">
|
48
|
+
<p>Article was not saved. Please correct the errors.</p>
|
49
|
+
</div>
|
50
|
+
<div class="alert-info">
|
51
|
+
<p>Article saved.</p>
|
52
|
+
</div>
|
53
|
+
<div class="alert-info">
|
54
|
+
<p>Successfully logged in</p>
|
55
|
+
</div>
|
56
|
+
HTML
|
57
|
+
|
58
|
+
subject { CornerStones::FlashMessages.new(:message_types => [:'alert-info', :'alert-error', :'alert-warning'])}
|
59
|
+
|
60
|
+
it 'uses the :message_type option to determine the available messages' do
|
61
|
+
subject.messages.must_equal(:'alert-error' => [{:text => 'Article was not saved. Please correct the errors.'}],
|
62
|
+
:'alert-info' => [{:text => 'Article saved.'}, {:text => 'Successfully logged in'}])
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'integration/spec_helper'
|
2
|
+
|
3
|
+
require 'corner_stones/form'
|
4
|
+
require 'corner_stones/form/with_inline_errors'
|
5
|
+
|
6
|
+
describe CornerStones::Form do
|
7
|
+
|
8
|
+
given_the_html <<-HTML
|
9
|
+
<form action="/articles" method="post" class="article-form">
|
10
|
+
<label for="title">Title</label>
|
11
|
+
<input type="text" name="title" id="title">
|
12
|
+
|
13
|
+
<label for="author">Author</label>
|
14
|
+
<select name="author" id="author">
|
15
|
+
<option value="1">Robert C. Martin</option>
|
16
|
+
<option value="2">Eric Evans</option>
|
17
|
+
<option value="3">Kent Beck</option>
|
18
|
+
</select>
|
19
|
+
|
20
|
+
<label for="body">Body</label>
|
21
|
+
<textarea name="body" id="body">
|
22
|
+
</textarea>
|
23
|
+
|
24
|
+
<input type="submit" name="button" value="Save">
|
25
|
+
<input type="submit" name="button" value="Save Article">
|
26
|
+
|
27
|
+
</form>
|
28
|
+
HTML
|
29
|
+
|
30
|
+
subject { CornerStones::Form.new('.article-form', :select_fields => ['Author']) }
|
31
|
+
|
32
|
+
it 'allows you to fill in the form' do
|
33
|
+
subject.fill_in_with('Title' => 'Domain Driven Design',
|
34
|
+
'Author' => 'Eric Evans',
|
35
|
+
'Body' => '...')
|
36
|
+
|
37
|
+
find('#title').value.must_equal 'Domain Driven Design'
|
38
|
+
find('#author').value.must_equal '2'
|
39
|
+
find('#body').value.must_equal '...'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'allows you to submit the form' do
|
43
|
+
subject.submit
|
44
|
+
|
45
|
+
current_path.must_equal '/articles'
|
46
|
+
page.driver.request.post?.must_equal true
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'you can supply the submit-button text with the :button option' do
|
50
|
+
subject.submit(:button => 'Save Article')
|
51
|
+
|
52
|
+
page.driver.request.params['button'].must_equal 'Save Article'
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'allows you to process (fill_in_with + submit) the form' do
|
56
|
+
subject.process(:fill_in => {
|
57
|
+
'Title' => 'Domain Driven Design',
|
58
|
+
'Author' => 'Eric Evans',
|
59
|
+
'Body' => 'Some Content...'})
|
60
|
+
|
61
|
+
current_path.must_equal '/articles'
|
62
|
+
page.driver.request.post?.must_equal true
|
63
|
+
|
64
|
+
page.driver.request.params.must_equal({"title" => "Domain Driven Design", "author" => "2", "body" => "Some Content...", 'button' => 'Save'})
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'allows you to process (fill_in_with + submit) the form using an alternate button' do
|
68
|
+
subject.process(:fill_in => {'Title' => 'Domain Driven Design',
|
69
|
+
'Author' => 'Eric Evans',
|
70
|
+
'Body' => 'Some Content...'},
|
71
|
+
:button => 'Save Article')
|
72
|
+
|
73
|
+
page.driver.request.params['button'].must_equal('Save Article')
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'mixins' do
|
77
|
+
describe 'form errors' do
|
78
|
+
|
79
|
+
before do
|
80
|
+
subject.extend(CornerStones::Form::WithInlineErrors)
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'with errors' do
|
84
|
+
given_the_html <<-HTML
|
85
|
+
<form action="/articles" method="post" class="form-with-errors article-form">
|
86
|
+
<div>
|
87
|
+
<label for="title">Title</label>
|
88
|
+
<input type="text" name="title" id="title">
|
89
|
+
</div>
|
90
|
+
|
91
|
+
<div class="error">
|
92
|
+
<label for="author">Author</label>
|
93
|
+
<select name="author" id="author">
|
94
|
+
<option value="1">Robert C. Martin</option>
|
95
|
+
<option value="2">Eric Evans</option>
|
96
|
+
<option value="3">Kent Beck</option>
|
97
|
+
</select>
|
98
|
+
<span class="help-inline">The author is not active</span>
|
99
|
+
</div>
|
100
|
+
|
101
|
+
<div class="error">
|
102
|
+
<label for="body">Body</label>
|
103
|
+
<textarea name="body" id="body">...</textarea>
|
104
|
+
<span class="help-inline">invalid body</span>
|
105
|
+
</div>
|
106
|
+
|
107
|
+
<input type="submit" value="Save">
|
108
|
+
|
109
|
+
</form>
|
110
|
+
HTML
|
111
|
+
|
112
|
+
it 'assembles the errors into a hash' do
|
113
|
+
subject.errors.must_equal([{"Field" => "Author", "Value" => "1", "Error" => "The author is not active"},
|
114
|
+
{"Field" => "Body", "Value" => "...", "Error" => "invalid body"}])
|
115
|
+
end
|
116
|
+
|
117
|
+
it '#assert_has_no_errors fails' do
|
118
|
+
lambda do
|
119
|
+
subject.assert_has_no_errors
|
120
|
+
end.must_raise(CornerStones::Form::WithInlineErrors::FormHasErrorsError)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'does not allow you to submit the form by default' do
|
124
|
+
lambda do
|
125
|
+
subject.submit
|
126
|
+
end.must_raise(CornerStones::Form::WithInlineErrors::FormHasErrorsError)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'bypass the auto-error-validation when passing :assert_valid => false' do
|
130
|
+
subject.submit(:assert_valid => false)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'without errors' do
|
135
|
+
given_the_html <<-HTML
|
136
|
+
<form action="/articles" method="post" class="form-without-errors article-form">
|
137
|
+
<label for="title">Title</label>
|
138
|
+
<input type="text" name="title" id="title">
|
139
|
+
|
140
|
+
<input type="submit" value="Save">
|
141
|
+
<form>
|
142
|
+
HTML
|
143
|
+
end
|
144
|
+
|
145
|
+
it '#assert_has_no_errors passes' do
|
146
|
+
subject.assert_has_no_errors
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'allows you to submit the form' do
|
150
|
+
subject.submit
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'integration/spec_helper'
|
2
|
+
|
3
|
+
require 'corner_stones/table'
|
4
|
+
require 'corner_stones/table/deletable_rows'
|
5
|
+
require 'corner_stones/table/selectable_rows'
|
6
|
+
|
7
|
+
describe CornerStones::Table do
|
8
|
+
|
9
|
+
given_the_html <<-HTML
|
10
|
+
<table class="articles">
|
11
|
+
<thead>
|
12
|
+
<tr>
|
13
|
+
<th>ID</th>
|
14
|
+
<th>Title</th>
|
15
|
+
<th>Author</th>
|
16
|
+
</tr>
|
17
|
+
</thead>
|
18
|
+
<tbody>
|
19
|
+
<tr>
|
20
|
+
<td>1</td>
|
21
|
+
<td>Clean Code</td>
|
22
|
+
<td>Robert C. Martin</td>
|
23
|
+
</tr>
|
24
|
+
<tr>
|
25
|
+
<td>2</td>
|
26
|
+
<td>Domain Driven Design</td>
|
27
|
+
<td>Eric Evans</td>
|
28
|
+
</tr>
|
29
|
+
</tbody>
|
30
|
+
</table
|
31
|
+
HTML
|
32
|
+
|
33
|
+
subject { CornerStones::Table.new('.articles') }
|
34
|
+
|
35
|
+
describe 'headers' do
|
36
|
+
it 'get detected automatically from the "th" tags' do
|
37
|
+
subject.headers.must_equal ['ID', 'Title', 'Author']
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'can be supplied with the :headers option' do
|
41
|
+
table_with_custom_headers = CornerStones::Table.new('.articles', :headers => ['A-ID', 'strTitle', 'strAuthor'])
|
42
|
+
table_with_custom_headers.headers.must_equal ['A-ID', 'strTitle', 'strAuthor']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "data" do
|
47
|
+
it 'is read into an array of hashes ({header} => {data})' do
|
48
|
+
subject.rows.must_equal [{'ID' => '1',
|
49
|
+
'Title' => 'Clean Code',
|
50
|
+
'Author' => 'Robert C. Martin'},
|
51
|
+
{'ID' => '2',
|
52
|
+
'Title' => 'Domain Driven Design',
|
53
|
+
'Author' => 'Eric Evans'}]
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'a row can be accessed with a single key' do
|
57
|
+
subject.row('Title' => 'Domain Driven Design').must_equal({ 'ID' => '2',
|
58
|
+
'Title' => 'Domain Driven Design',
|
59
|
+
'Author' => 'Eric Evans' })
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'a row can be accessed with multiple keys' do
|
63
|
+
subject.row('ID' => '1',
|
64
|
+
'Author' => 'Robert C. Martin').must_equal({'ID' => '1',
|
65
|
+
'Title' => 'Clean Code',
|
66
|
+
'Author' => 'Robert C. Martin'})
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'nil is returned when no matching row was found' do
|
70
|
+
subject.row('ID' => '3').must_equal(nil)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'custom tables' do
|
75
|
+
describe 'inline headers' do
|
76
|
+
given_the_html <<-HTML
|
77
|
+
<table class="articles">
|
78
|
+
<tbody>
|
79
|
+
<tr>
|
80
|
+
<th>Clean Code</th>
|
81
|
+
<td>Robert C. Martin</td>
|
82
|
+
</tr>
|
83
|
+
<tr>
|
84
|
+
<th>Domain Driven Design</th>
|
85
|
+
<td>Eric Evans</td>
|
86
|
+
</tr>
|
87
|
+
</tbody>
|
88
|
+
</table>
|
89
|
+
HTML
|
90
|
+
|
91
|
+
subject { CornerStones::Table.new('.articles', :headers => ['Book', 'Author'], :data_selector => 'th,td') }
|
92
|
+
|
93
|
+
it 'the option :data_selector can be used to widen the data to other elements' do
|
94
|
+
subject.rows.must_equal [{ 'Book' => 'Clean Code',
|
95
|
+
'Author' => 'Robert C. Martin'},
|
96
|
+
{ 'Book' => 'Domain Driven Design',
|
97
|
+
'Author' => 'Eric Evans'}]
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'mixins' do
|
104
|
+
describe 'deletable rows' do
|
105
|
+
given_the_html <<-HTML
|
106
|
+
<table class="articles">
|
107
|
+
<thead>
|
108
|
+
<tr>
|
109
|
+
<th>ID</th>
|
110
|
+
<th>Title</th>
|
111
|
+
<th>Author</th>
|
112
|
+
</tr>
|
113
|
+
</thead>
|
114
|
+
<tbody>
|
115
|
+
<tr>
|
116
|
+
<td>1</td>
|
117
|
+
<td>Clean Code</td>
|
118
|
+
<td>Robert C. Martin</td>
|
119
|
+
<td class="delete-action"><a href="/delete/clean_code">X</a></td>
|
120
|
+
</tr>
|
121
|
+
<tr>
|
122
|
+
<td>2</td>
|
123
|
+
<td>Domain Driven Design</td>
|
124
|
+
<td>Eric Evans</td>
|
125
|
+
<td class="delete-action"><a href="/delete/domain_driven_design">X</a></td>
|
126
|
+
</tr>
|
127
|
+
</tbody>
|
128
|
+
</table>
|
129
|
+
HTML
|
130
|
+
|
131
|
+
before do
|
132
|
+
subject.extend(CornerStones::Table::DeletableRows)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'it includes the "Delete-Link" object in the data' do
|
136
|
+
subject.rows.each do |row|
|
137
|
+
row['Delete-Link'].must_be_kind_of(Capybara::Node::Element)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'allows you to trigger a deletion with a row selector' do
|
142
|
+
subject.delete_row('Title' => 'Domain Driven Design')
|
143
|
+
current_path.must_equal '/delete/domain_driven_design'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe 'selectable rows' do
|
148
|
+
given_the_html <<-HTML
|
149
|
+
<table class="articles">
|
150
|
+
<thead>
|
151
|
+
<tr>
|
152
|
+
<th>ID</th>
|
153
|
+
<th>Title</th>
|
154
|
+
<th>Author</th>
|
155
|
+
</tr>
|
156
|
+
</thead>
|
157
|
+
<tbody>
|
158
|
+
<tr data-selected-url="/articles/clean_code">
|
159
|
+
<td>1</td>
|
160
|
+
<td>Clean Code</td>
|
161
|
+
<td>Robert C. Martin</td>
|
162
|
+
</tr>
|
163
|
+
<tr data-selected-url="/articles/domain_driven_design">
|
164
|
+
<td>2</td>
|
165
|
+
<td>Domain Driven Design</td>
|
166
|
+
<td>Eric Evans</td>
|
167
|
+
</tr>
|
168
|
+
</tbody>
|
169
|
+
</table>
|
170
|
+
HTML
|
171
|
+
|
172
|
+
before do
|
173
|
+
subject.extend(CornerStones::Table::SelectableRows)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'it includes the "Selected-Link" object in the data' do
|
177
|
+
subject.rows.map do |row|
|
178
|
+
row['Selected-Link']
|
179
|
+
end.must_equal ['/articles/clean_code', '/articles/domain_driven_design']
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'allows you to select a row' do
|
183
|
+
subject.select_row('ID' => '1')
|
184
|
+
current_path.must_equal '/articles/clean_code'
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'integration/spec_helper'
|
2
|
+
|
3
|
+
require 'corner_stones/tabs'
|
4
|
+
require 'corner_stones/tabs/active_tracking'
|
5
|
+
|
6
|
+
describe CornerStones::Tabs do
|
7
|
+
|
8
|
+
given_the_html <<-HTML
|
9
|
+
<ul class="main-tabs-nav">
|
10
|
+
<li class="active"><a href='/main'>Main</a></li>
|
11
|
+
<li><a href='/details'>Details</a></li>
|
12
|
+
<li><a href='/more_stuff'>More Stuff</a></li>
|
13
|
+
</ul>
|
14
|
+
HTML
|
15
|
+
|
16
|
+
subject { CornerStones::Tabs.new('.main-tabs-nav') }
|
17
|
+
|
18
|
+
it 'opens a given tab' do
|
19
|
+
subject.open('Details')
|
20
|
+
current_path.must_equal '/details'
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'mixins' do
|
24
|
+
describe 'active tab tracking' do
|
25
|
+
before do
|
26
|
+
subject.extend(CornerStones::Tabs::ActiveTracking)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'asserts the current tab after opening a new tab' do
|
30
|
+
lambda do
|
31
|
+
subject.open('More Stuff')
|
32
|
+
end.must_raise CornerStones::Tabs::ActiveTracking::ActiveTabMismatchError
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#assert_current_tab_is' do
|
36
|
+
it 'passes when the given tab is active' do
|
37
|
+
subject.assert_current_tab_is('Main')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'fails when the given tab is not active' do
|
41
|
+
lambda do
|
42
|
+
subject.assert_current_tab_is('More Stuff')
|
43
|
+
end.must_raise CornerStones::Tabs::ActiveTracking::ActiveTabMismatchError
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/tasks/test.rake
ADDED
data/travis.yml
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: corner_stones
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta1
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Yves Senn
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capybara
|
16
|
+
requirement: &70273546252500 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70273546252500
|
25
|
+
description: This gem makes it easy to build PageObjects and make your acceptance
|
26
|
+
tests more object oriented. It includes a implementations for common elements like
|
27
|
+
tables, tabs, navigations etc.
|
28
|
+
email:
|
29
|
+
- yves.senn@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- .gitignore
|
35
|
+
- .rvmrc
|
36
|
+
- Gemfile
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- corner_stones.gemspec
|
40
|
+
- lib/corner_stones.rb
|
41
|
+
- lib/corner_stones/all.rb
|
42
|
+
- lib/corner_stones/flash_messages.rb
|
43
|
+
- lib/corner_stones/form.rb
|
44
|
+
- lib/corner_stones/form/with_inline_errors.rb
|
45
|
+
- lib/corner_stones/table.rb
|
46
|
+
- lib/corner_stones/table/deletable_rows.rb
|
47
|
+
- lib/corner_stones/table/selectable_rows.rb
|
48
|
+
- lib/corner_stones/tabs.rb
|
49
|
+
- lib/corner_stones/tabs/active_tracking.rb
|
50
|
+
- lib/corner_stones/version.rb
|
51
|
+
- spec/integration/corner_stones/flash_messages_spec.rb
|
52
|
+
- spec/integration/corner_stones/form_spec.rb
|
53
|
+
- spec/integration/corner_stones/table_spec.rb
|
54
|
+
- spec/integration/corner_stones/tabs_spec.rb
|
55
|
+
- spec/integration/spec_helper.rb
|
56
|
+
- spec/integration/support/response_macros.rb
|
57
|
+
- tasks/test.rake
|
58
|
+
- travis.yml
|
59
|
+
homepage: ''
|
60
|
+
licenses: []
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>'
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.3.1
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project: corner_stones
|
79
|
+
rubygems_version: 1.8.10
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: capybara building blocks for acceptance tests
|
83
|
+
test_files:
|
84
|
+
- spec/integration/corner_stones/flash_messages_spec.rb
|
85
|
+
- spec/integration/corner_stones/form_spec.rb
|
86
|
+
- spec/integration/corner_stones/table_spec.rb
|
87
|
+
- spec/integration/corner_stones/tabs_spec.rb
|
88
|
+
- spec/integration/spec_helper.rb
|
89
|
+
- spec/integration/support/response_macros.rb
|