capybara-ui 0.10.0

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