kookaburra 2.0.0 → 3.0.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +1 -3
  4. data/README.markdown +259 -181
  5. data/Rakefile +11 -2
  6. data/kookaburra.gemspec +1 -0
  7. data/lib/kookaburra.rb +24 -9
  8. data/lib/kookaburra/api_client.rb +1 -1
  9. data/lib/kookaburra/api_driver.rb +1 -1
  10. data/lib/kookaburra/assertion.rb +1 -1
  11. data/lib/kookaburra/configuration.rb +25 -9
  12. data/lib/kookaburra/configuration/proxy.rb +55 -0
  13. data/lib/kookaburra/dependency_accessor.rb +7 -0
  14. data/lib/kookaburra/exceptions.rb +11 -3
  15. data/lib/kookaburra/mental_model.rb +5 -5
  16. data/lib/kookaburra/rack_app_server.rb +3 -12
  17. data/lib/kookaburra/test_helpers.rb +53 -12
  18. data/lib/kookaburra/ui_driver/ui_component.rb +9 -2
  19. data/lib/kookaburra/version.rb +1 -1
  20. data/spec/integration/test_a_rack_application_spec.rb +3 -2
  21. data/spec/integration/test_multiple_applications_spec.rb +96 -0
  22. data/spec/kookaburra/api_client_spec.rb +6 -5
  23. data/spec/kookaburra/api_driver_spec.rb +18 -0
  24. data/spec/kookaburra/configuration/proxy_spec.rb +39 -0
  25. data/spec/kookaburra/configuration_spec.rb +54 -2
  26. data/spec/kookaburra/mental_model_spec.rb +22 -20
  27. data/spec/kookaburra/test_helpers_spec.rb +60 -24
  28. data/spec/kookaburra/ui_driver/scoped_browser_spec.rb +3 -2
  29. data/spec/kookaburra/ui_driver/ui_component/address_bar_spec.rb +2 -1
  30. data/spec/kookaburra/ui_driver/ui_component_spec.rb +48 -14
  31. data/spec/kookaburra/ui_driver_spec.rb +4 -3
  32. data/spec/kookaburra_spec.rb +19 -14
  33. data/spec/spec_helper.rb +79 -0
  34. data/spec/support/shared_examples/it_can_have_ui_components.rb +2 -2
  35. data/spec/support/shared_examples/it_can_make_assertions.rb +3 -3
  36. data/spec/support/shared_examples/it_has_a_dependency_accessor.rb +3 -4
  37. metadata +25 -2
data/Rakefile CHANGED
@@ -3,8 +3,17 @@
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
5
 
6
- task :default => :spec
7
- RSpec::Core::RakeTask.new
6
+ task :default => [:spec, 'spec:slow']
7
+
8
+ RSpec::Core::RakeTask.new do |t|
9
+ t.rspec_opts = "-t ~slow"
10
+ end
11
+
12
+ namespace :spec do
13
+ RSpec::Core::RakeTask.new(:slow) do |t|
14
+ t.rspec_opts = "-t slow"
15
+ end
16
+ end
8
17
 
9
18
  desc 'Runs reek to detect code smells'
10
19
  task :reek do
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency "uuid"
33
33
  spec.add_development_dependency "find_a_port"
34
34
  spec.add_development_dependency "json"
35
+ spec.add_development_dependency "codeclimate-test-reporter"
35
36
  end
@@ -1,3 +1,4 @@
1
+ require 'forwardable'
1
2
  require 'kookaburra/exceptions'
2
3
  require 'kookaburra/mental_model'
3
4
  require 'kookaburra/api_driver'
@@ -17,21 +18,31 @@ require 'kookaburra/configuration'
17
18
  #
18
19
  # @see Kookaburra::TestHelpers
19
20
  class Kookaburra
21
+ extend Forwardable
22
+
20
23
  class << self
21
24
  # Stores the configuration object that is used by default when creating new
22
25
  # instances of Kookaburra
23
26
  #
24
- # @return [Kookaburra::Configuration] return value is memoized
27
+ # @return [Kookaburra::Configuration]
25
28
  def configuration
