capybara-ui 0.10.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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/lib/capybara-ui.rb +31 -0
  3. data/lib/capybara-ui/assertions.rb +19 -0
  4. data/lib/capybara-ui/capybara.rb +7 -0
  5. data/lib/capybara-ui/checkpoint.rb +111 -0
  6. data/lib/capybara-ui/conversions.rb +31 -0
  7. data/lib/capybara-ui/cucumber.rb +5 -0
  8. data/lib/capybara-ui/dsl.rb +107 -0
  9. data/lib/capybara-ui/instance_conversions.rb +19 -0
  10. data/lib/capybara-ui/matchers.rb +28 -0
  11. data/lib/capybara-ui/optional_dependencies.rb +5 -0
  12. data/lib/capybara-ui/rails.rb +5 -0
  13. data/lib/capybara-ui/rails/role.rb +9 -0
  14. data/lib/capybara-ui/role.rb +19 -0
  15. data/lib/capybara-ui/text_table.rb +107 -0
  16. data/lib/capybara-ui/text_table/cell_text.rb +7 -0
  17. data/lib/capybara-ui/text_table/mapping.rb +40 -0
  18. data/lib/capybara-ui/text_table/transformations.rb +13 -0
  19. data/lib/capybara-ui/text_table/void_mapping.rb +8 -0
  20. data/lib/capybara-ui/version.rb +3 -0
  21. data/lib/capybara-ui/widgets.rb +61 -0
  22. data/lib/capybara-ui/widgets/check_box.rb +26 -0
  23. data/lib/capybara-ui/widgets/cucumber_methods.rb +73 -0
  24. data/lib/capybara-ui/widgets/document.rb +19 -0
  25. data/lib/capybara-ui/widgets/dsl.rb +47 -0
  26. data/lib/capybara-ui/widgets/field.rb +22 -0
  27. data/lib/capybara-ui/widgets/field_group.rb +329 -0
  28. data/lib/capybara-ui/widgets/form.rb +26 -0
  29. data/lib/capybara-ui/widgets/list.rb +200 -0
  30. data/lib/capybara-ui/widgets/list_item.rb +22 -0
  31. data/lib/capybara-ui/widgets/parts/container.rb +46 -0
  32. data/lib/capybara-ui/widgets/parts/struct.rb +117 -0
  33. data/lib/capybara-ui/widgets/radio_button.rb +62 -0
  34. data/lib/capybara-ui/widgets/select.rb +57 -0
  35. data/lib/capybara-ui/widgets/string_value.rb +43 -0
  36. data/lib/capybara-ui/widgets/table.rb +76 -0
  37. data/lib/capybara-ui/widgets/text_field.rb +27 -0
  38. data/lib/capybara-ui/widgets/widget.rb +392 -0
  39. data/lib/capybara-ui/widgets/widget/node_filter.rb +48 -0
  40. data/lib/capybara-ui/widgets/widget_class.rb +11 -0
  41. data/lib/capybara-ui/widgets/widget_name.rb +56 -0
  42. metadata +240 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 23fc0acf5fe588ee3f45849a2874646086bd6ee5
