kookaburra 0.22.3 → 0.23.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.
data/README.markdown CHANGED
@@ -165,29 +165,34 @@ your own UIComponents. This method exists to verify preconditions and provide
165
165
  more informative error messages; it is not intended to be used for test
166
166
  verifications.
167
167
 
168
+ `Kookaburra::TestHelpers` provides a convenient way to make assertions about the
169
+ mental model. If you are using Test::Unit, see
170
+ `Kookaburra::TestHelpers#assert_mental_model_of`; for RSpec, see
171
+ `Kookaburra::TestHelpers#match_mental_model_of`.
172
+
168
173
  Given the Cucumber scenario above, here is how the test implementation layer
169
174
  might look:
170
175
 
171
176
  # step_definitions/various_steps.rb
172
177
 
173
178
  Given "I have an existing account" do
174
- given.existing_account(:my_account)
179
+ given.existing_account
175
180
  end
176
181
 
177
182
  Given "I have previously specified default payment options" do
178
- given.default_payment_options_specified_for(:my_account)
183
+ given.default_payment_options_specified
179
184
  end
180
185
 
181
186
  Given "I have previously specified default shipping options" do
182
- given.default_shipping_options_specified_for(:my_account)
187
+ given.default_shipping_options_specified
183
188
  end
184
189
 
185
190
  Given "I have an item in my shopping cart" do
186
- given.an_item_in_my_shopping_cart(:my_account)
191
+ given.an_item_in_my_shopping_cart
187
192
  end
188
193
 
189
194
  When "I sign in to my account" do
190
- ui.sign_in(:my_account)
195
+ ui.sign_in
191
196
  end
192
197
 
193
198
  When "I choose to check out" do
@@ -199,11 +204,13 @@ might look:
199
204
  end
200
205
 
201
206
  Then "I see that my default payment options will be used" do
202
- ui.order_summary.payment_options.should == k.get_data(:default_payment_options)[:my_account]
207
+ ui.order_summary.payment_options.should match_mental_model_of(:default_payment_options)
208
+ # Or if you prefer Test::Unit style assertions...
209
+ # assert_mental_model_matches(:default_payment_options, ui.order_summary.payment_options)
203
210
  end
204
211
 
205
212
  Then "I see that my default shipping options will be used" do
206
- ui.order_summary.shipping_options.should == k.get_data(:default_shipping_options)[:my_account]
213
+ ui.order_summary.shipping_options.should match_mental_model_of(:default_shipping_options)
207
214
  end
208
215
 