26
29
  @configuration ||= Configuration.new
27
30
  end
28
31
 
29
- # Yields the current configuration so that it can be modified
32
+ # Yields a new configuration so that it can be modified
33
+ #
34
+ # The new configuration object will now be used by default when creating
35
+ # instances of Kookaburra.
30
36
  #
31
37
  # @yield [Kookaburra::Configuration]
32
38
  def configure(&blk)
39
+ self.configuration = Configuration.new
33
40
  blk.call(configuration)
34
41
  end
42
+
43
+ private
44
+
45
+ attr_writer :configuration
35
46
  end
36
47
 
37
48
  # Returns a new Kookaburra instance that wires together your application's
@@ -39,10 +50,7 @@ class Kookaburra
39
50
  #
40
51
  # @param [Kookaburra::Configuration] configuration (Kookaburra.configuration)
41
52
  def initialize(configuration = Kookaburra.configuration)
42
- @configuration = configuration
43
- @configuration.mental_model = MentalModel.new
44
- @api_driver_class = configuration.api_driver_class
45
- @ui_driver_class = configuration.ui_driver_class
53
+ self.configuration = configuration
46
54
  end
47
55
 
48
56
  # Returns an instance of your APIDriver class configured to share test
@@ -50,7 +58,7 @@ class Kookaburra
50
58
  #
51
59
  # @return [Kookaburra::APIDriver]
52
60
  def api
53
- @api ||= @api_driver_class.new(@configuration)
61
+ @api ||= api_driver_class.new(configuration)
54
62
  end
55
63
 
56
64
  # Returns an instance of your UIDriver class configured to share test fixture
@@ -59,7 +67,7 @@ class Kookaburra
59
67
  #
60
68
  # @return [Kookaburra::UIDriver]
61
69
  def ui
62
- @ui ||= @ui_driver_class.new(@configuration)
70
+ @ui ||= ui_driver_class.new(configuration)
63
71
  end
64
72
 
65
73
  # Returns a deep-dup of the specified {MentalModel::Collection}.
@@ -75,6 +83,13 @@ class Kookaburra
75
83
  #
76
84
  # @return [Kookaburra::MentalModel::Collection]
77
85
  def get_data(collection_name)
78
- @configuration.mental_model.send(collection_name).dup
86
+ mental_model.send(collection_name).dup
79
87
  end
88
+
89
+ private
90
+
91
+ attr_accessor :configuration
92
+
93
+ def_delegators :configuration, :api_driver_class, :ui_driver_class,
94
+ :mental_model
80
95
  end
@@ -7,7 +7,7 @@ class Kookaburra
7
7
  #
8
8
  # You will create a subclass of {APIClient} in your testing
9
9
  # implementation to be used with you subclass of
10
- # {Kookaburra::APIDriver}. While the {GivenDriver} implements the
10
+ # {Kookaburra::APIDriver}. While the {APIDriver} implements the
11
11
  # "business domain" DSL for setting up your application state, the
12
12
  # {APIClient} maps discreet operations to your application's web
13
13
  # service API and can (optionally) handle encoding input data and
@@ -36,7 +36,7 @@ class Kookaburra
36
36
  extend Forwardable
37
37
 
38
38
  # It is unlikely that you would call #initialize yourself; your APIDriver
39
- # object is instantiated for you by {Kookaburra#given}.
39
+ # object is instantiated for you by {Kookaburra#api}.
40
40
  #
41
41
  # @param [Kookaburra::Configuration] configuration
42
42
  def initialize(configuration)
@@ -18,7 +18,7 @@ class Kookaburra
18
18
  #
19
19
  # @raise [Kookaburra::AssertionFailed] raised if test evaluates to false
20
20
  def assert(test, message = "You might want to provide a better message, eh?")
21
- test or raise AssertionFailed, message
21
+ (!!test == true) or raise AssertionFailed, message
22
22
  end
23
23
  end
24
24
  end
@@ -1,6 +1,8 @@
1
1
  require 'ostruct'
2
2
  require 'delegate'
3
3
  require 'kookaburra/dependency_accessor'
