domkey 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e084e3b9ec1fd8d799b054c5c7621ff614cbe25
4
- data.tar.gz: cc50953f3c169a28d21346e076681fb7e3dcc099
3
+ metadata.gz: 45e6ac05a48d93eede629f82f2a5b04b14037f62
4
+ data.tar.gz: 9c3b5d281b8b000852489e1933de6b855b2975e3
5
5
  SHA512:
6
- metadata.gz: 708a161af1039a64b5beed49e5b8b3ea354387624ceae4fa810f9c52993aa21fb41b493778f6bd816a75a751a55ba060d2b86c4d743ff75fbdd58e4dece13aa8
7
- data.tar.gz: 49e6a0ff0391b95355d136dcc44d9fae71acf7413829ea52cf3e0008d16a11de06d002a714071558b0f31a22bfe487a1f11289523e928f3f600c06238c70d095
6
+ metadata.gz: aa5af099b7354c11538d238e9c4a59eb91cf41a7f7845fe10bd92659af309e7e65ebf1630d722952e17a39ca1a0e9a790e955a00d91df35a074ea9de9b83d3d1
7
+ data.tar.gz: 2fe75c7ca69ba9f0656491cea2a358a048f815b5da77bc7d614425c4a4a5a353b653364b4f13b522e2a366de699744389e3244dcde80822a7ed9fd6ab727fe56
@@ -0,0 +1,3 @@
1
+ [submodule "spec/watirspec"]
2
+ path = spec/watirspec
3
+ url = git://github.com/watir/watirspec.git
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
- # Domain Specifc PageObject for Selenium Watir-Webdriver
1
+ # Domain Specific PageObject for Selenium Watir-Webdriver
2
2
 
3
- PageObject that models business domain first and browser code second.
4
-
5
- Watir-Webdriver is the Bee's Knees! Now with Domain Specific PageObject Factory!
3
+ PageObject that models specific domain first and browser code second.
6
4
 
7
5
  Watir-Webdriver is the Bee's Knees! Now with Domain Specific PageObject Factory!
8
6
 
data/Rakefile CHANGED
@@ -1,7 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new(:spec) do |spec|
5
+ # Do not include watirspec
6
+ spec.pattern = 'spec/*_spec.rb'
7
+ end
5
8
 
6
9
  task :default => :spec
7
10
 
@@ -1,3 +1,3 @@
1
1
  module Domkey
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,5 +1,9 @@
1
1
  require 'domkey/view/page_object'
2
2
  require 'domkey/view/page_object_collection'
3
+ require 'domkey/view/radio_group'
4
+ require 'domkey/view/checkbox_group'
5
+ require 'domkey/view/cargo'
6
+
3
7
  module Domkey
4
8
 
5
9
  module View
@@ -7,18 +11,25 @@ module Domkey
7
11
  module ClassMethods
8
12
 
9
13
  # PageObjectCollection factory
10
- def doms(key, &watirproc)
14
+ def doms(key, &package)
11
15
  send :define_method, key do
12
- PageObjectCollection.new watirproc, Proc.new { browser }
16
+ PageObjectCollection.new package, Proc.new { browser }
13
17
  end
14
18
  end
15
19
 
16
20
  # PageObject factory
17
- def dom(key, &watirproc)
21
+ def dom(key, &package)
18
22
  send :define_method, key do
19
- PageObject.new watirproc, Proc.new { browser }
23
+ PageObject.new package, Proc.new { browser }
20
24
  end
21
25
  end
26
+
27
+ # build Cargo with model and view
28
+ # @return [Domkey::View::Cargo]
29
+ def cargo model, browser: nil
30
+ Cargo.new model: model, view: self.new(browser)
31
+ end
32
+
22
33
  end
23
34
 
24
35
  def self.included(klass)
@@ -27,13 +38,40 @@ module Domkey
27
38
 
28
39
  attr_accessor :browser
29
40
 
41
+ # @param [Watir::Browser] (false)
30
42
  def initialize browser=nil
31
43
  @browser = browser
32
44
  end
33
45
 
46
+ # browser for this view.
47
+ # if View was initialized without a browser then default Domkey.browser is provided
48
+ # @return [Watir::Browser]
34
49
  def browser
35
50
  @browser ||= Domkey.browser
36
51
  end
37
52
 
