vapir-common 1.7.2 → 1.8.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.
@@ -1,6 +1,6 @@
1
1
  require 'vapir-common/element'
2
2
  require 'vapir-common/container'
3
-
3
+ require 'vapir-common/keycodes.rb'
4
4
  module Vapir
5
5
  module Frame
6
6
  extend ElementHelper
@@ -54,6 +54,23 @@ module Vapir
54
54
  !disabled
55
55
  end
56
56
 
57
+ module WatirInputElementConfigCompatibility
58
+ def requires_typing
59
+ if config.warn_deprecated
60
+ Kernel.warn_with_caller "WARNING: #requires_typing is deprecated; please use the new config framework with config.type_keys="
61
+ end
62
+ config.type_keys = true
63
+ self
64
+ end
65
+ def abhors_typing
66
+ if config.warn_deprecated
67
+ Kernel.warn_with_caller "WARNING: #abhors_typing is deprecated; please use the new config framework with config.type_keys="
68
+ end
69
+ config.type_keys = false
70
+ self
71
+ end
72
+ end
73
+ include WatirInputElementConfigCompatibility
57
74
  end
58
75
  module TextField
59
76
  extend ElementHelper
@@ -65,6 +82,8 @@ module Vapir
65
82
  container_single_method :text_field
66
83
  container_collection_method :text_fields
67
84
 
85
+ default_how :name
86
+
68
87
  dom_attr :size, :maxLength => :maxlength
69
88
  alias_deprecated :getContents, :value
70
89
 
@@ -73,101 +92,138 @@ module Vapir
73
92
  # to be consistent with similar methods #set and #append, returns the new value, though this will always be a blank string.
74
93
  #
75
94
  # takes options:
76
- # :blur => true/false; whether or not to fire the onblur event when done.
77
- # :highlight => true/false
95
+ # - :blur => true/false; whether or not to fire the onblur event when done.
96
+ # - :highlight => true/false
78
97
  #
79
- # Raises UnknownObjectException if the object can't be found
80
- # Raises ObjectDisabledException if the object is disabled
81
- # Raises ObjectReadOnlyException if the object is read only
98
+ # Raises UnknownObjectException if the object can't be found
99
+ # Raises ObjectDisabledException if the object is disabled
100
+ # Raises ObjectReadOnlyException if the object is read only
82
101
  def clear(options={})
83
102
  options={:blur => true, :change => true, :select => true, :focus => true}.merge(options)
84
103
  assert_enabled
85
104
  assert_not_readonly
86
105
  with_highlight(options) do
87
106
  if options[:focus]
107
+ assert_exists(:force => true)
88
108
  element_object.focus
89
109
  fire_event('onFocus')
90
- assert_exists(:force => true)
91
110
  end
92
111
  if options[:select]
112
+ assert_exists(:force => true)
93
113
  element_object.select
94
114
  fire_event("onSelect")
95
- assert_exists(:force => true)
96
115
  end
97
- element_object.value = ''
98
- fire_event :onKeyDown # TODO/fix - keyCode for 'delete' key
99
- assert_exists(:force => true)
100
- fire_event :onKeyPress
101
- assert_exists(:force => true)
102
- fire_event :onKeyUp
103
- assert_exists(:force => true)
116
+ handling_existence_failure do
117
+ with_key_down(:keyCode => KeyCodes[:delete]) do
118
+ assert_exists(:force => true)
119
+ element_object.value = ''
120
+ end
121
+ end
104
122
  if options[:change] && exists?
105
123
  fire_event("onChange")
106
124
  end
107
125
  if options[:blur] && exists?
108
126
  fire_event('onBlur')
109
127
  end
110
- self.value
128
+ exists? ? self.value : nil
111
129
  end
112
130
  end