4
+ require 'kookaburra/mental_model'
5
+ require 'kookaburra/configuration/proxy'
4
6
 
5
7
  class Kookaburra
6
8
  # Provides access to the configuration data used throughout Kookaburra
@@ -38,15 +40,6 @@ class Kookaburra
38
40
  # without it having been set
39
41
  dependency_accessor :app_host
40
42
 
41
- # This is the {Kookaburra::MentalModel} that is shared between your
42
- # APIDriver and your UIDriver. This attribute is managed by {Kookaburra},
43
- # so you shouldn't need to change it yourself.
44
- #
45
- # @attribute [rw] mental_model
46
- # @raise [Kookaburra::ConfigurationError] if you try to read this attribute
47
- # without it having been set
48
- dependency_accessor :mental_model
49
-
50
43
  # This is the logger to which Kookaburra will send various messages
51
44
  # about its operation. This would generally be used to allow
52
45
  # UIDriver subclasses to provide detailed failure information.
@@ -84,5 +77,28 @@ class Kookaburra
84
77
  def app_host_uri
85
78
  URI.parse(app_host)
86
79
  end
80
+
81
+ # This is the {Kookaburra::MentalModel} that is shared between your
82
+ # APIDriver and your UIDriver. This attribute is managed by {Kookaburra},
83
+ # so you shouldn't need to change it yourself.
84
+ def mental_model
85
+ @mental_model ||= MentalModel.new
86
+ end
87
+
88
+ attr_writer :mental_model
89
+
90
+ def application(name, &block)
91
+ proxy = Proxy.new(name: name, basis: self)
92
+ block.call(proxy) if block_given?
93
+ applications[name] = Kookaburra.new(proxy)
94
+ end
95
+
96
+ def applications
97
+ @applications ||= {}
98
+ end
99
+
100
+ def has_named_applications?
101
+ !applications.empty?
102
+ end
87
103
  end
88
104
  end
@@ -0,0 +1,55 @@
1
+ require 'delegate'
2
+
3
+ class Kookaburra
4
+ class Configuration
5
+ # Used to manage configuration for a named application
6
+ #
7
+ # `Configuration::Proxy` objects delegate to their basis object to retrieve
8
+ # default values. This allows a base configuration to be specified and
9
+ # inherited by each application configuration.
10
+ #
11
+ # @see Kookaburra::Configuration#application
12
+ # @private
13
+ class Proxy < DelegateClass(Configuration)
14
+ attr_reader :name
15
+
16
+ # Builds a new Proxy
17
+ #
18
+ # @param [String] name An (arbitrary) identifier for this configuration
19
+ # @param [Kookaburra::Configuration] The object that will be used as the source for default values
20
+ def initialize(name:, basis:)
21
+ self.name = name
22
+ self.basis = basis
23
+ __setobj__(basis)
24
+ end
25
+
26
+ private
27
+
28
+ attr_writer :name
29
+ attr_accessor :basis
30
+
31
+ def method_missing(method_name, *args, &block)
32
+ define_proxy_override(method_name, args.first) \
33
+ || super
34
+ end
35
+
36
+ def define_proxy_override(writer_name, value)
37
+ return false unless valid_attr_writer?(writer_name)
38
+ reader_name = reader_name_for writer_name
39
+ (class << self; self; end).class_eval do
40
+ define_method(reader_name) { value }
41
+ end
42
+ return true
43
+ end
44
+
45
+ def valid_attr_writer?(writer_name)
46
+ basis.respond_to?(writer_name) \
47
+ && /=$/ =~ writer_name.to_s
48
+ end
49
+
50
+ def reader_name_for(writer_name)
51
+ writer_name.to_s.sub(/=$/, '')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -12,13 +12,20 @@ class Kookaburra
12
12
  private
13
13
 
14
14
  def define_dependency_accessor(name)
15
+ define_attr_reader(name)
16
+ define_attr_writer(name)
17
+ end
18
+
19
+ def define_attr_reader(name)
15
20
  define_method(name) do
16
21
  class_name = self.class.name
