capybara-page-object 0.5.0 → 0.6.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.
Files changed (52) hide show
  1. data/.travis.yml +5 -0
  2. data/Gemfile +8 -9
  3. data/Gemfile.lock +27 -17
  4. data/README.markdown +32 -12
  5. data/Rakefile +0 -7
  6. data/VERSION +1 -1
  7. data/capybara-page-object.gemspec +41 -34
  8. data/lib/capybara-page-object.rb +12 -11
  9. data/lib/capybara-page-object/collections.rb +4 -0
  10. data/lib/capybara-page-object/component.rb +5 -0
  11. data/lib/{delegators.rb → capybara-page-object/delegators.rb} +0 -0
  12. data/lib/capybara-page-object/element.rb +6 -0
  13. data/lib/capybara-page-object/elements.rb +15 -0
  14. data/lib/capybara-page-object/elements/anchor.rb +6 -0
  15. data/lib/{element → capybara-page-object/elements}/form.rb +8 -16
  16. data/lib/capybara-page-object/elements/form_field.rb +10 -0
  17. data/lib/capybara-page-object/elements/head.rb +7 -0
  18. data/lib/capybara-page-object/elements/image.rb +7 -0
  19. data/lib/{element → capybara-page-object/elements}/input.rb +5 -2
  20. data/lib/{element → capybara-page-object/elements}/list.rb +1 -1
  21. data/lib/{element → capybara-page-object/elements}/list_item.rb +1 -1
  22. data/lib/capybara-page-object/elements/meta.rb +6 -0
  23. data/lib/{element → capybara-page-object/elements}/select.rb +0 -0
  24. data/lib/{element → capybara-page-object/elements}/table.rb +1 -1
  25. data/lib/capybara-page-object/elements/table_header.rb +4 -0
  26. data/lib/{element → capybara-page-object/elements}/table_row.rb +1 -1
  27. data/lib/{element → capybara-page-object/elements}/text_based_input.rb +0 -0
  28. data/lib/{element → capybara-page-object/elements}/textarea.rb +1 -3
  29. data/lib/capybara-page-object/extensions/capybara.rb +7 -0
  30. data/lib/capybara-page-object/extensions/enumerable.rb +10 -0
  31. data/lib/{html5_data.rb → capybara-page-object/html5_data.rb} +0 -0
  32. data/lib/{key_value.rb → capybara-page-object/key_value.rb} +0 -0
  33. data/lib/{node.rb → capybara-page-object/node.rb} +14 -10
  34. data/lib/capybara-page-object/page.rb +77 -0
  35. data/spec/common_spec.rb +6 -6
  36. data/spec/element/anchor_spec.rb +8 -3
  37. data/spec/element/form_field_spec.rb +7 -1
  38. data/spec/element/form_spec.rb +27 -17
  39. data/spec/helper.rb +13 -0
  40. data/spec/node_spec.rb +9 -0
  41. data/spec/page_spec.rb +90 -30
  42. metadata +58 -43
  43. data/lib/collections.rb +0 -17
  44. data/lib/element.rb +0 -14
  45. data/lib/element/TODO.md +0 -11
  46. data/lib/element/anchor.rb +0 -7
  47. data/lib/element/form_field.rb +0 -15
  48. data/lib/element/head.rb +0 -15
  49. data/lib/element/image.rb +0 -15
  50. data/lib/element/meta.rb +0 -11
  51. data/lib/element/table_header.rb +0 -4
  52. data/lib/page.rb +0 -40
@@ -0,0 +1,10 @@
1
+ module CapybaraPageObject
2
+ class FormField < CapybaraPageObject::Element
3
+ field(:key) { source[:name] }
4
+ field(:value) { source.value }
5
+
6
+ def blank?
7
+ value.nil? || '' == value
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module CapybaraPageObject
2
+ class Head < CapybaraPageObject::Element
3
+ field(:title) { find('title').text }
4
+ field(:meta_description) { find('meta[@name="description"]')['content'] }
5
+ field(:meta_keywords) { find('meta[@name="keywords"]')['content'].split(',').collect(&:strip) }
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module CapybaraPageObject
2
+ class Image < CapybaraPageObject::Element
3
+ field(:key) { alt }
4
+ field(:value) { URI(source[:src]) }
5
+ field(:alt){ source[:alt].strip }
6
+ end
7
+ end
@@ -1,5 +1,3 @@
1
- require 'element/text_based_input'
2
-
3
1
  module CapybaraPageObject
