capybara-page-object 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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