131
+ private
132
+ # todo: move to Element or something? this applies to hitting a key anywhere, really - not just TextFields.
133
+ def with_key_down(fire_event_options={})
134
+ assert_exists(:force => true)
135
+ fire_event :onKeyDown, fire_event_options
136
+ yield if block_given?
137
+ assert_exists(:force => true)
138
+ fire_event :onKeyUp, fire_event_options
139
+ end
140
+ def type_key(key)
141
+ # the events created by the following are sort of wrong - for firefox, only one of keyCode
142
+ # or charCode should ever be set.
143
+ # for ie, keyCode is always set and charCode is ignored (there is no such property of IE events).
144
+ # I could overload this method on a browser-specific basis, but having both set in ff seems to
145
+ # do no harm, so I will not do that for the moment.
146
+ if PrintKeyCodes.key?(key)
147
+ with_key_down(:keyCode => PrintKeyCodes[key]) do
148
+ assert_exists(:force => true)
149
+ fire_event :onKeyPress, :keyCode => key.vapir_ord, :charCode => key.vapir_ord
150
+ yield if block_given?
151
+ end
152
+ elsif ShiftPrintKeyCodes.key?(key)
153
+ with_key_down(:keyCode => KeyCodes[:shift], :shiftKey => true) do
154
+ with_key_down(:keyCode => ShiftPrintKeyCodes[key], :shiftKey => true) do
155
+ assert_exists(:force => true)
156
+ fire_event :onKeyPress, :keyCode => key.vapir_ord, :charCode => key.vapir_ord, :shiftKey => true
157
+ yield if block_given?
158
+ end
159
+ end
160
+ else #?
161
+ with_key_down do
162
+ assert_exists(:force => true)
163
+ fire_event :onKeyPress, :keyCode => key.vapir_ord, :charCode => key.vapir_ord
164
+ yield if block_given?
165
+ end
166
+ end
167
+ end
168
+ public
113
169
  # Appends the specified string value to the contents of the text box.
114
170
  #
115
171
  # returns the new value of the text field. this may not include all of what is given if there is a maxlength on the field.
116
172
  #
117
173
  # takes options:
118
- # :blur => true/false; whether or not to file the onblur event when done.
119
- # :highlight => true/false
174
+ # - :blur => true/false; whether or not to file the onblur event when done.
175
+ # - :highlight => true/false
120
176
  #
121
- # Raises UnknownObjectException if the object cant be found
122
- # Raises ObjectDisabledException if the object is disabled
123
- # Raises ObjectReadOnlyException if the object is read only
177
+ # Raises UnknownObjectException if the object cant be found
178
+ # Raises ObjectDisabledException if the object is disabled
179
+ # Raises ObjectReadOnlyException if the object is read only
124
180
  def append(value, options={})
125
181
  options={:blur => true, :change => true, :select => true, :focus => true}.merge(options)
126
182
  assert_enabled
127
183
  assert_not_readonly
128
184
 
129
185
  with_highlight(options) do