53
+ def set value
54
+ Cargo.new(model: value, view: self).set
55
+ end
56
+
57
+ # @param [Hash{Symbol => Object}] view data where Symbol is semantic descriptor for a pageobject in the view
58
+ # @param [Symbol] for one pageobject
59
+ # @param [Array<Symbol>] for array of pageobjects
60
+ #
61
+ # @return [Hash{Symbol => Object}] data from the view
62
+ def value value
63
+ # transform value into hash
64
+ value = case value
65
+ when Symbol
66
+ {value => nil}
67
+ when Array
68
+ #array of symbols
69
+ Hash[value.map { |v| [v, nil] }]
70
+ when Hash
71
+ value
72
+ end
73
+ Cargo.new(model: value, view: self).value
74
+ end
75
+
38
76
  end
39
77
  end
@@ -0,0 +1,92 @@
1
+ module Domkey
2
+
3
+ module View
4
+
5
+ # Data shipping between Model and View
6
+ # Sends data to view. Grabs data from view
7
+ #
8
+ # For specialized transfer object a client would sublcass this Cargo,
9
+ # by default View.cargo factory method is provided for regular data transfer object
10
+ #
11
+ # @usage
12
+ #
13
+ # class MyView
14
+ # include Domkey::View
15
+ #
16
+ # dom(:city) { text_field(id: 'city2') }
17
+ #
18
+ # def fruit
19
+ # RadioGroup.new -> { checkboxes(name: 'fruit') }
20
+ # end
21
+ # end
22
+ #
23
+ # model = {city: 'Austin', fruit: 'tomato'}
24
+ # cargo = MyView.cargo model
25
+ # cargo.set #=> sets view.city with model[:city] and view.fruit with model[:fruit]
26
+ # cargo.value #=> returns {city: 'Austing', fruit: 'tomato'}
27
+ #
28
+ # class MyCargo < Domkey::View::Cargo
29
+ #
30
+ # end
31
+ #
32
+ # model = {city: 'Mordor'}
33
+ # view = MyView.new
34
+ # cargo = MyCargo.new view: view, model: model
35
+ # cargo.set
36
+ #
37
+ class Cargo
38
+
39
+ attr_accessor :view, :model
40
+
41
+ def initialize model: nil, view: nil
42
+ @model = model
43
+ @view = view
44
+ end
45
+
46
+ # set each pageobject in the view with the value from the model
47
+ def set
48
+ @model.each_pair do |key, value|
49
+ set_pageobject key, value
50
+ end
51
+ self
52
+ end
53
+
54
+ # extracts value for each pageobject identified by the model
55
+ # @returns [Model]
56
+ def value
57
+ extracted = {}
58
+ @model.each_pair do |key, value|
59
+ extracted[key] = value_for_pageobject(key, value)
60
+ end
61
+ extracted
62
+ end
63
+
64
+ private
65
+
66
+ def set_pageobject key, value
67
+ @view.send(key).set value
68
+ end
69
+
70
+ def value_for_pageobject key, value
71
+ object = @view.send(key)
72
+ # object is pageobject
73
+ if object.method(:value).parameters.empty?
74
+ object.value
75
+ else
76
+ # object is another view that has collection of pageobject
77
+ object.value value
78
+ end
79
+ end
80
+
81
+ ## submits view
82
+ #def submit
83
+ #
84
+ #end
85
+ #
86
+ ## is view ready?
87
+ #def ready?
88
+ #
89
+ #end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,75 @@
1
+ require 'domkey/view/labeled_group'
2
+ module Domkey
3
+
4
+ module View
5
+
6
+ # Acts like OptionSelectable object
7
+ # CheckboxGroup allows you to interact with PageObjectCollection of checkboxes as a single PageObject.
8
+ # Checkboxes collection is constrained by the same name attribute and acts like on object.
9
+ # It behaves like a Multi Select list.
10
+ # It can none, one or more options selected
11
+ class CheckboxGroup < PageObjectCollection
12
+
13
+ # clears all options and sets only the desired value(s)
14
+ # @param [String, Regexp] find value attribute or match value and set that checkbox
15
+ # @param [Array<String, Regexp>] find each value attribute and set each checkbox
16
+ # @param [False] uncheck any checked checkboxes
17
+ def set value
18
+ validate_scope
19
+ element.each { |e| e.clear }
20
+ return unless value
21
+ [*value].each do |v|
22
+ e = case v
23
+ when String
24
+ element.find { |e| e.value == v }
25
+ when Regexp
26
+ element.find { |e| e.value.match(v) }
27
+ end
28
+ e ? e.set : fail(Exception::Error, "Checkbox to be set not found by value: #{v.inspect}")
29
+ end
30
+ end
31
+
32
+ # @return [Array<String>] value attributes of each checked checkbox
33
+ def value
34
+ validate_scope
35
+ element.find_all { |e| e.set? }.map { |e| e.value }
36
+ end
37
+
38
+ def options
39
+ validate_scope
40
+ element.map { |e| e.value }
41
+ end
42
+
43
+ # convert to LabeledGroup settable by corresponding label text
44
+ def to_labeled
45
+ LabeledGroup.new(self)
46
+ end
47
+
48
+ # @yield [PageObject]
49
+ def each(&blk)
50
+ validate_scope
51
+ super(&blk)
52
+ end
53
+
54
+ # @return [Array<PageObject>]
55
+ def to_a
56
+ validate_scope
57
+ super
58
+ end
59
+
60
+ private
61
+
62
+ # precondition on acting on this collection
63
+ # @return [true] when all checkboxes in collection share the same name attribute
64
+ # @raise [Exception::Error] when where is more than one unique name attribute
65
+ # --
66
+ # returns true on subsequent unless magically more radios show up after initial validation
67
+ def validate_scope
68
+ return if @validated
69
+ groups = element.map { |e| e.name }.uniq
70
+ fail(Exception::Error, "CheckboxGroup definition scope too broad: Found #{groups.count} checkbox groups with names: #{groups}") unless (groups.size == 1)
71
+ @validated = true
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,18 @@
1
+ module Domkey
2
+
3
+ module View
4
+
5
+ # return collection of PageObjects for label locators corresponding to id of each element in a collection
6
+ class LabelMapper
7
+ # @param [Array<PageObject>]
8
+ # @param [PageObjectCollection]
9
+ # @return [Array<PageObject>] where each PageObject is a locator for label for an id of a PageObject passed in parameters
10
+ def self.for collection
11
+ collection.map do |e|
12
+ PageObject.new -> { label(for: e.element.id) }, e.container
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,50 @@
1
+ require 'delegate'
2
+ require 'domkey/view/label_mapper'
3
+ module Domkey
4
+
5
+ module View
6
+
7
+ # Interfact to CheckboxGroup and RadioGroup elements through corresponding label elements;
8
+ # radio and checkbox controls form a group by name attribute
9
+ # however they don't have visible text indicators to the user who is looking at the page.
10
+ # The common strategy is to provide a lable element such that
11
+ # its for: attribute value maps to id: attribute value of an individual control in a group.
12
+ # The labels become visual indicators for the user. Clicking corresponding lable activates the control.
13
+ class LabeledGroup < SimpleDelegator
14
+
15
+ def initialize group
16
+ __setobj__(group)
17
+ end
18
+
19
+ # @param value [String] a label text to set a corresponding element referenced
20
+ # @param value [Array<String>] one or more labels
21
+ def set value
22
+ __getobj__.set false
23
+ labels = self.options
24
+ indices = [*value].map do |what|
25
+ i = case what
26
+ when String
27
+ labels.index(what)
28
+ when Regexp
29
+ labels.index(labels.find { |e| e.match(what) })
30
+ end
31
+ i ? i : fail(Exception::Error, "Label text to set not found for value: #{what.inspect}")
32
+ end
33
+ indices.each do |i|
34
+ __getobj__[i].element.set
35
+ end
36
+ end
37
+
38
+ # @return [Array<String>] label texts for selected elements
39
+ def value
40
+ selected_ones = __getobj__.find_all { |e| e.element.set? }
41
+ LabelMapper.for(selected_ones).map { |e| e.element.text }
42
+ end
43
+
44
+ # @return [Array<String>] label texts for all elements in a group
45
+ def options
46
+ LabelMapper.for(__getobj__).map { |e| e.element.text }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,120 +1,120 @@
1
+ require 'domkey/view/widgetry_package'
2
+ require 'domkey/view/watir_widget'
3
+
1
4
  module Domkey