209
216
  The step definitions contain neither explicitly shared state (instance
@@ -281,17 +288,17 @@ share a `MentalModel` instance, which is available to both of them via their
281
288
 
282
289
  The `MentalModel` instance will return a `MentalModel::Collection` for any method
283
290
  called on the object. The `MentalModel::Collection` object behaves like a `Hash`
284
- for the most part, however it will raise a `Kookaburra::UnknownKeyError` if you
291
+ for the most part; however, it will raise a `Kookaburra::UnknownKeyError` if you
285
292
  try to access a key that has not yet been assigned a value.
286
293
 
287
294
  Deletions (via `#delete` or `#delete_if`) will actually remove the key/value
288
- pair from the collection, but add it to a subcollection (available at
295
+ pair from the collection, but add it to a sub-collection (available at
289
296
  `MentalModel::Collection#deleted`). This reflects the fact that the user's
290
297
  mental model of the dataset would also include any intentional exceptions -
291
- the user will, for example, want to verify that an item they deleted does
298
+ the user will, for example, want to verify that an item they deleted does
292
299
  not appear to be available in the system.
293
300
 
294
- Here's a quick example of MentalModel behavor:
301
+ Here's an example of MentalModel behavior:
295
302
 
296
303
  mental_model = MentalModel.new
297
304
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.22.3
1
+ 0.23.0
data/kookaburra.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "kookaburra"
8
- s.version = "0.22.3"
8
+ s.version = "0.23.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Wilger", "Sam Livingston-Gray", "Ravi Gadad"]
12
- s.date = "2012-03-26"
12
+ s.date = "2012-04-02"
13
13
  s.description = "Cucumber + Capybara = Kookaburra? It made sense at the time."
14
14
  s.email = "johnwilger@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  "lib/kookaburra/given_driver.rb",
38
38
  "lib/kookaburra/json_api_driver.rb",
39
39
  "lib/kookaburra/mental_model.rb",
40
+ "lib/kookaburra/mental_model_matcher.rb",
40
41
  "lib/kookaburra/test_helpers.rb",
41
42
  "lib/kookaburra/ui_driver.rb",
42
43
  "lib/kookaburra/ui_driver/ui_component.rb",
@@ -45,7 +46,9 @@ Gem::Specification.new do |s|
45
46
  "spec/kookaburra/api_driver_spec.rb",
46
47
  "spec/kookaburra/configuration_spec.rb",
47
48
  "spec/kookaburra/json_api_driver_spec.rb",
49
+ "spec/kookaburra/mental_model_matcher_spec.rb",
48
50
  "spec/kookaburra/mental_model_spec.rb",
51
+ "spec/kookaburra/test_helpers_spec.rb",
49
52
  "spec/kookaburra/ui_driver/ui_component/address_bar_spec.rb",
50
53
  "spec/kookaburra/ui_driver/ui_component_spec.rb",
51
54
  "spec/kookaburra/ui_driver_spec.rb",
data/lib/kookaburra.rb CHANGED
@@ -62,10 +62,7 @@ class Kookaburra
62
62
  @ui ||= @ui_driver_class.new(@configuration)
63
63
  end
64
64
 
65
- # Returns a frozen copy of the specified {MentalModel::Collection}.
66
- # However, this is neither a deep copy nor a deep freeze, so it is possible
67
- # that you could modify data outside of your GivenDriver or UIDriver. Just
68
- # don't do that. Trust me.
65
+ # Returns a deep-dup of the specified {MentalModel::Collection}.
69
66
  #
70
67
  # This access is provided so that you can reference the current mental model
71
68
  # within your test implementation and make assertions about the state
@@ -74,10 +71,16 @@ class Kookaburra
74
71
  # @example
75
72
  # given.a_widget(:foo)
76
73
  # ui.create_a_new_widget(:bar)
77
- # ui.widget_list.widgets.should == k.get_data(:widgets).slice(:foo, :bar)
74
+ # ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:foo, :bar)
78
75
  #
79
76
  # @return [Kookaburra::MentalModel::Collection]
80
77
  def get_data(collection_name)
81
- @configuration.mental_model.send(collection_name).dup.freeze
78
+ @configuration.mental_model.send(collection_name).dup
79
+ end
80
+
81
+ private
82
+
83
+ def __mental_model__
84
+ @configuration.mental_model
82
85
  end
83
86
  end
@@ -66,5 +66,16 @@ class Kookaburra
66
66
  @server_error_detection
67
67
  end
68
68
  end
69
+
70
+ # The parsed version of the {#app_host}
71
+ #
72
+ # This is useful if, for example, you are testing a multi-host application
73
+ # and need to change the hostname that will be accessed but want to keep the
74
+ # originally-specified port
75
+ #
76
+ # @return [URI] A URI object created from the {#app_host} string
77
+ def app_host_uri
78
+ URI.parse(app_host)
79
+ end
69
80
  end
70
81
  end
@@ -1,5 +1,6 @@
1
1
  require 'delegate'
2
2
  require 'kookaburra/exceptions'
3
+ require 'active_support/core_ext/hash'
3
4
 
4
5
  class Kookaburra
5
6
  # Each instance of {Kookaburra} has its own instance of MentalModel. This object
@@ -50,11 +51,13 @@ class Kookaburra
50
51
  class Collection < SimpleDelegator
51
52
  # @param [String] name The name of the collection. Used to provide
52
53
  # helpful error messages when unknown keys are accessed.
53
- def initialize(name)
54
+ # @param [Hash] init_data Preloads specific data into the collection
55
+ def initialize(name, init_data = nil)
54
56
  @name = name
55
57
  data = Hash.new do |hash, key|
56
58
  raise UnknownKeyError, "Can't find mental_model.#{@name}[#{key.inspect}]. Did you forget to set it?"
57
59
  end
60
+ data.merge!(init_data) unless init_data.nil?
58
61
  super(data)
59
62
  end
60
63
 
@@ -66,23 +69,6 @@ class Kookaburra
66
69
  self.object_id == other.object_id
67
70
  end
68
71
 
69
- # Returns the values of multiple keys from the collection.
70
- #
71
- # Unlike the `Hash#slice` implementation provided by ActiveSupport, this
72
- # method returns an array of the values rather than a Hash.
73
- #
74
- # @param keys a list of keys to fetch from the collection.
75
- #
76
- # @return [Array] the values matching the specified index keys
77
- #
78
- # @raise [Kookaburra::UnknownKeyError] if any of the specified keys have
79
- # not been set
80
- def slice(*keys)
81
- results = keys.map do |key|
82
- self[key]
83
- end
84
- end
85
-
86
72
  # Deletes a key/value pair from the collection, and persists the deleted pair
87
73
  # in a subcollection.
88
74
  #
@@ -121,6 +107,12 @@ class Kookaburra
121
107
  move = lambda { |k,v| deleted[k] = v; true }
122
108
  super { |k,v| block.call(k,v) && move.call(k,v) }
123
109
  end
110
+
111
+ def dup
112
+ new_data = {}.merge(self)
113
+ new_data = Marshal.load(Marshal.dump(new_data))
114
+ self.class.new(@name, new_data)
115
+ end
124
116
  end
125
117
  end
126
118
  end
@@ -0,0 +1,130 @@
1
+ require 'kookaburra/mental_model'
2
+
3
+ class Kookaburra
4
+ class MentalModel
5
+ # This is a custom matcher that matches the RSpec matcher API.
6
+ #
7
+ # @see Kookaburra::TestHelpers#match_mental_model_of
8
+ # @see Kookaburra::TestHelpers#assert_mental_model_of
9
+ class Matcher
10
+ def initialize(mental_model, collection_key)
11
+ @collection_key = collection_key
12
+
13
+ mental_model.send(collection_key).tap do |collection|
14
+ @expected = collection
15
+ @unexpected = collection.deleted
16
+ end
17
+ end
18
+
19
+ # Specifies that result should be limited to the given keys from the
20
+ # mental model.
21
+ #
22
+ # Useful if you are looking at a filtered result. That is, your mental
23
+ # model contains elements { A, B, C }, but you only expect to see element
24
+ # A.
25
+ #
26
+ # @param [Array] collection_keys The keys used in your mental model to
27
+ # reference the data
28
+ # @return [self]
29
+ def only(*collection_keys)
30
+ keepers = @expected.slice(*collection_keys)
31
+ tossers = @expected.except(*collection_keys)
32
+
33
+ @expected = keepers
34
+ @unexpected.merge! tossers
35
+
36
+ self
37
+ end
38
+
39
+ # Reads better than {#only} with no args
40
+ #
41
+ # @return [self]
42
+ def expecting_nothing
43
+ only
44
+ end
45
+
46
+ # The result contains everything that was expected to be found and nothing
47
+ # that was unexpected.
48
+ #
49
+ # (Part of the RSpec protocol for custom matchers.)
50
+ #
51
+ # @param [Array] actual This is the data observed that you are attempting
52
+ # to match against the mental model.
53
+ # @return Boolean
54
+ def matches?(actual)
55
+ @actual = actual
56
+ expected_items_not_found.empty? && unexpected_items_found.empty?
57
+ end
58
+
59
+ # Message to be printed when observed reality does not conform to
60
+ # mental model.
61
+ #
62
+ # (Part of the RSpec protocol for custom matchers.)
63
+ #
64
+ # @return String
65
+ def failure_message_for_should
66
+ message = "expected #{@collection_key} to match the user's mental model, but:\n"
67
+ if expected_items_not_found.present?
68
+ message += "expected to be present: #{pp_array(expected_items)}\n"
69
+ message += "the missing elements were: #{pp_array(expected_items_not_found)}\n"
70
+ end
71
+ if unexpected_items_found.present?
72
+ message += "expected to not be present: #{pp_array(unexpected_items)}\n"
73
+ message += "the unexpected extra elements: #{pp_array(unexpected_items_found)}\n"
74
+ end
75
+ message
76
+ end
77
+
78
+ # Message to be printed when observed reality does conform to mental
79
+ # model, but you did not expect it to. (To be honest, we can't think of
80
+ # why you would want this, but it is included for the sake of RSpec
81
+ # compatibility.)
82
+ #
83
+ # (Part of the RSpec protocol for custom matchers.)
84
+ #
85
+ # @return String
86
+ def failure_message_for_should_not
87
+ "expected #{@collection_key} not to match the user's mental model"
88
+ end
89
+
90
+ # (Part of the RSpec protocol for custom matchers.)
91
+ #
92
+ # @return String
93
+ def description
94
+ "match the user's mental model of #{@collection_key}"
95
+ end
96
+
97
+ private
98
+
99
+ def expected_items; @expected.values; end
100
+ def unexpected_items; @unexpected.values; end
101
+
102
+ def expected_items_not_found
103
+ difference_between_arrays(expected_items, @actual)
104
+ end
105
+
106
+ def unexpected_items_found
107
+ unexpected_items_not_found = difference_between_arrays(unexpected_items, @actual)
108
+ difference_between_arrays(unexpected_items, unexpected_items_not_found)
109
+ end
110
+
111
+ # (Swiped from RSpec's array matcher)
112
+ # Returns the difference of arrays, accounting for duplicates.
113
+ # e.g., difference_between_arrays([1, 2, 3, 3], [1, 2, 3]) # => [3]
114
+ def difference_between_arrays(array_1, array_2)
115
+ difference = array_1.dup
116
+ array_2.each do |element|
117
+ if index = difference.index(element)
118
+ difference.delete_at(index)
119
+ end
120
+ end
121
+ difference
122
+ end
123
+
124
+ def pp_array(array)
125
+ array = array.sort if array.all? { |e| e.respond_to?(:<=>) }
126
+ array.inspect
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,4 +1,5 @@
1
1
  require 'kookaburra'
2
+ require 'kookaburra/mental_model_matcher'
2
3
  require 'active_support/core_ext/module/delegation'
3
4
 
4
5
  class Kookaburra
@@ -96,5 +97,26 @@ class Kookaburra
96
97
  # @method ui
97
98
  # Delegates to {#k}
98
99
  delegate :ui, :to => :k
100
+
101
+ # RSpec-style custom matcher that compares a given array with
102
+ # the current state of one named collection in the mental model
103
+ #
104
+ # @see Kookaburra::MentalModel::Matcher
105
+ def match_mental_model_of(collection_key)
106
+ MentalModel::Matcher.new(k.send(:__mental_model__), collection_key)
107
+ end
108
+
109
+ # Custom assertion for Test::Unit-style tests
110
+ # (really, anything that uses #assert(predicate, message = nil))
111
+ #
112
+ # @see Kookaburra::MentalModel::Matcher
113
+ def assert_mental_model_matches(collection_key, actual, message = nil)
114
+ matcher = match_mental_model_of(collection_key)
115
+ result = matcher.matches?(actual)
116
+ return if !!result # don't even bother
117
+
118
+ message ||= matcher.failure_message_for_should
119
+ assert result, message
120
+ end
99
121
  end
100
122
  end
@@ -294,7 +294,7 @@ describe "testing a Rack application with Kookaburra" do
294
294
 
295
295
  def delete_widget(name)
296
296
  assert widget_list.visible?, "Widget list is not visible!"
297
- widget_list.choose_to_delete_widget(mental_model.widgets[name])
297
+ widget_list.choose_to_delete_widget(mental_model.widgets.delete(name))
298
298
  end
299
299
  end
300
300
 
@@ -336,13 +336,24 @@ describe "testing a Rack application with Kookaburra" do
336
336
 
337
337
  ui.sign_in(:bob)
338
338
  ui.view_widget_list
339
- ui.widget_list.widgets.should == k.get_data(:widgets).slice(:widget_a, :widget_b)
339
+
340
+ # The following two lines are two different ways to shave the yak, but
341
+ # the second one does more to match against the full state of the mental
342
+ # model, provides better failure messages, and is shorter.
343
+ ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:widget_a, :widget_b)
344
+ ui.widget_list.widgets.should match_mental_model_of(:widgets)
340
345
 