130
- existing_value_chars=element_object.value.split(//)
131
- new_value_chars=existing_value_chars+value.split(//)
132
- #value_chars=value.split(//) # split on blank regexp (rather than iterating over each byte) for multibyte chars
186
+ existing_value_chars=element_object.value.split(//u)
187
+ new_value_chars=existing_value_chars+value.split(//u) # IE treats the string value is set to as utf8, and this is consistent with String#ord defined in core_ext
133
188
  if self.type.downcase=='text' && maxlength && maxlength >= 0 && new_value_chars.length > maxlength
134
189
  new_value_chars=new_value_chars[0...maxlength]
135
190
  end
136
191
  element_object.scrollIntoView
137
- type_keys=respond_to?(:type_keys) ? self.type_keys : true # TODO: FIX
138
- typingspeed=respond_to?(:typingspeed) ? self.typingspeed : 0 # TODO: FIX
139
- if type_keys
140
- if options[:focus]
141
- element_object.focus
142
- fire_event('onFocus')
143
- assert_exists(:force => true)
144
- end
145
- if options[:select]
146
- element_object.select
147
- fire_event("onSelect")
148
- assert_exists(:force => true)
192
+ if options[:focus]
193
+ assert_exists(:force => true)
194
+ element_object.focus
195
+ fire_event('onFocus')
196
+ end
197
+ if options[:select]
198
+ assert_exists(:force => true)
199
+ element_object.select
200
+ fire_event("onSelect")
201
+ end
202
+ if config.type_keys
203
+ (existing_value_chars.length...new_value_chars.length).each do |i|
204
+ last_key = (i == new_value_chars.length - 1)
205
+ handling_existence_failure(:handle => (last_key ? :ignore : :raise)) do
206
+ type_key(new_value_chars[i]) do
207
+ assert_exists(:force => true)
208
+ element_object.value = new_value_chars[0..i].join('')
209
+ end
210
+ end
211
+ sleep config.typing_interval
149
212
  end
150
- ((existing_value_chars.length)...new_value_chars.length).each do |i|
151
- # sleep typingspeed # TODO/FIX
152
- element_object.value = new_value_chars[0..i].join('')
153
- fire_event :onKeyDown # TODO/fix - keyCode for character typed
154
- assert_exists(:force => true)
155
- fire_event :onKeyPress
156
- assert_exists(:force => true)
157
- fire_event :onKeyUp
213
+ else
214
+ with_key_down do # simulate at least one keypress
158
215
  assert_exists(:force => true)
216
+ element_object.value = new_value_chars.join('')
159
217
  end
160
- if options[:change] && exists?
161
- fire_event("onChange")
162
- end
163
- if options[:blur] && exists?
164
- fire_event('onBlur')
165
- end
166
- else
167
- element_object.value = element_object.value + value
218
+ end
219
+ if options[:change] && exists?
220
+ handling_existence_failure { fire_event("onChange") }
221
+ end
222
+ if options[:blur] && exists?
223
+ handling_existence_failure { fire_event('onBlur') }
168
224
  end
169
225
  wait
170
- self.value
226
+ exists? ? self.value : nil
171
227
  end
172
228
  end
173
229
  # Sets the contents of the text field to the given value
@@ -175,18 +231,16 @@ module Vapir
175
231
  # returns the new value of the text field. this may be shorter than what is given if there is a maxlength on the field.
176
232
  #
177
233
  # takes options:
178
- # :blur => true/false; whether or not to file the onblur event when done.
179
- # :highlight => true/false
234
+ # - :blur => true/false; whether or not to file the onblur event when done.
235
+ # - :highlight => true/false
180
236
  #
181
- # Raises UnknownObjectException if the object cant be found
182
- # Raises ObjectDisabledException if the object is disabled
183
- # Raises ObjectReadOnlyException if the object is read only
237
+ # Raises UnknownObjectException if the object cant be found
238
+ # Raises ObjectDisabledException if the object is disabled
239
+ # Raises ObjectReadOnlyException if the object is read only
184
240
  def set(value, options={})
185
241
  with_highlight(options) do
186
242
  clear(options.merge(:blur => false, :change => false))
187
- assert_exists(:force => true)
188
243
  append(value, options.merge(:focus => false, :select => false))
189
- self.value
190
244
  end
191
245
  end
192
246
  end
@@ -256,7 +310,7 @@ module Vapir
256
310
  method_options={:highlight => true, :wait => true}.merge(method_options)
257
311
  with_highlight(method_options) do
258
312
  state_was=element_object.selected
259
- element_object.selected=state
313
+ element_object.selected=state # TODO: if state is false and this isn't an option of a multiple select list, should this error?
260
314
  if @extra[:select_list] && state_was != state
261
315
  @extra[:select_list].fire_event(:onchange, method_options)
262
316
  end
@@ -545,10 +599,95 @@ module Vapir
545
599
  end
546
600
  end
547
601
  module HasRowsAndColumns
602
+ # returns a TableRow which is a row of this of this Table or TBody (not in a nested table).
603
+ # takes the usual arguments for specifying what you want - see http://github.com/vapir/vapir/wiki/Locators
604
+ def row(first=nil, second=nil)
605
+ element_by_howwhat(element_class_for(Vapir::TableRow), first, second, :extra => {:candidates => :rows})
606
+ end
607
+
608
+ # returns a TableCell which is a cell of this of this Table or TBody (not in a nested table).
609
+ # takes the usual arguments for specifying what you want - see http://github.com/vapir/vapir/wiki/Locators
610
+ def cell(first=nil, second=nil)
611
+ element_by_howwhat(element_class_for(Vapir::TableCell), first, second, :extra => {:candidates => proc do |container|
612
+ container_object=container.element_object
613
+ object_collection_to_enumerable(container_object.rows).inject([]) do |candidates, row|
614
+ candidates+object_collection_to_enumerable(row.cells).to_a
615
+ end
616
+ end})
617
+ end
618
+
548
619
  # Returns a 2 dimensional array of text contents of each row and column of the table or tbody.
549
620
  def to_a
550
621
  rows.map{|row| row.cells.map{|cell| cell.text.strip}}
551
622
  end
623
+
624
+ # Returns an array of hashes representing this table. This assumes that the table has one row
625
+ # with header information and any number of rows with data. Each element of the array is a hash
626
+ # whose keys are the header values (every hash has the same keys), and whose values correspond
627
+ # to the current row.
628
+ #
629
+ # +--------------+---------------+
630
+ # | First Header | Second Header |
631
+ # | First Data 1 | Second Data 1 |
632
+ # | First Data 2 | Second Data 2 |
633
+ # +--------------+---------------+
634
+ #
635
+ # Given the above table, #to_hashes will return
636
+ # [{'First Header' => 'First Data 1', 'Second Header' => 'Second Data 1'}, {'First Header' => 'First Data 2', 'Second Header' => 'Second Data 2'}]
637
+ #
638
+ # This method will correctly account for colSpans and return text from all cells underneath a
639
+ # given header on one row. However, this method makes no attempt to deal with any rowSpans and
640
+ # will probably not work with any table with rowSpans in either the header row or data rows.
641
+ #
642
+ # options:
643
+ # - :header_count (default 1) - the number of rows before the table data start.
644
+ # - :header_index (default whatever :header_count is) - the index of the row that contains
645
+ # header data which will be the keys of the hashes returned. (1-indexed)
646
+ # - :footer_count (default 0) - the number of rows to discard from the end, being footer
647
+ # information and not table data.
648
+ # - :separator (default ' ') - used to join cells when there is more than one cell
649
+ # underneath a header.
650
+ def to_hashes(options={})
651
+ options=handle_options(options, {:header_count => 1, :footer_count => 0, :separator => ' '}, [:header_index])
652
+ options[:header_index]||=options[:header_count]
653
+
654
+ col_headings=rows[options[:header_index]].cells.map do |cell|
655
+ {:colSpan => cell.colSpan || 1, :text => cell.text.strip}
656
+ end
657
+
658
+ body_range=(options[:header_count]+1 .. self.row_count-options[:footer_count])
659
+ return body_range.map do |row_index|
660
+ row=rows[row_index]
661
+ # cells_by_heading will contain an array of arrays of table cells
662
+ # underneath the col_heading corresponding to cells_by_heading's array index.
663
+ # if cells do not line up underneath the column heading, exception is raised.
664
+ cells_by_heading=[]
665
+ curr_heading_index=0
666
+ cols_in_curr_heading=0
667
+ row.cells.each do |cell|
668
+ curr_heading=col_headings[curr_heading_index]
669
+ cells_by_heading[curr_heading_index] ||= []
670
+ cells_by_heading[curr_heading_index] << cell
671
+ cols_in_curr_heading += cell.colSpan || 1
672
+ if cols_in_curr_heading == curr_heading[:colSpan]
673
+ curr_heading_index+=1
674
+ cols_in_curr_heading=0
675
+ elsif cols_in_curr_heading > curr_heading[:colSpan]
676
+ raise "Cells underneath heading #{curr_heading[:text].inspect} do not line up!"
677
+ end # else, we haven't got all the cells under the current heading; keep going
678
+ end
679
+ if curr_heading_index > col_headings.length
680
+ raise "Too many cells for the headings!"
681
+ elsif curr_heading_index < col_headings.length
682
+ raise "Too few cells for the headings!"
683
+ end
684
+
685
+ col_headings.zip(cells_by_heading).inject({}) do |row_hash, (heading, cells)|
686
+ cell_texts=cells.map(&:text).join(options[:separator]).strip
687
+ row_hash.merge(heading[:text] => cell_texts)
688
+ end
689
+ end
690
+ end
552
691
 
553
692
  # iterates through the rows in the table. Yields a TableRow object
554
693
  def each_row
@@ -603,6 +742,7 @@ module Vapir
603
742
  nil
604
743
  end
605
744
  end
745
+ #--
606
746
  # I was going to define #cell_count(index=nil) here as an alternative to #column_count
607
747
  # but it seems confusing; to me #cell_count on a Table would count up all the cells in
608
748
  # all rows, so going to avoid confusion and not do it.
@@ -617,6 +757,7 @@ module Vapir
617
757
 
618
758
  # Returns an array containing the text of the cell in the specified index in each row.
619
759
  def column_texts_at(column_index)
760
+ # TODO: since this is named as 'column', not 'cell', shouldn't it return cell_at_column?
620
761
  rows.map do |row|
621
762
  row.cells[column_index].text
622
763
  end
@@ -641,7 +782,13 @@ module Vapir
641
782
  # Returns an ElementCollection of cells in the row
642
783
  element_collection :cells, :cells, TableCell
643
784
 
644
- # Iterate over each cell in the row.
785
+ # returns a TableCell which is a cell of this of this row (not in a nested table).
786
+ # takes the usual arguments for specifying what you want - see http://github.com/vapir/vapir/wiki/Locators
787
+ def cell(first=nil, second=nil)
788
+ element_by_howwhat(element_class_for(Vapir::TableCell), first, second, :extra => {:candidates => :cells})
789
+ end
790
+
791
+ # Iterate over each cell in the row. same as #cells.each.
645
792
  def each_cell
646
793
  cells.each do |cell|
647
794
  yield cell
@@ -654,24 +801,51 @@ module Vapir
654
801
  cells[index]
655
802
  end
656
803
 
804
+ # the number of columns in this row, accounting for cells with a colspan attribute greater than 1
657
805
  def column_count
658
806
  cells.inject(0) do |count, cell|
659
807
  count+ (cell.colSpan || 1)
660
808
  end
661
809
  end
810
+
811
+ # the number of cells in this row
662
812
  def cell_count
663
813
  cells.length
664
814
  end
665
815
 
666
- # returns the cell of the current row at the given column index (starting from 1), taking
816
+ # returns the column index (starting at 0), taking into account colspans, of the table cell for which the given block returns true.
817
+ #
818
+ # if nothing matches the block, returns nil.
819
+ def column_count_where # :yields: table_cell
820
+ cells.inject(0) do |count, cell|
821
+ if yield cell
822
+ return count
823
+ end
824
+ count+(cell.colSpan || 1)
825
+ end
826
+ nil
827
+ end
828
+
829
+ # returns the cell of the current row at the given column index (starting from 0), taking
667
830
  # into account conSpans of other cells.
668
831
  #
669
832
  # returns nil if index is greater than the number of columns of this row.
670
- def cell_at_colum(index)
671
- cells.detect do |cell|
833
+ def cell_at_column(index)
834
+ #TODO: test
835
+ cells.each_by_index do |cell|
672
836
  index=index-(cell.colSpan || 1)
673
- index <= 0
837
+ return cell if index < 0
674
838
  end
839
+ nil
840
+ end
841
+
842
+ # returns the cell of the current row at the given column index (starting from 0), taking
843
+ # into account conSpans of other cells.
844
+ #
845
+ # raises exception if the cell does not exist (that is, index is greater than the number of columns of this row).
846
+ def cell_at_column!(index)
847
+ #TODO: test
848
+ cell_at_column(index) || raise(Vapir::Exception::UnknownObjectException, "Unable to locate cell at column #{index}. Column count is #{column_count}\non container: #{@container.inspect}")
675
849
  end
676
850
  end
677
851
  module TBody
@@ -687,7 +861,9 @@ module Vapir
687
861
  end
688
862
  module Table
689
863
  def self.create_from_element(container, element)
690
- Kernel.warn "DEPRECATION WARNING: create_from_element is deprecated. Please use (element).parent_table (element being the second argument to this function)\n(called from #{caller.map{|c|"\n"+c}})"
864
+ if config.warn_deprecated
865
+ Kernel.warn_with_caller "DEPRECATION WARNING: create_from_element is deprecated. Please use (element).parent_table (element being the second argument to this function)"
866
+ end
691
867
  element.parent_table
692
868
  end
693
869