Skip to content

Commit

Permalink
Merge pull request #21 from senny/table_row_refactoring
Browse files Browse the repository at this point in the history
table row refactoring
  • Loading branch information
senny committed Sep 17, 2012
2 parents 4e00f31 + 54cb05d commit f90db66
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 52 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -49,17 +49,22 @@ flash.assert_flash_is_present(:notice, 'Article saved') # verify that a given fl


```ruby ```ruby
table = CornerStones::Table.new('.articles') table = CornerStones::Table.new('.articles')
table.rows # returns an array of rows. Each row is represented as a Hash {header} => {value} table.rows # returns an array of rows. Each row is represented as a row object.
table.row('Title' => 'Management') # returns the row-hash for the row with 'Management' in the 'Title' column table.row('Title' => 'Management') # returns the row object for the row with 'Management' in the 'Title' column
``` ```


A row object has two primary methods: `#node` is a reference to the capybara node of the row and `#attributes` is a hash
with the following structure: ({Table Header} => {Cell Value})

The following extensions are available for the `Table`:

```ruby ```ruby
table = CornerStones::Table.new('.articles').tap do |t| table = CornerStones::Table.new('.articles').tap do |t|
t.extend(CornerStones::Table::SelectableRows) t.extend(CornerStones::Table::SelectableRows)
t.extend(CornerStones::Table::DeletableRows) t.extend(CornerStones::Table::DeletableRows)
end end
table.select_row('Created at' => '01.12.2001') # select the row, which has '01.12.2001' in the 'Created at' column table.row('Created at' => '01.12.2001').select # select the row, which has '01.12.2001' in the 'Created at' column
table.delete_row('ID' => '9') # delete the row, which contains '9' in the 'ID' column table.row('ID' => '9').delete # delete the row, which contains '9' in the 'ID' column
``` ```


### Forms ### Forms
Expand Down
16 changes: 13 additions & 3 deletions lib/corner_stones/table.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,19 +18,24 @@ def initialize(scope, options = {})


def row(options) def row(options)
rows.detect { |row| rows.detect { |row|
identity = row.select { |key, value| options.has_key?(key) } identity = row.attributes.select { |key, value| options.has_key?(key) }
identity == options identity == options
} or raise MissingRowError, "no row with '#{options.inspect}'\n\ngot:#{rows}" } or raise MissingRowError, "no row with '#{options.inspect}'\n\ngot:#{rows}"
end end


def rows def rows
within @scope do within @scope do
all('tbody tr').map do |row| all('tbody tr').map do |row|
attributes_for_row(row) build_row(row)
end end
end end
end end


def build_row(node)
Row.new(node, attributes_for_row(node))
end
protected :build_row

def headers def headers
@options[:headers] || detect_table_headers @options[:headers] || detect_table_headers
end end
Expand All @@ -44,7 +49,6 @@ def attributes_for_row(row)
headers.each.with_index.with_object(row_data) do |(header, index), row_data| headers.each.with_index.with_object(row_data) do |(header, index), row_data|
augment_row_with_cell(row_data, row, index, header) augment_row_with_cell(row_data, row, index, header)
end end
row_data['Row-Element'] = row
row_data row_data
end end


Expand All @@ -57,6 +61,12 @@ def augment_row_with_cell(row_data, row, index, header)
def value_for_cell(cell) def value_for_cell(cell)
cell.text unless cell.nil? cell.text unless cell.nil?
end end

Row = Struct.new(:node, :attributes) do
def [](key)
attributes[key]
end
end
end end


end end
25 changes: 17 additions & 8 deletions lib/corner_stones/table/deletable_rows.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@ class Table
module DeletableRows module DeletableRows


def delete_row(options) def delete_row(options)
row = row(options) warn "[DEPRECATION] `delete_row` is deprecated. Please use `row(row_spec).delete` instead."
if row['Delete-Link'] row(options).delete
row['Delete-Link'].click end
else
raise "The row matching '#{options}' does not have a delete-link" def build_row(node)
end row = super
row.extend RowMethods
row
end end