341
346
  ui.create_new_widget(:widget_c, :name => 'Bar')
342
- ui.widget_list.widgets.should == k.get_data(:widgets).slice(:widget_a, :widget_b, :widget_c)
347
+
348
+ # As above, these are equivalent, but the second line is preferred.
349
+ ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:widget_a, :widget_b, :widget_c)
350
+ ui.widget_list.widgets.should match_mental_model_of(:widgets)
343
351
 
344
352
  ui.delete_widget(:widget_b)
345
- ui.widget_list.widgets.should == k.get_data(:widgets).slice(:widget_a, :widget_c)
353
+
354
+ # As above, these are equivalent, but the second line is preferred.
355
+ ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:widget_a, :widget_c)
356
+ ui.widget_list.widgets.should match_mental_model_of(:widgets)
346
357
  end
347
358
  end
348
359
  end
@@ -15,4 +15,24 @@ describe Kookaburra::Configuration do
15
15
  subject.server_error_detection.should == block
16
16
  end
17
17
  end
18
+
19
+ describe '#app_host_uri' do
20
+ it 'returns a URI version of the #app_host attribute via URI.parse' do
21
+ URI.should_receive(:parse) \
22
+ .with('http://example.com') \
23
+ .and_return(:a_parsed_uri)
24
+ subject.app_host = 'http://example.com'
25
+ subject.app_host_uri.should == :a_parsed_uri
26
+ end
27
+
28
+ it 'changes if #app_host changes' do
29
+ URI.stub!(:parse) do |url|
30
+ url.to_sym
31
+ end
32
+ subject.app_host = 'http://example.com'
33
+ subject.app_host_uri.should == 'http://example.com'.to_sym
34
+ subject.app_host = 'http://foo.example.com'
35
+ subject.app_host_uri.should == 'http://foo.example.com'.to_sym
36
+ end
37
+ end
18
38
  end