17
22
  class_name.sub!(/^$/, 'an Anonymous Class!!!')
18
23
  instance_variable_get("@#{name}") or raise "No %s object was set on %s initialization." \
19
24
  % [name, class_name]
20
25
  end
26
+ end
21
27
 
28
+ def define_attr_writer(name)
22
29
  define_method("#{name}=") do |value|
23
30
  instance_variable_set("@#{name}", value)
24
31
  end
@@ -1,14 +1,22 @@
1
1
  class Kookaburra
2
+ # Raised when accessing unknown mental model data
2
3
  # @private
3
4
  class UnknownKeyError < ArgumentError; end
5
+
6
+ # Raised when there is a problem with the Kookaburra runtime configuration
4
7
  # @private
5
8
  class ConfigurationError < StandardError; end
9
+
10
+ # Raised when an API or UI request results in an enexpected HTTP response
6
11
  # @private
7
12
  class UnexpectedResponse < RuntimeError; end
13
+
14
+ # Raised when an assertion fails inside the drivers.
8
15
  # @private
9
16
  class AssertionFailed < RuntimeError; end
17
+
18
+ # Raised when calling `api` or `ui` test helpers and named applications are
19
+ # configured
10
20
  # @private
11
- class ComponentNotFound < RuntimeError; end
12
- # @private
13
- class NullBrowserError < ConfigurationError; end
21
+ class AmbiguousDriverError < StandardError; end
14
22
  end
@@ -28,7 +28,7 @@ class Kookaburra
28
28
  # MentalModel instances respond to everything.
29
29
  #
30
30
  # @see #method_missing
31
- def respond_to?
31
+ def respond_to?(_)
32
32
  true
33
33
  end
34
34
 
@@ -53,12 +53,12 @@ class Kookaburra
53
53
  # @param [String] name The name of the collection. Used to provide
54
54
  # helpful error messages when unknown keys are accessed.
55
55
  # @param [Hash] init_data Preloads specific data into the collection
56
- def initialize(name, init_data = nil)
56
+ def initialize(name, init_data = {})
57
57
  self.name = name
58
58
  data = Hash.new do |hash, key|
59
59
  raise UnknownKeyError, "Can't find mental_model.#{@name}[#{key.inspect}]. Did you forget to set it?"
60
60
  end
61
- data.merge!(init_data) unless init_data.nil?
61
+ data.merge!(init_data)
62
62
  super(data)
63
63
  end
64
64
 
@@ -132,8 +132,8 @@ class Kookaburra
132
132
  #
133
133
  # @return [Hash] the key/value pairs still remaining after the deletion
134
134
  def delete_if(&block)
135
- move = lambda { |k,v| deleted[k] = v; true }
136
- super { |k,v| block.call(k,v) && move.call(k,v) }
135
+ move = ->(key, value) { deleted[key] = value; true }
136
+ super { |key, value| block.call(key, value) && move.call(key, value) }
137
137
  end
138
138
 
139
139
  def dup
@@ -49,16 +49,11 @@ class Kookaburra::RackAppServer
49
49
  # will then monitor that port and only return once the app server is
50
50
  # responding (or after the timeout period specified on {#initialize}).
51
51
  def boot
52
- if defined?(JRUBY_VERSION)
53
- thread_app_server
54
- else
55
- fork_app_server
56
- end
52
+ fork_app_server
57
53
  wait_for_app_to_respond
58
54
  end
59
55
 
60
56
  def shutdown
61
- return if defined?(JRUBY_VERSION)
62
57
  Process.kill(9, rack_server_pid)
63
58
  Process.wait
64
59
  end
@@ -68,10 +63,6 @@ class Kookaburra::RackAppServer
68
63
  attr_accessor :rack_app_initializer, :rack_server_pid, :startup_timeout
69
64
  attr_writer :port
70
65
 
71
- def thread_app_server
72
- Thread.new { start_server }
73
- end
74
-
75
66
  def fork_app_server
76
67
  self.rack_server_pid = fork do
77
68
  start_server
@@ -98,8 +89,8 @@ class Kookaburra::RackAppServer
98
89
  end