def attributes_for_row(row) module RowMethods
super.merge('Delete-Link' => row.first('td .delete-action')) def delete
delete_link = node.first('td .delete-action')
if delete_link
delete_link.click
else
raise "The row '#{attributes}' does not have a delete-link"
end
end
end end


end end
Expand Down
15 changes: 12 additions & 3 deletions lib/corner_stones/table/selectable_rows.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ class Table
module SelectableRows module SelectableRows


def select_row(options) def select_row(options)
visit row(options)['Selected-Link'] warn "[DEPRECATION] `select_row` is deprecated. Please use `row(row_spec).select` instead."
row(options).select
end end


def attributes_for_row(row) def build_row(node)
super.merge('Selected-Link' => row['data-selected-url']) row = super
row.extend RowMethods
row
end

module RowMethods
def select
visit node['data-selected-url']
end
end end


end end
Expand Down
2 changes: 1 addition & 1 deletion spec/integration/corner_stones/table_form_spec.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
it 'ignores empty cells' do it 'ignores empty cells' do
expected_data = [{'Title' => 'Indiana Jones', 'Duration' => '210 minutes', 'Time' => nil}] expected_data = [{'Title' => 'Indiana Jones', 'Duration' => '210 minutes', 'Time' => nil}]
subject.rows.map {|r| subject.rows.map {|r|
r.reject {|key, value| ['Row-Element', 'Inputs'].include? key} r.attributes.reject {|key, _value| !expected_data.first.has_key?(key)}
}.must_equal(expected_data) }.must_equal(expected_data)
end end
end end
Expand Down
73 changes: 40 additions & 33 deletions spec/integration/corner_stones/table_spec.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -51,26 +51,20 @@
{ 'ID' => '2', 'Title' => 'Domain Driven Design', 'Author' => 'Eric Evans'}] { 'ID' => '2', 'Title' => 'Domain Driven Design', 'Author' => 'Eric Evans'}]


subject.rows.map {|r| subject.rows.map {|r|
r.each do |k, v| r.attributes.reject do |key, _value|
r.delete(k) unless expected_data.first.has_key?(k) !expected_data.first.has_key?(key)
end end
}.must_equal(expected_data) }.must_equal(expected_data)
end end


it 'a row can be accessed with a single key' do it 'a row can be accessed with a single key' do
expected_data = { 'ID' => '2', 'Title' => 'Domain Driven Design', 'Author' => 'Eric Evans' }
actual = subject.row('Title' => 'Domain Driven Design') actual = subject.row('Title' => 'Domain Driven Design')

actual['Author'].must_equal('Eric Evans')
actual.each {|k, v| actual.delete(k) unless expected_data.has_key?(k)}
actual.must_equal(expected_data)
end end


it 'a row can be accessed with multiple keys' do it 'a row can be accessed with multiple keys' do
expected_data = {'ID' => '1', 'Title' => 'Clean Code', 'Author' => 'Robert C. Martin'}

actual = subject.row('ID' => '1', 'Author' => 'Robert C. Martin') actual = subject.row('ID' => '1', 'Author' => 'Robert C. Martin')
actual.each {|k, v| actual.delete(k) unless expected_data.has_key?(k)} actual['Title'].must_equal('Clean Code')
actual.must_equal(expected_data)
end end


it 'It raises an Exception when no Row was found' do it 'It raises an Exception when no Row was found' do
Expand All @@ -80,7 +74,7 @@
end end


it 'extracts the Capybara-Element for the table row' do it 'extracts the Capybara-Element for the table row' do
subject.row('ID' => '1')['Row-Element'].path.must_equal('/html/body/table/tbody/tr[1]') subject.row('ID' => '1').node.path.must_equal('/html/body/table/tbody/tr[1]')
end end
end end