@@ -0,0 +1,234 @@
1
+ require 'kookaburra/mental_model_matcher'
2
+
3
+ # Makes the specs themselves a bit less verbose. You should probably read the
4
+ # specs first, though.
5
+ module MentalModelMatcherMacros
6
+ def self.included(receiver)
7
+ receiver.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def pp_array(array)
12
+ array = array.sort if array.all? { |e| e.respond_to?(:<=>) }
13
+ array.inspect
14
+ end
15
+
16
+ def it_matches
17
+ it "matches" do
18
+ matcher.matches?(target).should be_true
19
+ end
20
+ end
21
+
22
+ def it_doesnt_match
23
+ it "doesn't match" do
24
+ matcher.matches?(target).should be_false
25
+ end
26
+ end
27
+
28
+ def from_line
29
+ '(line %d)' % caller(2).first.split(':').last
30
+ end
31
+
32
+ def it_complains_about_missing(missing, options)
33
+ expected = options[:expected]
34
+ it "complains about a mismatch #{from_line}" do
35
+ failure_msg.should include("expected widgets to match the user's mental model, but:")
36
+ end
37
+ it "says what it expected to be present #{from_line}" do
38
+ failure_msg.should include("expected to be present: #{pp_array(expected)}")
39
+ end
40
+ it "complains about missing items #{from_line}" do
41
+ failure_msg.should include("the missing elements were: #{pp_array(missing)}")
42
+ end
43
+ end
44
+
45
+ def it_complains_about_extra(extra, options)
46
+ unexpected = options[:unexpected]
47
+ it "complains about a mismatch #{from_line}" do
48
+ failure_msg.should include("expected widgets to match the user's mental model, but:")
49
+ end
50
+ it "says what it expected not to find #{from_line}" do
51
+ failure_msg.should include("expected to not be present: #{pp_array(unexpected)}")
52
+ end
53
+ it "complains about extra items #{from_line}" do
54
+ failure_msg.should include("the unexpected extra elements: #{pp_array(extra)}")
55
+ end
56
+ end
57
+ end
58
+
59
+ def matcher_for(collection_key)
60
+ Kookaburra::MentalModel::Matcher.new(mm, collection_key).tap do |m|
61
+ m.matches?(target)
62
+ end
63
+ end
64
+
65
+ def pp_array(array)
66
+ self.class.pp_array(array)
67
+ end
68
+ end
69
+
70
+ describe Kookaburra::MentalModel::Matcher do
71
+ include MentalModelMatcherMacros
72
+
73
+ let(:mm) { Kookaburra::MentalModel.new }
74
+ let(:matcher) { matcher_for(:widgets) }
75
+ let(:failure_msg) { matcher.failure_message_for_should }
76
+
77
+ def self.foo; 'FOO' ; end
78
+ def self.bar; 'BAR' ; end
79
+ def self.yak; 'YAK' ; end
80
+ let(:foo) { self.class.foo }
81
+ let(:bar) { self.class.bar }
82
+ let(:yak) { self.class.yak }
83
+
84
+ context "when mental model is [foo];" do
85
+ before(:each) do
86
+ mm.widgets[:foo] = foo
87
+ end
88
+
89
+ context "for [] (foo missing)" do
90
+ let(:target) { [] }
91
+ it_doesnt_match
92
+ it_complains_about_missing [foo], :expected => [foo]
93
+ end
94
+
95
+ context "for [foo] (OK: exact match)" do
96
+ let(:target) { [foo] }
97
+ it_matches
98
+ end
99
+
100
+ context "for [foo, bar] (OK: bar ignored)" do
101
+ let(:target) { [foo, bar] }
102
+ it_matches
103
+ end
104
+ end
105
+
106
+ context "when mental model is [];" do
107
+ context "for [] (OK)" do
108
+ let(:target) { [] }
109
+ it_matches
110
+ end
111
+
112
+ context "for [foo] (OK: foo ignored)" do
113
+ let(:target) { [foo] }
114
+ it_matches
115
+ end
116
+ end
117
+
118
+ context "when mental model is [foo, bar];" do
119
+ before(:each) do
120
+ mm.widgets[:foo] = foo
121
+ mm.widgets[:bar] = bar
122
+ end
123
+
124
+ context "for [] (foo, bar missing)" do
125
+ let(:target) { [] }
126
+ it_doesnt_match
127
+ it_complains_about_missing [foo, bar], :expected => [foo, bar]
128
+ end
129
+
130
+ context "for [foo] (bar missing)" do
131
+ let(:target) { [foo] }
132
+ it_doesnt_match
133
+ it_complains_about_missing [bar], :expected => [foo, bar]
134
+ end
135
+
136
+ context "for [foo, bar] (OK: exact match)" do
137
+ let(:target) { [foo, bar] }
138
+ it_matches
139
+ end
140
+
141
+ context "for [foo, bar, yak] (OK: foo, bar expected; yak ignored)" do
142
+ let(:target) { [foo, bar, yak] }
143
+ it_matches
144
+ end
145
+ end
146
+
147
+ context "when mental model is [foo], not expecting [bar];" do
148
+ before(:each) do
149
+ mm.widgets[:foo] = foo
150
+ mm.widgets[:bar] = bar
151
+ mm.widgets.delete(:bar)
152
+ end
153
+
154
+ context "for [] (foo missing)" do
155
+ let(:target) { [] }
156
+ it_doesnt_match
157
+ it_complains_about_missing [foo], :expected => [foo]
158
+ end
159
+
160
+ context "for [bar] (foo missing, bar deleted)" do
161
+ let(:target) { [bar] }
162
+ it_doesnt_match
163
+ it_complains_about_missing [foo], :expected => [foo]
164
+ it_complains_about_extra [bar], :unexpected => [bar]
165
+ end
166
+
167
+ context "for [foo, bar] (bar deleted)" do
168
+ let(:target) { [foo, bar] }
169
+ it_doesnt_match
170
+ it_complains_about_extra [bar], :unexpected => [bar]
171
+ end
172
+
173
+ context "for [foo] (OK: foo expected, bar not found)" do
174
+ let(:target) { [foo] }
175
+ it_matches
176
+ end
177
+
178
+ context "for [foo, yak] (OK: foo expected; yak ignored)" do
179
+ let(:target) { [foo, yak] }
180
+ it_matches
181
+ end
182
+ end
183
+
184
+ describe "postfix scoping methods" do
185
+ context "when mental model is [foo, bar];" do
186
+ before(:each) do
187
+ mm.widgets[:foo] = foo
188
+ mm.widgets[:bar] = bar
189
+ end
190
+
191
+ context "but scoped to .only(:foo)" do
192
+ let(:matcher) { matcher_for(:widgets).only(:foo) }
193
+
194
+ context "for [foo] (OK)" do
195
+ let(:target) { [foo] }
196
+ it_matches
197
+ end
198
+
199
+ context "for [foo, bar] (not expecting [bar])" do
200
+ let(:target) { [foo, bar] }
201
+ it_doesnt_match
202
+ it_complains_about_extra [bar], :unexpected => [bar]
203
+ end
204
+ end
205
+ end
206
+
207
+ context "when mental model is [foo];" do
208
+ before(:each) do
209
+ mm.widgets[:foo] = foo
210
+ end
211
+
212
+ context "but scoped with .expecting_nothing" do
213
+ let(:matcher) { matcher_for(:widgets).expecting_nothing }
214
+
215
+ context "for [] (OK)" do
216
+ let(:target) { [] }
217
+ it_matches
218
+ end
219
+
220
+ context "for [foo] (unexpected foo)" do
221
+ let(:target) { [foo] }
222
+ it_doesnt_match
223
+ it_complains_about_extra [foo], :unexpected => [foo]
224
+ end
225
+
226
+ context "for [foo, bar] (unexpected [foo]; bar ignored)" do
227
+ let(:target) { [foo, bar] }
228
+ it_doesnt_match
229
+ it_complains_about_extra [foo], :unexpected => [foo]
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
@@ -14,12 +14,21 @@ describe Kookaburra::MentalModel do
14
14
  describe Kookaburra::MentalModel::Collection do