4
2
  class Input < CapybaraPageObject::FormField
5
3
  include TextBasedInput
@@ -7,6 +5,11 @@ module CapybaraPageObject
7
5
  BUTTON_TYPES = ['submit', 'reset', 'button']
8
6
  CHECKABLE_TYPES = ['radio', 'checkbox']
9
7
 
8
+ # TODO this doesn't belong here
9
+ def checked?
10
+ source.checked?
11
+ end
12
+
10
13
  def untyped?
11
14
  source[:type].nil?
12
15
  end
@@ -1,5 +1,5 @@
1
1
  module CapybaraPageObject
2
- class List < CapybaraPageObject::Node
2
+ class List < CapybaraPageObject::Element
3
3
  def items(*args)
4
4
  all('li').map { |e| ListItem.new(e) }
5
5
  end
@@ -1,5 +1,5 @@
1
1
  module CapybaraPageObject
2
- class ListItem < CapybaraPageObject::Node
2
+ class ListItem < CapybaraPageObject::Element
3
3
  def text
4
4
  source.text.strip
5
5
  end
@@ -0,0 +1,6 @@
1
+ module CapybaraPageObject
2
+ class Meta < CapybaraPageObject::Element
3
+ field(:key) { source[:name] }
4
+ field(:value) { source[:content] }
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  module CapybaraPageObject
2
- class Table < CapybaraPageObject::Node
2
+ class Table < CapybaraPageObject::Element
3
3
  def rows
4
4
  all('tr').each_with_object({}) do |e, hash|
5
5
  tr = CapybaraPageObject::TableRow.new(e)
@@ -0,0 +1,4 @@
1
+ module CapybaraPageObject
2
+ class TableHeader < CapybaraPageObject::Element
3
+ end
4
+ end
@@ -1,5 +1,5 @@
1
1
  module CapybaraPageObject
2
- class TableRow < CapybaraPageObject::Node
2
+ class TableRow < CapybaraPageObject::Element
3
3
  def header?
4
4
  source.all('th').any?
5
5
  end
@@ -1,7 +1,5 @@
1
- require 'element/text_based_input'
2
-
3
1
  module CapybaraPageObject
4
- class Textarea < CapybaraPageObject::Node
2
+ class Textarea < CapybaraPageObject::Element
5
3
  include TextBasedInput
6
4
  def blank?
7
5
  '' == text
@@ -0,0 +1,7 @@
1
+ module Capybara
2
+ module Node
3
+ class Simple
4
+ include CapybaraPageObject::HTML5Data
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # need to add each_with_object for Ruby 1.8.7
2
+ module Enumerable
3
+ def each_with_object(memo)
4
+ return to_enum :each_with_object, memo unless block_given?
5
+ each do |element|
6
+ yield element, memo
7
+ end
8
+ memo
9
+ end
10
+ end
@@ -1,18 +1,13 @@
1
- require 'collections'
2
- require 'key_value'
3
- require 'html5_data'
4
- require 'delegators'
1
+ require 'capybara-page-object/delegators'
2
+
5
3
 
6
4
  module CapybaraPageObject
7
- class Node < Capybara::Node::Element
5
+ class Node
8
6
  include Delegators
9
- include CapybaraPageObject::Collections
10
- include CapybaraPageObject::HTML5Data
11
7
 
12
8
  attr_accessor :source
13
9
 
14
10
  def initialize(source=nil)
15
- source ||= Capybara.current_session
16
11
  @source = source
17
12
  end
18
13
 
@@ -24,8 +19,17 @@ module CapybaraPageObject
24
19
  classes_list = source[:class] or return []
25
20
  classes_list.split(' ')
26
21
  end
27
- end
28
22
 
29
- class MissingPath < RuntimeError
23
+ def source
24
+ @source ||= Capybara.current_session
25
+ end
26
+
27
+ def self.element(name, &block)
28
+ define_method(name, &block)
29
+ end
30
+
31
+ def self.field(name, &block)
32
+ define_method(name, &block)
33
+ end
30
34
  end
31
35
  end