99
90
 
100
91
  def running?
101
- res = Net::HTTP.start('localhost', port) { |http| http.get('/__identify__') }
102
- if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
92
+ case (Net::HTTP.start('localhost', port) { |http| http.get('/__identify__') })
93
+ when Net::HTTPSuccess, Net::HTTPRedirection
103
94
  true
104
95
  else
105
96
  false
@@ -82,21 +82,62 @@ class Kookaburra
82
82
  module TestHelpers
83
83
  extend Forwardable
84
84
 
85
- # The {Kookaburra} instance to be used by your tests. It gets configured
86
- # using the options set in {Kookaburra.configuration}, and the result is
87
- # memoized.
85
+ # @method api
86
+ # Delegates to main {Kookaburra} instance
87
+ #
88
+ # @note This method will only be available when no named applications have
89
+ # been defined. (See {Kookaburra::Configuration#application}.)
90
+ #
91
+ # @raise [Kookaburra::AmbiguousDriverError] if named applications have been
92
+ # configured.
93
+ def_delegator :kookaburra, :api
94
+
95
+ # @method ui
96
+ # Delegates to main {Kookaburra} instance
88
97
  #
89
- # @return [Kookaburra]
90
- def k
91
- @k ||= Kookaburra.new
98
+ # @note This method will only be available when no named applications have
99
+ # been defined. (See {Kookaburra::Configuration#application}.)
100
+ #
101
+ # @raise [Kookaburra::AmbiguousDriverError] if named applications have been
102
+ # configured.
103
+ def_delegator :kookaburra, :ui
104
+
105
+ # Delegates to main {Kookaburra} instance
106
+ def get_data(*args)
107
+ main_kookaburra.get_data(*args)
92
108
  end
93
109
 
94
- # @method api
95
- # Delegates to {#k}
96
- def_delegator :k, :api
110
+ # Will return the {Kookaburra} instance for any configured applications
111
+ #
112
+ # After an application is configured via
113
+ # {Kookaburra::Configuration#application}, the TestHelpers will respond to
114
+ # the name of that application by returning the associated Kookaburra
115
+ # instance. (Messages that do not match the name of a configured application
116
+ # will be handled with the usual #method_missing semantics.)
117
+ #
118
+ # @see [Kookaburra::Configuration#application]
119
+ def method_missing(method_name, *args, &block)
120
+ Kookaburra.configuration.applications[method_name.to_sym] \
121
+ || super
122
+ end
97
123
 
98
- # @method ui
99
- # Delegates to {#k}
100
- def_delegator :k, :ui
124
+ # @see [Kookaburra::TestHelpers#method_missing]
125
+ def respond_to?(method_name)
126
+ Kookaburra.configuration.applications.has_key?(method_name) \
127
+ || super
128
+ end
129
+
130
+ private
131
+
132
+ def kookaburra
133
+ if Kookaburra.configuration.has_named_applications?
134
+ raise AmbiguousDriverError
135
+ end
136
+ main_kookaburra
137
+ end
138
+
139
+ def main_kookaburra
140
+ @kookaburra ||= Kookaburra.new
141
+ end
101
142
  end
102
143
  end
@@ -106,15 +106,22 @@ class Kookaburra
106
106
  end
107
107
 
108
108
  # Is the component's element found on the page and is it considered
109
- # "visible" by the browser driver.
109
+ # "visible" by the browser driver?
110
110
  def visible?
111
- visible = browser.has_css?(component_locator, :visible)
111
+ visible = browser.has_css?(component_locator, visible: true)
112
112
  unless visible
113
113
  detect_server_error!
114
114
  end
115
115
  visible
116
116
  end
117
117
 
118
+ # The opposite of {#visible?}
119
+ #
120
+ # @note This does not check for a server error.
121
+ def not_visible?
122
+ browser.has_no_css?(component_locator, visible: true)
123
+ end
124
+
118
125
  # Returns the full URL by appending {#component_path} to the value of the
119
126
  # {Kookaburra::Configuration#app_host} from the initialized configuration.
120
127
  def url(*args)