15
15
  let(:collection) { Kookaburra::MentalModel::Collection.new('widgets') }
16
16
 
17
- describe '#slice' do
17
+ describe '#values_at' do
18
18
  it 'returns an array of items matching the specified keys' do
19
19
  collection[:foo] = 'foo'
20
20
  collection[:bar] = 'bar'
21
21
  collection[:baz] = 'baz'
22
- collection.slice(:foo, :baz).should == %w(foo baz)
22
+ collection.values_at(:foo, :baz).should == %w(foo baz)
23
+ end
24
+ end
25
+
26
+ describe '#slice' do
27
+ it 'returns a hash of items matching the specified keys' do
28
+ collection[:foo] = 'foo'
29
+ collection[:bar] = 'bar'
30
+ collection[:baz] = 'baz'
31
+ collection.slice(:foo, :baz).should == { :foo => 'foo', :baz => 'baz' }
23
32
  end
24
33
  end
25
34
 
@@ -81,5 +90,27 @@ describe Kookaburra::MentalModel do
81
90
  lambda { collection[:foo] }.should \
82
91
  raise_error(Kookaburra::UnknownKeyError, "Can't find mental_model.widgets[:foo]. Did you forget to set it?")
83
92
  end
93
+
94
+ describe '#dup' do
95
+ it 'returns a different object' do
96
+ new_collection = collection.dup
97
+ new_collection.__id__.should_not === collection.__id__
98
+ end
99
+
100
+ it 'returns an object with equal values to the original' do
101
+ collection[:foo] = :bar
102
+ collection[:baz] = :bam
103
+ new_collection = collection.dup
104
+ new_collection[:foo].should == :bar
105
+ new_collection[:baz].should == :bam
106
+ end
107
+
108
+ it 'is a deep copy' do
109
+ collection[:foo] = {:bar => 'baz'}
110
+ new_collection = collection.dup
111
+ new_collection[:foo][:bar].should == 'baz'
112
+ new_collection[:foo][:bar].__id__.should_not === collection[:foo][:bar].__id__
113
+ end
114
+ end
84
115
  end