@@ -0,0 +1,77 @@
1
+ require 'capybara-page-object/node'
2
+ require 'capybara-page-object/collections'
3
+
4
+ module CapybaraPageObject
5
+
6
+ module InstanceMethods
7
+ def path(*args)
8
+ raise MissingPath, "You need to override #path in #{self.class}"
9
+ end
10
+
11
+ def prefix
12
+ '/'
13
+ end
14
+
15
+ def visit_path(attr)
16
+ target = prefix + path
17
+ if attr.kind_of?(String)
18
+ target += attr
19
+ elsif attr.kind_of?(Hash)
20
+ pairs = attr.map { |k, v| "#{k}=#{v}" }
21
+ target += '?' + pairs.join('&') if pairs.any?
22
+ elsif attr != nil
23
+ raise ArgumentError
24
+ end
25
+ source.visit target
26
+ end
27
+ end
28
+
29
+ module ClassMethods
30
+ def from_string(string, target)
31
+ new(Capybara.string(string).find(target))
32
+ end
33
+
34
+ def current?
35
+ page = new
36
+ page.source.current_path == page.prefix + page.path
37
+ end
38
+
39
+ def visit(attr={})
40
+ page = new
41
+ page.visit_path(attr)
42
+ page
43
+ end
44
+ end
45
+
46
+ class Page < CapybaraPageObject::Node
47
+ # TODO why doesn't this work when in the module?
48
+ def tables
49
+ all('table').each_with_object({}) do |e, hash|
50
+ t = CapybaraPageObject::Table.new(e)
51
+ hash[t.key] = t
52
+ end
53
+ end
54
+
55
+ def forms
56
+ all('form').each_with_object({}) do |e, hash|
57
+ f = CapybaraPageObject::Form.new(e)
58
+ hash[f.key] = f
59
+ end
60
+ end
61
+
62
+ def self.path(p)
63
+ define_method(:path) { p }
64
+ end
65
+
66
+ def self.component(name, &block)
67
+ define_method(name, &block)
68
+ end
69
+
70
+ include CapybaraPageObject::Collections
71
+ include CapybaraPageObject::InstanceMethods
72
+ extend CapybaraPageObject::ClassMethods
73
+ end
74
+
75
+ class MissingPath < RuntimeError
76
+ end
77
+ end
data/spec/common_spec.rb CHANGED
@@ -3,20 +3,20 @@ require 'helper'
3
3
  describe "Page" do
4
4
  before do
5
5
  html = File.open(File.dirname(__FILE__) + '/fixtures/node.html').read
6
- @page = CapybaraPageObject::Node.from_string html, 'body'
6
+ @page = CapybaraPageObject::Page.from_string html, 'body'
7
7
  end
8
8
 
9
9
  context "#tables" do
10
10
  it "returns a hash of Tables keyed by ID" do
11
11
  tables = @page.tables
12
- tables.keys.should == ['table_1', 'table_2']
12
+ tables.keys.should =~ ['table_1', 'table_2']
13
13
  tables['table_1'].class.should == CapybaraPageObject::Table
14
14
  end
15
15
  end
16
16
 
17
17
  context "#forms" do
18
- pending "returns a hash of Forms keyed by ID" do
19
- @page.forms.should == ['form_1', 'form_2']
18
+ it "returns a hash of Forms keyed by ID" do
19
+ @page.forms.keys.should =~ ['form_1', 'form_2']
20
20
  @page.forms['form_1'].class.should == CapybaraPageObject::Form
21
21
  end
22
22
  end
@@ -24,13 +24,13 @@ describe "Page" do
24
24
  context "#data" do
25
25
  it "returns an empty hash if the element has no HTML5 data attributes" do
26
26
  html = '<div foo="bar"/>'
27
- @fragment = CapybaraPageObject::Node.from_string html, 'div'
27
+ @fragment = CapybaraPageObject::Element.from_string html, 'div'
28
28
  @fragment.data.should == {}
29
29
  end
30
30
 
31
31
  it "returns a hash of the elements HTML5 data attributes" do
32
32
  html = '<div id="data" data-foo="a" data-bar="b" data-cat>Some data</div>'
33
- @fragment = CapybaraPageObject::Node.from_string html, 'div'
33
+ @fragment = CapybaraPageObject::Element.from_string html, 'div'
34
34
  h = {'foo' => 'a', 'bar' => 'b', 'cat' => ''}
35
35
  @fragment.data.should == h
36
36
  end
@@ -7,9 +7,14 @@ describe "Anchor" do
7
7
  end
8
8
 
9
9
  context "#link" do
10
- it "return a URI object" do
11
- @anchor.link.class.should == URI::Generic
12
- @anchor.link.path.should == 'hello.html'
10
+ it "return the link" do
11
+ @anchor.link.should == 'hello.html'
12
+ end
13
+ end
14
+
15
+ context "#uri" do
16
+ it "return the link as a URI" do
17
+ @anchor.uri.should == URI('hello.html')
13
18
  end