4
+ data.tar.gz: cc92aa9e22aa4c98962fe0f69cf932064199eb44
5
+ SHA512:
6
+ metadata.gz: c7e1e9fe1f240f81740fd5069ed9a28fdadb8c0243cc46c614a6172cb6e88ed0e8e08ab58611093780b66eeea5356d5d6a5b8c7e1016ac897c77f49d078e24d5
7
+ data.tar.gz: a7cb453961ba99e4ef31daf00e0016fc8c9b802a018efdf030191b99cfe13878d65651514d35e43e89bb504f04ddc45845a66612a0361a70b210a009807172e8
@@ -0,0 +1,31 @@
1
+ require 'chronic'
2
+ require 'nokogiri'
3
+ require 'capybara'
4
+
5
+ require 'capybara-ui/optional_dependencies'
6
+ require 'capybara-ui/conversions'
7
+ require 'capybara-ui/instance_conversions'
8
+ require 'capybara-ui/checkpoint'
9
+ require 'capybara-ui/widgets'
10
+ require 'capybara-ui/text_table'
11
+ require 'capybara-ui/text_table/mapping'
12
+ require 'capybara-ui/text_table/void_mapping'
13
+ require 'capybara-ui/text_table/transformations'
14
+ require 'capybara-ui/text_table/cell_text'
15
+ require 'capybara-ui/capybara'
16
+ require 'capybara-ui/dsl'
17
+
18
+ module CapybaraUI
19
+ # An exception that signals that something is missing.
20
+ class Missing < StandardError; end
21
+ class MissingWidget < StandardError; end
22
+ class AmbiguousWidget < StandardError; end
23
+ class InvalidOption < StandardError; end
24
+ class InvalidRadioButton < StandardError; end
25
+
26
+ def deprecate(method, alternate_method, once=false)
27
+ @deprecation_notified ||= {}
28
+ warn "DEPRECATED: ##{method} is deprecated, please use ##{alternate_method} instead" unless once and @deprecation_notified[method]
29
+ @deprecation_notified[method] = true
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ module CapybaraUI
2
+ module Assertions
3
+ def assert_visible(role, widget_name, *args)
4
+ eventually do
5
+ assert role.see?(widget_name, *args)
6
+
7
+ true
8
+ end
9
+ end
10
+
11
+ def assert_not_visible(role, widget_name, *args)
12
+ eventually do
13
+ refute role.see?(widget_name, *args)
14
+
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ Capybara.configure do |config|
2
+ config.match = :one
3
+ config.exact = true
4
+ config.exact_options = true
5
+ config.ignore_hidden_elements = true
6
+ config.visible_text_only = true
7
+ end
@@ -0,0 +1,111 @@
1
+ module CapybaraUI
2
+ # A point in time where some condition, or some set of conditions, should be
3
+ # verified.
4
+ #
5
+ # @see http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Base#synchronize-instance_method,
6
+ # which inspired this class.
7
+ class Checkpoint
8
+ class ConditionNotMet < StandardError; end
9
+
10
+ class << self
11
+ attr_accessor :rescuable_errors
12
+ end
13
+
14
+ self.rescuable_errors = [StandardError]
15
+
16
+ if defined?(RSpec)
17
+ require 'rspec/expectations'
18
+ self.rescuable_errors << RSpec::Expectations::ExpectationNotMetError
19
+ end
20
+
21
+ class Timer
22
+ class Frozen < StandardError; end
23
+
24
+ def initialize(duration)
25
+ @duration = duration
26
+ end
27
+
28
+ attr_reader :duration
29
+
30
+ def expired?
31
+ duration < elapsed
32
+ end
33
+
34
+ def elapsed
35
+ now - start_time
36
+ end
37
+
38
+ def start
39
+ @start_time = now
40
+ end
41
+
42
+ def tick
43
+ sleep tick_duration
44
+
45
+ raise Frozen, 'time appears to be frozen' if frozen?
46
+ end
47
+
48
+ protected
49
+
50
+ def now
51
+ Time.now
52
+ end
53
+
54
+ attr_reader :start_time
55
+
56
+ def frozen?
57
+ now == start_time
58
+ end
59
+
60
+ def tick_duration
61
+ 0.05
62
+ end
63
+ end
64
+
65
+ # Shortcut for instance level wait_for.
66
+ def self.wait_for(wait_time = Capybara.default_max_wait_time, &block)
67
+ new(wait_time).call(&block)
68
+ end
69
+
70
+ # Initializes a new Checkpoint.
71
+ #
72
+ # @param wait_time how long this checkpoint will wait for its conditions to
73
+ # be met, in seconds.
74
+ def initialize(wait_time = Capybara.default_max_wait_time)
75
+ @timer = Timer.new(wait_time)
76
+ end
77
+
78
+ # Executes +block+ repeatedly until it returns a "truthy" value or
79
+ # +wait_time+ expires.
80
+ #
81
+ # Swallows any StandardError or StandardError descendent until +wait_time+
82
+ # expires. If an exception is raised and the time has expired, that
83
+ # exception will be raised again.
84
+ #
85
+ # If the block does not return a "truthy" value until +wait_time+ expires,
86
+ # raises a CapybaraUI::Checkpoint::ConditionNotMet error.
87
+ #
88
+ # Returns whatever value is returned by the block.
89
+ def call(&condition)
90
+ timer.start
91
+
92
+ begin
93
+ yield or raise ConditionNotMet
94
+ rescue *rescuable_errors
95
+ raise if timer.expired?
96
+
97
+ timer.tick
98
+
99
+ retry
100
+ end
101
+ end
102
+
103
+ protected
104
+
105
+ def rescuable_errors
106
+ self.class.rescuable_errors
107
+ end
108
+
109
+ attr_reader :timer
110
+ end
111
+ end
@@ -0,0 +1,31 @@
1
+ module CapybaraUI
2
+ module Conversions
3
+ extend self
4
+
5
+ def Boolean(val)
6
+ case val
7
+ when 'yes', 'true', true
8
+ true
9
+ when 'no', 'false', false, nil, ''
10
+ false
11
+ else
12
+ raise ArgumentError, "can't convert #{val.inspect} to boolean"
13
+ end
14
+ end
15
+
16
+ def List(valstr, &block)
17
+ vs = valstr.strip.split(/\s*,\s*/)
18
+
19
+ block ? vs.map(&block) : vs
20
+ end
21
+
22
+ def Timeish(val)
23
+ raise ArgumentError, "can't convert nil to Timeish" if val.nil?
24
+
25
+ return val if Date === val || Time === val || DateTime === val
26
+
27
+ Chronic.parse(val) or
28
+ raise ArgumentError, "can't parse #{val.inspect} to Timeish"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ require 'capybara/cucumber'
2
+ require 'capybara-ui/rails'
3
+ require 'capybara-ui/matchers'
4
+
5
+ World(CapybaraUI::DSL)
@@ -0,0 +1,107 @@
1
+ module CapybaraUI
2
+ module DSL
3
+ attr_writer :widget_lookup_scope
4
+
5
+ # Clicks the widget defined by +name+ and optional +args+.
6
+ #
7
+ # Makes no distinction between links or buttons.
8
+ #
9
+ # class MyWidget < CapybaraUI::Widget
10
+ # root { |text| ['.my-widget', text: text] }
11
+ # end
12
+ #
13
+ # # <a href="#one" class="my-widget">One</li>
14
+ # # <a href="#two" class="my-widget">Two</li> <!-- clicks this node -->
15
+ # click :my_widget, 'Two'
16
+ def click(name, *args)
17
+ widget(name, *args).click
18
+ end
19
+
20
+ # Hovers the widget defined by +name+ and optional +args+.
21
+ def hover(name, *args)
22
+ widget(name, *args).hover
23
+ end
24
+
25
+ # Double clicks the widget defined by +name+ and optional +args+.
26
+ def double_click(name, *args)
27
+ widget(name, *args).double_click
28
+ end
29
+
30
+ # Right clicks the widget defined by +name+ and optional +args+.
31
+ def right_click(name, *args)
32
+ widget(name, *args).right_click
33
+ end
34
+
35
+ # @return [Document] the current document with the class of the
36
+ # current object set as the widget lookup scope.
37
+ def document
38
+ Document.new(widget_lookup_scope)
39
+ end
40
+
41
+ # @return [Boolean] Whether one or more widgets exist in the current
42
+ # document.
43
+ def has_widget?(name, *args)
44
+ document.has_widget?(name, *args)
45
+ end
46
+
47
+ alias_method :widget?, :has_widget?
48
+
49
+ def visible?(name, *args)
50
+ document.visible?(name, *args)
51
+ end
52
+
53
+ def not_visible?(name, *args)
54
+ document.not_visible?(name, *args)
55
+ end
56
+
57
+ def set(name, fields)
58
+ widget(name).set fields
59
+ end
60
+
61
+ def submit(name, fields = {})
62
+ widget(name).submit_with fields
63
+ end
64
+
65
+ def value(name, *args)
66
+ widget(name, *args).value
67
+ end
68
+
69
+ def values(name, *args)
70
+ widgets(name, *args).map(&:value)
71
+ end
72
+
73
+ def visit(path)
74
+ Capybara.current_session.visit path
75
+ end
76
+
77
+ # Returns a widget instance for the given name.
78
+ #
79
+ # @param name [String, Symbol]
80
+ def widget(name, *args)
81
+ eventually { document.widget(name, *args) }
82
+ end
83
+
84
+ # Returns a list of widget instances for the given name.
85
+ #
86
+ # @param name [String, Symbol]
87
+ def widgets(name, *args)
88
+ document.widgets(name, *args)
89
+ end
90
+
91
+ def widget_lookup_scope
92
+ @widget_lookup_scope ||= default_widget_lookup_scope
93
+ end
94
+
95
+ # re-run one or more assertions until either they all pass,
96
+ # or CapybaraUI times out, which will result in a test failure.
97
+ def eventually(wait_time = Capybara.default_max_wait_time, &block)
98
+ Checkpoint.wait_for wait_time, &block
99
+ end
100
+
101
+ private
102
+
103
+ def default_widget_lookup_scope
104
+ Module === self ? self : self.class
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,19 @@
1
+ module CapybaraUI
2
+ module InstanceConversions
3
+ def self.included(base)
4
+ base.send :include, CapybaraUI::Conversions
5
+ end
6
+
7
+ def to_boolean
8
+ Boolean(self)
9
+ end
10
+
11
+ def to_a
12
+ List(self)
13
+ end
14
+
15
+ def to_time
16
+ Timeish(self)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ begin
2
+ require 'rspec/matchers'
3
+ require 'rspec/version'
4
+
5
+ unless Gem::Version.new(RSpec::Version::STRING) >= Gem::Version.new(CapybaraUI::OptionalDependencies::RSPEC_VERSION)
6
+ raise LoadError,
7
+ "requires RSpec version #{CapybaraUI::OptionalDependencies::RSPEC_VERSION} or later. " \
8
+ "You have #{RSpec::Version::STRING}."
9
+ end
10
+
11
+ RSpec::Matchers.define :see do |widget_name, *args|
12
+ match do |role|
13
+ begin
14
+ eventually { role.see?(widget_name, *args) }
15
+ rescue CapybaraUI::Checkpoint::ConditionNotMet
16
+ false
17
+ end
18
+ end
19
+
20
+ match_when_negated do |role|
21
+ begin
22
+ eventually { !role.see?(widget_name, *args) }
23
+ rescue CapybaraUI::Checkpoint::ConditionNotMet
24
+ false
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module CapybaraUI
2
+ module OptionalDependencies
3
+ RSPEC_VERSION = '3.0'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'action_dispatch/testing/integration'
2
+
3
+ require 'capybara-ui/rails/role'
4
+ require 'capybara-ui'
5
+ require 'capybara-ui/role'
@@ -0,0 +1,9 @@
1
+ module CapybaraUI
2
+ module Rails
3
+ class Role < ActionDispatch::IntegrationTest
4
+ def initialize
5
+ super self.class.name
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module CapybaraUI
2
+ class Role < CapybaraUI::Rails::Role
3
+ extend Widgets::DSL
4
+
5
+ include CapybaraUI::DSL
6
+
7
+ def see?(name, *args)
8
+ if respond_to?("see_#{name}?")
9
+ send("see_#{name}?", *args)
10
+ else
11
+ visible?(name, *args)
12
+ end
13
+ end
14
+
15
+ def inspect
16
+ self.class.name
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,107 @@
1
+ module CapybaraUI
2
+ class TextTable
3
+ extend Forwardable
4
+
5
+ include Enumerable
6
+ include Conversions
7
+
8
+ class << self
9
+ def Array(table)
10
+ new(table).to_a
11
+ end
12
+
13
+ def Hash(table)
14
+ new(table).to_h
15
+ end
16
+
17
+ def map(name, options = {}, &block)
18
+ case name
19
+ when :*
20
+ set_default_mapping options, &block
21
+ else
22
+ set_mapping name, options, &block
23
+ end
24
+ end
25
+
26
+ def mappings
27
+ @mappings ||= Hash.
28
+ new { |h, k| h[k] = Mapping.new }.
29
+ merge(with_parent_mappings)
30
+ end
31
+
32
+ def skip(name)
33
+ case name
34
+ when :*
35
+ set_default_mapping VoidMapping
36
+ else
37
+ raise ArgumentError, "can't convert #{name.inspect} to name"
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def set_default_mapping(options, &block)
44
+ case options
45
+ when Hash
46
+ @mappings = Hash.
47
+ new { |h, k|
48
+ h[k] = Mapping.new(key_transformer: options[:to],
49
+ value_transformer: block) }.
50
+ merge(mappings)
51
+ when Class
52
+ @mappings = Hash.new { |h, k| h[k] = options.new }.merge(mappings)
53
+ else
54
+ raise ArgumentError, "can't convert #{options.inspect} to mapping"
55
+ end
56
+ end
57
+
58
+ def set_mapping(name, options, &block)
59
+ mappings[name] = Mapping.
60
+ new(key: options[:to], value_transformer: block)
61
+ end
62
+
63
+ def with_parent_mappings
64
+ if superclass.respond_to?(:mappings)
65
+ superclass.send(:mappings).dup
66
+ else
67
+ {}
68
+ end
69
+ end
70
+ end
71
+
72
+ def_delegators 'self.class', :mappings
73
+
74
+ def initialize(table)
75
+ self.table = table
76
+ end
77
+
78
+ def each(&block)
79
+ rows.each(&block)
80
+ end
81
+
82
+ def rows
83
+ @rows ||= table.hashes.map { |h| new_row(h) }
84
+ end
85
+
86
+ def single_row
87
+ @single_row ||= new_row(table.rows_hash)
88
+ end
89
+
90
+ alias_method :to_a, :rows
91
+ alias_method :to_h, :single_row
92
+
93
+ private
94
+
95
+ attr_accessor :table
96
+
97
+ def new_row(hash)
98
+ hash.each_with_object({}) { |(k, v), h|
99
+ mapping_for(k).set(self, h, k, CellText.new(v))
100
+ }
101
+ end
102
+
103
+ def mapping_for(header)
104
+ mappings[header]
105
+ end
106
+ end
107
+ end