corner_stones 0.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/senny/corner_stones.png)](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
|