14
19
  end
15
20
  end
@@ -2,11 +2,17 @@ require 'helper'
2
2
 
3
3
  describe "FormField" do
4
4
  before do
5
- @input = CapybaraPageObject::FormField.from_string '<input value="hello">', 'input'
5
+ @input = CapybaraPageObject::FormField.from_string '<input name="greeting" value="hello">', 'input'
6
6
  @textarea = CapybaraPageObject::FormField.from_string '<textarea>foo</textarea>', 'textarea'
7
7
  @blank_textarea = CapybaraPageObject::FormField.from_string '<textarea></textarea>', 'textarea'
8
8
  end
9
9
 
10
+ context "#key" do
11
+ it "returns the name of the form field" do
12
+ @input.key.should == 'greeting'
13
+ end
14
+ end
15
+
10
16
  context "#value" do
11
17
  it "delegates" do
12
18
  @input.value.should == 'hello'
@@ -42,23 +42,33 @@ describe "Form" do
42
42
  end
43
43
  end
44
44
 
45
- # context "#fields" do
46
- # it "return the fields contained in the form - inputs, selects and textareas. Excludes button type inputs (submit, reset, etc.)" do
47
- # f = {
48
- # 'input_1' => 'value_1',
49
- # 'text_input' => 'text_input_value',
50
- # 'password_input' => 'password',
51
- # 'colour' => 'blue',
52
- # 'essay' => 'hello world',
53
- # 'checkbox_1' => false,
54
- # 'checkbox_2' => true,
55
- # 'radio_button_1' => false,
56
- # 'radio_button_2' => true,
57
- # 'countries' => ['spain', 'germany']
58
- # }
59
- # @form.fields.should == f
60
- # end
61
- # end
45
+ context "#inputs" do
46
+ it "returns the inputs on the page (excluding buttons)" do
47
+ @form.inputs.keys.should == ["input_1", "text_input", "password_input", "checkbox_1",
48
+ "checkbox_2", "radio_button_1", "radio_button_2"]
49
+ end
50
+ end
51
+
52
+ context "#textareas" do
53
+ it "returns the textareas on the page" do
54
+ @form.textareas.keys.should == ["essay"]
55
+ end
56
+ end
57
+
58
+ context "#selects" do
59
+ it "returns the selects on the page" do
60
+ @form.selects.keys.should == ["colour", "countries"]
61
+ end
62
+ end
63
+
64
+ context "#fields" do
65
+ it "merges inputs, textareas and selects" do
66
+ @form.stub!(:inputs => {'a' => 1})
67
+ @form.stub!(:textareas => {'b' => 2})
68
+ @form.stub!(:selects => {'c' => 3})
69
+ @form.fields.should == {'a' => 1, 'b' => 2, 'c' => 3}
70
+ end
71
+ end
62
72
 
63
73
  # it "provides direct read access to form's fields" do
64
74
  # @form.essay.should == 'hello world'
data/spec/helper.rb CHANGED
@@ -12,4 +12,17 @@ require 'rspec'
12
12
 
13
13
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
14
  $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+
16
+ unless ENV["SKIP_COVERAGE"]
17
+ require 'simplecov'
18
+
19
+ SimpleCov.start
20
+ SimpleCov.at_exit do
21
+ SimpleCov.result.format!
22
+ if SimpleCov.result.covered_percent < 100
23
+ `open coverage/index.html`
24
+ end
25
+ end
26
+ end
27
+
15
28
  require 'capybara-page-object'
data/spec/node_spec.rb CHANGED
@@ -18,5 +18,14 @@ describe "Page" do
18
18
  anchor = CapybaraPageObject::Anchor.from_string '<a/>', 'a'
19
19
  anchor.classes.should be_empty
20
20
  end
21
+
22
+ it "has a nice DSL" do
23
+ source = mock
24
+ source.should_receive(:find_button).with('place_order')
25
+ class Bar < CapybaraPageObject::Node
26
+ element(:place_order) { source.find_button('place_order') }
27
+ end
28
+ Bar.new(source).place_order
29
+ end
21
30
  end
22
31
  end
