kookaburra 0.22.3 → 0.23.0

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