Expand Down Expand Up @@ -110,8 +104,8 @@
{ 'Book' => 'Domain Driven Design', { 'Book' => 'Domain Driven Design',
'Author' => 'Eric Evans'}] 'Author' => 'Eric Evans'}]
subject.rows.map {|r| subject.rows.map {|r|
r.each do |k, v| r.attributes.reject do |key, _value|
r.delete(k) unless expected_data.first.has_key?(k) !expected_data.first.has_key?(key)
end end
}.must_equal(expected_data) }.must_equal(expected_data)
end end
Expand Down Expand Up @@ -141,9 +135,10 @@


it 'ignores empty cells' do it 'ignores empty cells' do
expected_data = [{'ID' => '1', 'Title' => 'Clean Code', 'Author' => nil}] expected_data = [{'ID' => '1', 'Title' => 'Clean Code', 'Author' => nil}]
subject.rows.map {|r| actual = subject.rows
r.reject {|key, value| key == 'Row-Element'}
}.must_equal(expected_data) actual = actual.map {|row| row.attributes.reject {|key, _value| !expected_data.first.has_key?(key)}}
actual.must_equal(expected_data)
end end
end end


Expand Down Expand Up @@ -184,24 +179,31 @@
subject.extend(CornerStones::Table::DeletableRows) subject.extend(CornerStones::Table::DeletableRows)
end end


it 'it includes the "Delete-Link" object in the data' do it 'allows you to trigger a deletion with a row selector' do
subject.rows.each do |row| subject.row('Title' => 'Domain Driven Design').delete
unless row['Delete-Link'].nil? current_path.must_equal '/delete/domain_driven_design'
row['Delete-Link'].must_be_kind_of(Capybara::Node::Element) end
end
it 'raises an error when a the target row can not be found' do
begin
subject.row('ID' => '3').delete
rescue => e
e.message.must_match /^The row '.*' does not have a delete-link$/
end end
end end


it 'allows you to trigger a deletion with a row selector' do it 'the deprecated method #delete_row still works' do
errors = StringIO.new
original_stderr = $stderr
$stderr = errors

subject.delete_row('Title' => 'Domain Driven Design') subject.delete_row('Title' => 'Domain Driven Design')

$stderr = original_stderr
current_path.must_equal '/delete/domain_driven_design' current_path.must_equal '/delete/domain_driven_design'
errors.string.must_equal "[DEPRECATION] `delete_row` is deprecated. Please use `row(row_spec).delete` instead.\n"
end end


it 'raises an error when a the target row can not be found' do
lambda do
subject.delete_row('ID' => '3')
end.must_raise(RuntimeError)
end
end end


describe 'selectable rows' do describe 'selectable rows' do
Expand Down Expand Up @@ -234,15 +236,21 @@
subject.extend(CornerStones::Table::SelectableRows) subject.extend(CornerStones::Table::SelectableRows)
end end


it 'it includes the "Selected-Link" object in the data' do it 'allows you to select a row' do
subject.rows.map do |row| subject.row('ID' => '1').select
row['Selected-Link'] current_path.must_equal '/articles/clean_code'
end.must_equal ['/articles/clean_code', '/articles/domain_driven_design']
end end


it 'allows you to select a row' do it 'the deprecated method #select_row still works' do
errors = StringIO.new
original_stderr = $stderr
$stderr = errors

subject.select_row('ID' => '1') subject.select_row('ID' => '1')

$stderr = original_stderr
current_path.must_equal '/articles/clean_code' current_path.must_equal '/articles/clean_code'
errors.string.must_equal "[DEPRECATION] `select_row` is deprecated. Please use `row(row_spec).select` instead.\n"
end end
end end


Expand Down Expand Up @@ -276,7 +284,6 @@
it 'strips whitespace from cell content' do it 'strips whitespace from cell content' do
subject.rows.map { |r| r['Author']}.must_equal ["Robert C. Martin", "Eric Evans"] subject.rows.map { |r| r['Author']}.must_equal ["Robert C. Martin", "Eric Evans"]
end end

end end
end end
end end

0 comments on commit f90db66

Please sign in to comment.