domkey 0.1.0 → 0.2.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.
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