2
5
 
3
6
  module View
4
7
 
8
+ # PageObject represents an semantically essential area in a View
9
+ # It is an object that responds to set and value as the main way of sending data to it.
10
+ # it is composed of one or more watir elements.
11
+ # PageObject encapuslates the widgetry of DOM elements to provide semantic interfact to the user of the widgetry
12
+ #
13
+ # Compose PageObject with package and container
14
+ #
15
+ # What is a container? it's a proc, a callable object that plays a role of a container for package widgetry
16
+ # container can be one of:
17
+ # - browser (default)
18
+ # - a pageobject
19
+ #
20
+ # What is package? it's a proc of DOM elements widgetry that can be found inside the container
21
+ # package can be one of the following:
22
+ # - definition of single watir element i.e. `-> { text_field(:id, 'foo')}`
23
+ # - a pageobject i.e. previously instantiated definition
24
+ # - hash where key defines subelement and value a definition or pageobject
25
+ #
26
+ # Usage:
27
+ # Clients would not usually instantate this class.
28
+ # A client class which acts as a View would use a :dom factory method to create PageObjects
29
+ # Example:
30
+ #
31
+ # class MyView
32
+ # include Domkey::View
33
+ #
34
+ # dom(:headline) { text_field(id:, 'some_desc_text') }
35
+ #
36
+ # def property
37
+ # PropertyPanel.new browser.div(id: 'container')
38
+ # end
39
+ # end
40
+ #
41
+ # class PropertyPanel
42
+ # include Domkey::View
43
+ # dom(:headline) { text_field(class: 'headline_for_house') }
44
+ # end
45
+ #
46
+ # view = MyView.new
47
+ # view.headline.set 'HomeAway Rules!'
48
+ # view.value #=> returns 'HomeAway Rules!'
49
+ # view.property.headline.set 'Awesome Vactaion Home'
50
+ # view.property.headline.value #=> returns 'Awesome Vaction Home'
51
+ #
5
52
  class PageObject