data/spec/page_spec.rb CHANGED
@@ -1,37 +1,97 @@
1
- class FooPage < CapybaraPageObject::Page
2
- def path
3
- '/foo'
1
+ require 'active_support/ordered_hash'
2
+ require File.dirname(__FILE__) + '/../lib/capybara-page-object/page'
3
+
4
+ describe "a class which extends CapybaraPageObject::Page" do
5
+
6
+ before do
7
+ class FooShow < CapybaraPageObject::Page
8
+ def path
9
+ 'foos/'
10
+ end
11
+ end
12
+ class FooIndex < CapybaraPageObject::Page
13
+ def path
14
+ 'foos'
15
+ end
16
+ end
4
17
  end
5
- end
6
18
 
7
- describe "Page" do
8
- context "#new" do
9
- it "visits the appropriate when initialised" do
10
- mock_source = mock()
11
- mock_source.should_receive(:current_path)
12
- mock_source.should_receive(:visit).with('/foo')
13
- @page = FooPage.new(nil, mock_source)
14
- end
15
-
16
- it "supports key-value pairs" do
17
- mock_source = mock()
18
- mock_source.should_receive(:current_path)
19
- mock_source.should_receive(:visit).with('/foo?a=1&b=2')
20
- # intential mix of string and symbol keys below
21
- @page = FooPage.new({'a' => 1, :b => 2}, mock_source)
22
- end
23
-
24
- it "raises an exception if revisiting" do
25
- s = stub('Capybara', :current_path => '/foo', :visit => nil)
26
- lambda do
27
- @page = FooPage.new(nil, s)
28
- end.should raise_error(RuntimeError, 'Detected repeat of page load - use #reload instead')
19
+ context ".current?" do
20
+ it "is true if the path matches the current session path" do
21
+ Capybara.stub :current_session => mock(:current_path => '/foos')
22
+ FooIndex.should be_current
23
+ end
24
+
25
+ it "is false if the path doesn't match the current session path" do
26
+ Capybara.stub :current_session => mock(:current_path => '/foos/1')
27
+ FooIndex.should_not be_current
29
28
  end
30
29
  end
31
-
32
- context "#refresh" do
33
- pending "revisits the page" do
34
- # TODO implemented but untested
30
+
31
+ context ".visit" do
32
+ it "visits the appropriate path" do
33
+ session = mock
34
+ session.should_receive(:visit).with('/foos')
35
+ Capybara.stub :current_session => session
36
+ FooIndex.visit
37
+ end
38
+
39
+ it "throws an error if path hasn't been overridden" do
40
+ lambda do
41
+ CapybaraPageObject::Page.visit
42
+ end.should raise_error(CapybaraPageObject::MissingPath, 'You need to override #path in CapybaraPageObject::Page')
43
+ end
44
+
45
+ it "converts a supplied hash into querystring paramaters" do
46
+ session = mock
47
+ session.should_receive(:visit).with('/foos?a=1&b=2')
48
+ Capybara.stub :current_session => session
49
+ # using an OrderedHash so we can guarantee the order on Ruby 1.8
50
+ # also, the mix of string and symbol keys is intentional
51
+ # for some reason, passing in the hash to the initializer fails when
52
+ # running via Rake
53
+ key_values = ActiveSupport::OrderedHash.new
54
+ key_values['a'] = 1
55
+ key_values[:b] = 2
56
+ @page = FooIndex.visit key_values
57
+ end
58
+
59
+ it "converts a supplied string into a resource identifier" do
60
+ session = mock()
61
+ session.should_receive(:visit).with('/foos/bar')
62
+ Capybara.stub :current_session => session
63
+ @page = FooShow.visit 'bar'
64
+ end
65
+
66
+ it "returns an instance of the page object" do
67
+ Capybara.stub :current_session => stub.as_null_object
68
+ FooIndex.visit.class.should == FooIndex
69
+ end
70
+
71
+ it "throws an error if the supplied argument is something other a string or hash" do
72
+ lambda do
73
+ FooIndex.visit Object
74
+ end.should raise_error ArgumentError
75
+ end
76
+
77
+ it "has a DSL for setting the path" do
78
+ class Blah < CapybaraPageObject::Page
79
+ path 'xyz'
80
+ end
81
+ session = mock()
82
+ session.should_receive(:visit).with('/xyz')
83
+ Capybara.stub :current_session => session
84
+ @page = Blah.visit
85
+ end
86
+
87
+ it "has a DSL for setting components" do
88
+ class Blah < CapybaraPageObject::Page
89
+ component(:navigation) { source.find('#navigation') }
90
+ end
91
+ session = mock
92
+ session.should_receive(:find).with('#navigation')
93
+ Capybara.stub :current_session => session
94
+ Blah.new.navigation
35
95
  end
36
96
  end
37
97
  end