85
116
  end
@@ -0,0 +1,81 @@
1
+ require 'kookaburra/test_helpers'
2
+
3
+ describe Kookaburra::TestHelpers do
4
+ include Kookaburra::TestHelpers
5
+
6
+ before(:all) do
7
+ Kookaburra.configure do |c|
8
+ c.given_driver_class = Kookaburra::GivenDriver
9
+ c.ui_driver_class = Kookaburra::UIDriver
10
+ end
11
+ end
12
+
13
+ after(:all) do
14
+ Kookaburra.configure do |c|
15
+ c.given_driver_class = nil
16
+ c.ui_driver_class = nil
17
+ end
18
+ end
19
+
20
+ describe "#k" do
21
+ it "returns an instance of Kookaburra" do
22
+ k.should be_kind_of(Kookaburra)
23
+ end
24
+
25
+ it "memoizes" do
26
+ a = k; b = k
27
+ a.should equal(b)
28
+ end
29
+ end
30
+
31
+ describe "methods delegated to #k" do
32
+ it "includes #given" do
33
+ k.should_receive(:given)
34
+ given
35
+ end
36
+
37
+ it "includes #ui" do
38
+ k.should_receive(:ui)
39
+ ui
40
+ end
41
+ end
42
+
43
+ describe "methods related to the mental model" do
44
+ before(:each) do
45
+ mm = k.send(:__mental_model__)
46
+ mm.widgets[:foo] = 'FOO'
47
+ end
48
+
49
+ describe "#match_mental_model_of" do
50
+ it "does a positive match" do
51
+ ['FOO'].should match_mental_model_of(:widgets)
52
+ end
53
+
54
+ it "does a negative match" do
55
+ ['BAR'].should_not match_mental_model_of(:widgets)
56
+ end
57
+ end
58
+
59
+ describe "#assert_mental_model_matches" do
60
+ it "does a positive assertion" do
61
+ actual = ['FOO']
62
+ actual.should match_mental_model_of(:widgets) # Sanity check
63
+ self.should_receive(:assert).never
64
+ self.assert_mental_model_matches(:widgets, actual)
65
+ end
66
+
67
+ it "does a negative assertion" do
68
+ actual = ['BAR']
69
+ self.should_receive(:assert).with(false, kind_of(String))
70
+ self.assert_mental_model_matches(:widgets, actual)
71
+ end
72
+
73
+ it "does a negative assertion with a custom message" do
74
+ actual = ['YAK']
75
+ psa = 'Put the razor down and step away!'
76
+ self.should_receive(:assert).with(false, psa)
77
+ self.assert_mental_model_matches(:widgets, actual, psa)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -30,18 +30,12 @@ describe Kookaburra do
30
30
  end