6
53
 
7
- attr_accessor :watirproc, :container
8
-
9
- # PageObject represents an semantically essential area in a View
10
- # It is an object that responds to set and value as the main way of sending data to it.
11
- # it is composed of one or more watir elements.
12
- # PageObject encapuslates the widgetry of DOM elements to provide semantic interfact to the user of the widgetry
13
- #
14
- # Compose PageObject with watirproc and container
15
- #
16
- # What is a container? it's a proc, a callable object that plays a role of a container for watirproc widgetry
17
- # container can be one of:
18
- # - browser (default)
19
- # - a pageobject
20
- #
21
- # What is watirproc? it's a proc of DOM elements widgetry that can be found inside the container
22
- # watirproc can be one of the following:
23
- # - definition of single watir element i.e. `-> { text_field(:id, 'foo')}`
24
- # - a pageobject i.e. previously instantiated definition
25
- # - hash where key defines subelement and value a definition or pageobject
26
- #
27
- # Usage:
28
- # Clients would not usually instantate this class.
29
- # A client class which acts as a View would use a :dom factory method to create PageObjects
30
- # Example:
31
- #
32
- # class MyView
33
- # include Domkey::View
34
- #
35
- # dom(:headline) { text_field(id:, 'some_desc_text') }
36
- #
37
- # def property
38
- # PropertyPanel.new browser.div(id: 'container')
39
- # end
40
- # end
41
- #
42
- # class PropertyPanel
43
- # include Domkey::View
44
- # dom(:headline) { text_field(class: 'headline_for_house') }
45
- # end
46
- #
47
- # view = MyView.new
48
- # view.headline.set 'HomeAway Rules!'
49
- # view.value #=> returns 'HomeAway Rules!'
50
- # view.property.headline.set 'Awesome Vactaion Home'
51
- # view.property.headline.value #=> returns 'Awesome Vaction Home'
52
- #
53
- def initialize watirproc, container=lambda { Domkey.browser }
54
- @container = container
55
- @watirproc = initialize_this watirproc
56
- end
54
+ # @api private
55
+ include WidgetryPackage
57
56
 
57
+ # Each Semantic PageObject defines what value means for itself
58
+ # @param [SemanticValue] Delegated to WebdriverElement and we expect it to respond to set
59
+ # @parma [Hash{Symbol => SemanticValue}]
58
60
  def set value