31
31
 
32
32
  describe '#get_data' do
33
- it 'returns a equivalent copy of the test data collection specified' do
34
- foos = {:spam => 'ham'}
35
- configuration.stub!(:mental_model => stub(:foos => foos))
36
- k.get_data(:foos).should == foos
37
- end
38
-
39
- it 'does not return the same object that is the test data collection' do
40
- k.get_data(:foos).should_not === k.get_data(:foos)
41
- end
42
-
43
- it 'returns a frozen object' do
44
- k.get_data(:foos).should be_frozen
33
+ it 'returns a dup of the specified MentalModel::Collection' do
34
+ collection = stub('MentalModel::Collection')
35
+ collection.should_receive(:dup) \
36
+ .and_return(:mental_model_collection_dup)
37
+ configuration.stub!(:mental_model => stub(:foos => collection))
38
+ k.get_data(:foos).should == :mental_model_collection_dup
45
39
  end
46
40
  end
47
41
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kookaburra
3
3
  version: !ruby/object:Gem::Version
4
- hash: 65
4
+ hash: 67
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 22
9
- - 3
10
- version: 0.22.3
8
+ - 23
9
+ - 0
10
+ version: 0.23.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - John Wilger
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2012-03-26 00:00:00 Z
20
+ date: 2012-04-02 00:00:00 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  prerelease: false
@@ -205,6 +205,7 @@ files:
205
205
  - lib/kookaburra/given_driver.rb
206
206
  - lib/kookaburra/json_api_driver.rb
207
207
  - lib/kookaburra/mental_model.rb
208
+ - lib/kookaburra/mental_model_matcher.rb
208
209
  - lib/kookaburra/test_helpers.rb
209
210
  - lib/kookaburra/ui_driver.rb
210
211
  - lib/kookaburra/ui_driver/ui_component.rb
@@ -213,7 +214,9 @@ files:
213
214
  - spec/kookaburra/api_driver_spec.rb
214
215
  - spec/kookaburra/configuration_spec.rb
215
216
  - spec/kookaburra/json_api_driver_spec.rb
217
+ - spec/kookaburra/mental_model_matcher_spec.rb
216
218
  - spec/kookaburra/mental_model_spec.rb
219
+ - spec/kookaburra/test_helpers_spec.rb
217
220
  - spec/kookaburra/ui_driver/ui_component/address_bar_spec.rb
218
221
  - spec/kookaburra/ui_driver/ui_component_spec.rb
219
222
  - spec/kookaburra/ui_driver_spec.rb