59
- return instantiator.set(value) unless value.respond_to?(:each_pair)
60
- value.each_pair { |k, v| watirproc.fetch(k).set(v) }
61
+ return watir_widget.set value unless value.respond_to?(:each_pair)
62
+ value.each_pair { |k, v| package.fetch(k).set(v) }
61
63
  end
62
64
 
65
+ alias_method :value=, :set
66
+
67
+ # Each Semantic PageObject defines what value means for itself
68
+ # @return [SemanticValue] delegated to WebdriverElement and we expect it to respond to value message
69
+ # @return [Hash{Symbol => SemanticValue}]
63
70
  def value
64
- return instantiator.value unless watirproc.respond_to?(:each_pair)
65
- Hash[watirproc.map { |key, pageobject| [key, pageobject.value] }]
71
+ return watir_widget.value unless package.respond_to?(:each_pair)
72
+ Hash[package.map { |key, pageobject| [key, pageobject.value] }]
66
73
  end
67
74
 
68
- # access widgetry of watir elements composing this page object
69
- def element(key=false)
70
- return instantiator unless watirproc.respond_to?(:each_pair)
71
- return watirproc.fetch(key).element if key
72
- Hash[watirproc.map { |key, watirproc| [key, watirproc.element] }]
75
+ def options
76
+ return watir_widget.options unless package.respond_to?(:each_pair)
77
+ Hash[package.map { |key, pageobject| [key, pageobject.options] }]
73
78
  end
74
79
 
75
80
  private
76
81
 
77
- # recursive
78
- def initialize_this watirproc
79
- if watirproc.respond_to?(:each_pair) #hash
80
- Hash[watirproc.map { |key, watirproc| [key, PageObject.new(watirproc, container)] }]
82
+ # wrap instantiator with strategy for setting and getting value for watir object
83
+ # expects that element to respond to set and value
84
+ # @returns [WatirWidget]
85
+ def watir_widget
86
+ WatirWidget.new(instantiator)
87
+ end
88
+
89
+ # @api private
90
+ # Recursive. Examines each packages and turns each Proc into PageObject
91
+ def initialize_this package
92
+ if package.respond_to?(:each_pair) #hash
93
+ Hash[package.map { |key, package| [key, PageObject.new(package, container)] }]
81
94
  else
82
- if watirproc.respond_to?(:call) #proc
95
+ if package.respond_to?(:call) #proc
83
96
  begin
84
97
  # peek inside suitcase that is proc. XXX ouch, ugly
85
- peeked_inside = watirproc.call
98
+ peeked_inside = package.call
86
99
  rescue NoMethodError
87
- return watirproc #suitecase exploded, proc returned
100
+ return package #suitecase exploded, proc returned
88
101
  end
89
102
  if peeked_inside.respond_to?(:each_pair) # hash
90
103
  return initialize_this peeked_inside
91
104
  elsif peeked_inside.respond_to?(:wd) # watir element
92
105
  return lambda { peeked_inside }
93
- elsif peeked_inside.respond_to?(:watirproc) #pageobject
94
- return peeked_inside.watirproc
106
+ elsif peeked_inside.respond_to?(:package) #pageobject
107
+ return peeked_inside.package
95
108
  else
96
- fail Exception::Error, "watirproc must be kind of hash, watirelement or pageobject but I got this: #{watirproc}"
109
+ fail Exception::Error, "package must be kind of hash, watirelement or pageobject but I got this: #{package}"
97
110
  end
98
- elsif watirproc.respond_to?(:watirproc) #pageobject
99
- return watirproc.watirproc
111
+ elsif package.respond_to?(:package) #pageobject
112
+ return package.package
100
113
  else
101
- fail Exception::Error, "watirproc must be kind of hash, watirelement or pageobject but I got this: #{watirproc}"
114
+ fail Exception::Error, "package must be kind of hash, watirelement or pageobject but I got this: #{package}"
102
115
  end
103
116
  end
104
117
  end
105
-
106
- # talk to the browser executor.
107
- # returns runtime element in a specified container
108
- # expects that element to respond to set and value
109
- def instantiator
110
- container_instantiator.instance_exec(&watirproc)
111
- end
112
-
113
- # talk to the browser
114
- # returns runtime container element in a browser/driver
115
- def container_instantiator
116
- container.respond_to?(:call) ? container.call : container.send(:instantiator)
117
- end
118
118
  end
119
119
  end
120
120
  end