kookaburra 1.3.1 → 2.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/README.markdown +52 -59
- data/lib/kookaburra.rb +11 -17
- data/lib/kookaburra/api_client.rb +214 -0
- data/lib/kookaburra/api_driver.rb +47 -196
- data/lib/kookaburra/configuration.rb +4 -4
- data/lib/kookaburra/mental_model.rb +16 -4
- data/lib/kookaburra/test_helpers.rb +15 -37
- data/lib/kookaburra/version.rb +1 -1
- data/spec/integration/test_a_rack_application_spec.rb +34 -356
- data/spec/kookaburra/{api_driver_spec.rb → api_client_spec.rb} +14 -14
- data/spec/kookaburra/configuration_spec.rb +6 -6
- data/spec/kookaburra/mental_model_spec.rb +13 -1
- data/spec/kookaburra/test_helpers_spec.rb +5 -44
- data/spec/kookaburra_spec.rb +7 -7
- data/spec/support/json_api_app_and_kookaburra_drivers.rb +372 -0
- metadata +41 -42
- data/lib/kookaburra/given_driver.rb +0 -65
- data/lib/kookaburra/mental_model_matcher.rb +0 -138
- data/spec/kookaburra/mental_model_matcher_spec.rb +0 -237
@@ -1,13 +1,13 @@
|
|
1
|
-
require 'kookaburra/
|
1
|
+
require 'kookaburra/api_client'
|
2
2
|
|
3
|
-
describe Kookaburra::
|
3
|
+
describe Kookaburra::APIClient do
|
4
4
|
def url_for(uri)
|
5
5
|
URI.join('http://example.com', uri).to_s
|
6
6
|
end
|
7
7
|
|
8
8
|
let(:configuration) { double('Configuration', :app_host => 'http://example.com') }
|
9
9
|
|
10
|
-
let(:api) { Kookaburra::
|
10
|
+
let(:api) { Kookaburra::APIClient.new(configuration, client) }
|
11
11
|
|
12
12
|
let(:response) { double('RestClient::Response', body: 'foo', code: 200) }
|
13
13
|
|
@@ -20,20 +20,20 @@ describe Kookaburra::APIDriver do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'returns the response body' do
|
23
|
-
api.send(http_verb, '/foo').
|
23
|
+
expect(api.send(http_verb, '/foo')).to eq 'foo'
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'raises an UnexpectedResponse if the request is not successful' do
|
27
27
|
response.stub(code: 500)
|
28
28
|
client.stub(http_verb).and_raise(RestClient::Exception.new(response))
|
29
|
-
|
30
|
-
.
|
29
|
+
expect{ api.send(http_verb, '/foo') } \
|
30
|
+
.to raise_error(Kookaburra::UnexpectedResponse)
|
31
31
|
end
|
32
32
|
|
33
33
|
let(:expect_client_to_receive_headers) { ->(expected_headers) {
|
34
34
|
# Some HTTP verb methods pass data, some don't, and their arity
|
35
35
|
# is different
|
36
|
-
client.
|
36
|
+
allow(client).to receive(http_verb) do |path, data_or_headers, headers|
|
37
37
|
headers ||= data_or_headers
|
38
38
|
expect(headers).to eq(expected_headers)
|
39
39
|
response
|
@@ -42,7 +42,7 @@ describe Kookaburra::APIDriver do
|
|
42
42
|
|
43
43
|
context 'when custom global headers are specified' do
|
44
44
|
let(:api) {
|
45
|
-
klass = Class.new(Kookaburra::
|
45
|
+
klass = Class.new(Kookaburra::APIClient) do
|
46
46
|
header 'Header-Foo', 'Baz'
|
47
47
|
header 'Header-Bar', 'Bam'
|
48
48
|
end
|
@@ -100,14 +100,14 @@ describe Kookaburra::APIDriver do
|
|
100
100
|
|
101
101
|
context 'when a custom decoder is specified' do
|
102
102
|
let(:api) {
|
103
|
-
klass = Class.new(Kookaburra::
|
103
|
+
klass = Class.new(Kookaburra::APIClient) do
|
104
104
|
decode_with { |data| :some_decoded_data }
|
105
105
|
end
|
106
106
|
klass.new(configuration, client)
|
107
107
|
}
|
108
108
|
|
109
109
|
it "decodes response bodies from requests" do
|
110
|
-
api.send(http_verb, '/foo').
|
110
|
+
expect(api.send(http_verb, '/foo')).to eq :some_decoded_data
|
111
111
|
end
|
112
112
|
end
|
113
113
|
end
|
@@ -121,9 +121,9 @@ describe Kookaburra::APIDriver do
|
|
121
121
|
|
122
122
|
context 'when a custom encoder is specified' do
|
123
123
|
let(:api) {
|
124
|
-
klass = Class.new(Kookaburra::
|
124
|
+
klass = Class.new(Kookaburra::APIClient) do
|
125
125
|
encode_with { |data|
|
126
|
-
data
|
126
|
+
raise "Wrong data!" unless data == :some_ruby_data
|
127
127
|
:some_encoded_data
|
128
128
|
}
|
129
129
|
end
|
@@ -131,7 +131,7 @@ describe Kookaburra::APIDriver do
|
|
131
131
|
}
|
132
132
|
|
133
133
|
it "encodes input to requests" do
|
134
|
-
client.
|
134
|
+
expect(client).to receive(http_verb) do |_, data, _|
|
135
135
|
data.should == :some_encoded_data
|
136
136
|
response
|
137
137
|
end
|
@@ -145,7 +145,7 @@ describe Kookaburra::APIDriver do
|
|
145
145
|
shared_examples_for 'it encodes data as a querystring' do |http_verb|
|
146
146
|
context "(#{http_verb})" do
|
147
147
|
it 'adds data as querystirng params' do
|
148
|
-
client.
|
148
|
+
expect(client).to receive(http_verb).with(url_for('/foo?bar=baz&yak=shaved'), {}) \
|
149
149
|
.and_return(response)
|
150
150
|
api.send(http_verb, '/foo', bar: 'baz', yak: 'shaved')
|
151
151
|
end
|
@@ -2,7 +2,7 @@ require 'kookaburra/configuration'
|
|
2
2
|
require 'support/shared_examples/it_has_a_dependency_accessor'
|
3
3
|
|
4
4
|
describe Kookaburra::Configuration do
|
5
|
-
it_behaves_like :it_has_a_dependency_accessor, :
|
5
|
+
it_behaves_like :it_has_a_dependency_accessor, :api_driver_class
|
6
6
|
it_behaves_like :it_has_a_dependency_accessor, :ui_driver_class
|
7
7
|
it_behaves_like :it_has_a_dependency_accessor, :browser
|
8
8
|
it_behaves_like :it_has_a_dependency_accessor, :app_host
|
@@ -13,13 +13,13 @@ describe Kookaburra::Configuration do
|
|
13
13
|
it 'returns the block that it was last given' do
|
14
14
|
block = lambda { 'foo' }
|
15
15
|
subject.server_error_detection(&block)
|
16
|
-
subject.server_error_detection.
|
16
|
+
expect(subject.server_error_detection).to eq block
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
describe '#app_host_uri' do
|
21
21
|
it 'returns a URI version of the #app_host attribute via URI.parse' do
|
22
|
-
URI.
|
22
|
+
expect(URI).to receive(:parse) \
|
23
23
|
.with('http://example.com') \
|
24
24
|
.and_return(:a_parsed_uri)
|
25
25
|
subject.app_host = 'http://example.com'
|
@@ -27,13 +27,13 @@ describe Kookaburra::Configuration do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'changes if #app_host changes' do
|
30
|
-
URI.
|
30
|
+
allow(URI).to receive(:parse) do |url|
|
31
31
|
url.to_sym
|
32
32
|
end
|
33
33
|
subject.app_host = 'http://example.com'
|
34
|
-
subject.app_host_uri.
|
34
|
+
expect(subject.app_host_uri).to eq 'http://example.com'.to_sym
|
35
35
|
subject.app_host = 'http://foo.example.com'
|
36
|
-
subject.app_host_uri.
|
36
|
+
expect(subject.app_host_uri).to eq 'http://foo.example.com'.to_sym
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -86,7 +86,8 @@ describe Kookaburra::MentalModel do
|
|
86
86
|
describe '#deleted' do
|
87
87
|
it 'generates a new subcollection if none exists' do
|
88
88
|
initialized_collection = collection
|
89
|
-
Kookaburra::MentalModel::Collection.should_receive(:new)
|
89
|
+
Kookaburra::MentalModel::Collection.should_receive(:new) \
|
90
|
+
.with("#{initialized_collection.name}.deleted")
|
90
91
|
initialized_collection.deleted
|
91
92
|
end
|
92
93
|
|
@@ -121,6 +122,17 @@ describe Kookaburra::MentalModel do
|
|
121
122
|
new_collection[:foo][:bar].should == 'baz'
|
122
123
|
new_collection[:foo][:bar].__id__.should_not === collection[:foo][:bar].__id__
|
123
124
|
end
|
125
|
+
|
126
|
+
context 'when there are deleted items present' do
|
127
|
+
it 'also dupes the deleted items' do
|
128
|
+
collection[:foo] = 'foo'
|
129
|
+
collection[:bar] = 'bar'
|
130
|
+
deleted = collection.delete(:bar)
|
131
|
+
new_collection = collection.dup
|
132
|
+
expect(new_collection.deleted[:bar]).to eq deleted
|
133
|
+
expect(new_collection.deleted[:bar]).to_not equal deleted
|
134
|
+
end
|
135
|
+
end
|
124
136
|
end
|
125
137
|
end
|
126
138
|
end
|
@@ -5,14 +5,14 @@ describe Kookaburra::TestHelpers do
|
|
5
5
|
|
6
6
|
before(:all) do
|
7
7
|
Kookaburra.configure do |c|
|
8
|
-
c.
|
8
|
+
c.api_driver_class = Kookaburra::APIDriver
|
9
9
|
c.ui_driver_class = Kookaburra::UIDriver
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
13
|
after(:all) do
|
14
14
|
Kookaburra.configure do |c|
|
15
|
-
c.
|
15
|
+
c.api_driver_class = nil
|
16
16
|
c.ui_driver_class = nil
|
17
17
|
end
|
18
18
|
end
|
@@ -29,9 +29,9 @@ describe Kookaburra::TestHelpers do
|
|
29
29
|
end
|
30
30
|
|
31
31
|
describe "methods delegated to #k" do
|
32
|
-
it "includes #
|
33
|
-
k.should_receive(:
|
34
|
-
|
32
|
+
it "includes #api" do
|
33
|
+
k.should_receive(:api)
|
34
|
+
api
|
35
35
|
end
|
36
36
|
|
37
37
|
it "includes #ui" do
|
@@ -39,43 +39,4 @@ describe Kookaburra::TestHelpers do
|
|
39
39
|
ui
|
40
40
|
end
|
41
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
42
|
end
|
data/spec/kookaburra_spec.rb
CHANGED
@@ -7,14 +7,14 @@ describe Kookaburra do
|
|
7
7
|
|
8
8
|
let(:k) { Kookaburra.new(configuration) }
|
9
9
|
|
10
|
-
describe '#
|
11
|
-
it 'returns an instance of the configured
|
12
|
-
|
13
|
-
|
10
|
+
describe '#api' do
|
11
|
+
it 'returns an instance of the configured APIDriver' do
|
12
|
+
my_api_driver_class = double(Class)
|
13
|
+
my_api_driver_class.should_receive(:new) \
|
14
14
|
.with(configuration) \
|
15
|
-
.and_return(:
|
16
|
-
configuration.stub(:
|
17
|
-
k.
|
15
|
+
.and_return(:an_api_driver)
|
16
|
+
configuration.stub(:api_driver_class => my_api_driver_class)
|
17
|
+
k.api.should == :an_api_driver
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -0,0 +1,372 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# This is the fixture Rack application against which the integration
|
5
|
+
# test will run. It uses class variables to persist data, because
|
6
|
+
# Sinatra will instantiate a new instance of TestRackApp for each
|
7
|
+
# request.
|
8
|
+
class JsonApiApp < Sinatra::Base
|
9
|
+
enable :sessions
|
10
|
+
disable :show_exceptions
|
11
|
+
|
12
|
+
def parse_json_req_body
|
13
|
+
request.body.rewind
|
14
|
+
JSON.parse(request.body.read, :symbolize_names => true)
|
15
|
+
end
|
16
|
+
|
17
|
+
post '/users' do
|
18
|
+
user_data = parse_json_req_body
|
19
|
+
@@users ||= {}
|
20
|
+
@@users[user_data[:email]] = user_data
|
21
|
+
status 201
|
22
|
+
headers 'Content-Type' => 'application/json'
|
23
|
+
body user_data.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
post '/session' do
|
27
|
+
user = @@users[params[:email]]
|
28
|
+
if user && user[:password] == params[:password]
|
29
|
+
session[:logged_in] = true
|
30
|
+
status 200
|
31
|
+
body 'You are logged in!'
|
32
|
+
else
|
33
|
+
session[:logged_in] = false
|
34
|
+
status 403
|
35
|
+
body 'Log in failed!'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/session/new' do
|
40
|
+
body <<-EOF
|
41
|
+
<html>
|
42
|
+
<head>
|
43
|
+
<title>Sign In</title>
|
44
|
+
</head>
|
45
|
+
<body>
|
46
|
+
<div id="sign_in_screen">
|
47
|
+
<form action="/session" method="POST">
|
48
|
+
<label for="email">Email:</label>
|
49
|
+
<input id="email" name="email" type="text" />
|
50
|
+
|
51
|
+
<label for="password">Password:</label>
|
52
|
+
<input id="password" name="password" type="password" />
|
53
|
+
|
54
|
+
<input type="submit" value="Sign In" />
|
55
|
+
</form>
|
56
|
+
</div>
|
57
|
+
</body>
|
58
|
+
</html>
|
59
|
+
EOF
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete_widget(id)
|
63
|
+
@@widgets.delete_if do |w|
|
64
|
+
w[:id] == id
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
post '/widgets/:widget_id' do
|
69
|
+
delete_widget(params['widget_id'])
|
70
|
+
redirect to('/widgets')
|
71
|
+
end
|
72
|
+
|
73
|
+
delete '/widgets/:widget_id' do
|
74
|
+
result = delete_widget(params['widget_id'])
|
75
|
+
status 200
|
76
|
+
body result.to_json
|
77
|
+
end
|
78
|
+
|
79
|
+
get '/widgets/new' do
|
80
|
+
body <<-EOF
|
81
|
+
<html>
|
82
|
+
<head>
|
83
|
+
<title>New Widget</title>
|
84
|
+
</head>
|
85
|
+
<body>
|
86
|
+
<div id="widget_form">
|
87
|
+
<form action="/widgets" method="POST">
|
88
|
+
<label for="name">Name:</label>
|
89
|
+
<input id="name" name="name" type="text" />
|
90
|
+
|
91
|
+
<input type="submit" value="Save" />
|
92
|
+
</form>
|
93
|
+
</div>
|
94
|
+
</body>
|
95
|
+
</html>
|
96
|
+
EOF
|
97
|
+
end
|
98
|
+
|
99
|
+
post '/widgets' do
|
100
|
+
@@widgets ||= []
|
101
|
+
widget_data = if request.media_type == 'application/json'
|
102
|
+
parse_json_req_body
|
103
|
+
else
|
104
|
+
{:name => params['name']}
|
105
|
+
end
|
106
|
+
widget_data[:id] = UUID.new.generate
|
107
|
+
@@widgets << widget_data
|
108
|
+
@@last_widget_created = widget_data
|
109
|
+
request.accept.each do |type|
|
110
|
+
case type.to_s
|
111
|
+
when 'application/json'
|
112
|
+
status 201
|
113
|
+
headers 'Content-Type' => 'application/json'
|
114
|
+
halt widget_data.to_json
|
115
|
+
else
|
116
|
+
halt redirect to('/widgets')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
get '/widgets' do
|
122
|
+
@@widgets ||= []
|
123
|
+
if request.media_type == 'application/json'
|
124
|
+
status 200
|
125
|
+
headers 'Content-Type' => 'application/json'
|
126
|
+
halt @@widgets.to_json
|
127
|
+
else
|
128
|
+
raise "Not logged in!" unless session[:logged_in]
|
129
|
+
last_widget_created, @@last_widget_created = @@last_widget_created, nil
|
130
|
+
content = ''
|
131
|
+
content << <<-EOF
|
132
|
+
<html>
|
133
|
+
<head>
|
134
|
+
<title>Widgets</title>
|
135
|
+
</head>
|
136
|
+
<body>
|
137
|
+
<div id="widget_list">
|
138
|
+
EOF
|
139
|
+
if last_widget_created
|
140
|
+
content << <<-EOF
|
141
|
+
<div class="last_widget created">
|
142
|
+
<span class="id">#{last_widget_created[:id]}</span>
|
143
|
+
<span class="name">#{last_widget_created[:name]}</span>
|
144
|
+
</div>
|
145
|
+
EOF
|
146
|
+
end
|
147
|
+
content << <<-EOF
|
148
|
+
<ul>
|
149
|
+
EOF
|
150
|
+
@@widgets.each do |w|
|
151
|
+
content << <<-EOF
|
152
|
+
<li class="widget_summary">
|
153
|
+
<span class="id">#{w[:id]}</span>
|
154
|
+
<span class="name">#{w[:name]}</span>
|
155
|
+
<form id="delete_#{w[:id]}" action="/widgets/#{w[:id]}" method="POST">
|
156
|
+
<button type="submit" value="Delete" />
|
157
|
+
</form>
|
158
|
+
</li>
|
159
|
+
EOF
|
160
|
+
end
|
161
|
+
content << <<-EOF
|
162
|
+
</ul>
|
163
|
+
<a href="/widgets/new">New Widget</a>
|
164
|
+
</div>
|
165
|
+
</body>
|
166
|
+
</html>
|
167
|
+
EOF
|
168
|
+
body content
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
get '/error_page' do
|
173
|
+
content = <<-EOF
|
174
|
+
<html>
|
175
|
+
<head>
|
176
|
+
<title>Internal Server Error</title>
|
177
|
+
</head>
|
178
|
+
<body>
|
179
|
+
<p>A Purposeful Error</p>
|
180
|
+
</body>
|
181
|
+
</html>
|
182
|
+
EOF
|
183
|
+
body content
|
184
|
+
end
|
185
|
+
|
186
|
+
error do
|
187
|
+
e = request.env['sinatra.error']
|
188
|
+
body << <<-EOF
|
189
|
+
<html>
|
190
|
+
<head>
|
191
|
+
<title>Internal Server Error</title>
|
192
|
+
</head>
|
193
|
+
<body>
|
194
|
+
<pre>
|
195
|
+
#{e.to_s}\n#{e.backtrace.join("\n")}
|
196
|
+
</pre>
|
197
|
+
</body>
|
198
|
+
</html>
|
199
|
+
EOF
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class MyAPIClient < Kookaburra::APIClient
|
204
|
+
encode_with { |data| JSON.dump(data) }
|
205
|
+
decode_with { |data| JSON.parse(data) }
|
206
|
+
header 'Content-Type', 'application/json'
|
207
|
+
header 'Accept', 'application/json'
|
208
|
+
|
209
|
+
def create_user(user_data)
|
210
|
+
post '/users', user_data
|
211
|
+
end
|
212
|
+
|
213
|
+
def create_widget(widget_data)
|
214
|
+
post '/widgets', widget_data
|
215
|
+
end
|
216
|
+
|
217
|
+
def delete_widget(id)
|
218
|
+
delete "/widgets/#{id}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def widgets
|
222
|
+
get '/widgets'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
class MyAPIDriver < Kookaburra::APIDriver
|
227
|
+
def api
|
228
|
+
MyAPIClient.new(configuration)
|
229
|
+
end
|
230
|
+
|
231
|
+
def create_user(name)
|
232
|
+
mental_model.users.fetch(name) { |name|
|
233
|
+
user = {'email' => 'bob@example.com', 'password' => '12345'}
|
234
|
+
result = api.create_user(user)
|
235
|
+
mental_model.users[name] = result
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
def create_widget(name, attributes = {})
|
240
|
+
mental_model.widgets.fetch(name) { |name|
|
241
|
+
widget = {'name' => name}.merge(attributes)
|
242
|
+
result = api.create_widget(widget)
|
243
|
+
mental_model.widgets[name] = result
|
244
|
+
}
|
245
|
+
end
|
246
|
+
|
247
|
+
def delete_widget(name)
|
248
|
+
widget = mental_model.widgets.delete(name)
|
249
|
+
api.delete_widget(widget['id'])
|
250
|
+
end
|
251
|
+
|
252
|
+
def widgets
|
253
|
+
api.widgets
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class SignInScreen < Kookaburra::UIDriver::UIComponent
|
258
|
+
def component_path
|
259
|
+
'/session/new'
|
260
|
+
end
|
261
|
+
|
262
|
+
# Use default component locator value
|
263
|
+
#
|
264
|
+
# def component_locator
|
265
|
+
# '#sign_in_screen'
|
266
|
+
# end
|
267
|
+
|
268
|
+
def sign_in(user_data)
|
269
|
+
fill_in 'Email:', :with => user_data['email']
|
270
|
+
fill_in 'Password:', :with => user_data['password']
|
271
|
+
click_button 'Sign In'
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
class ErrorPage < Kookaburra::UIDriver::UIComponent
|
276
|
+
def component_path
|
277
|
+
'/error_page'
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
class WidgetDataContainer
|
282
|
+
def initialize(element)
|
283
|
+
@element = element
|
284
|
+
end
|
285
|
+
|
286
|
+
def to_hash
|
287
|
+
{
|
288
|
+
'id' => @element.find('.id').text,
|
289
|
+
'name' => @element.find('.name').text
|
290
|
+
}
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
class LastWidgetCreated < Kookaburra::UIDriver::UIComponent
|
295
|
+
def component_locator
|
296
|
+
@options[:component_locator]
|
297
|
+
end
|
298
|
+
|
299
|
+
def data
|
300
|
+
raise "Foo" unless visible?
|
301
|
+
WidgetDataContainer.new(self).to_hash
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
class WidgetList < Kookaburra::UIDriver::UIComponent
|
306
|
+
ui_component :last_widget_created, LastWidgetCreated, :component_locator => '#widget_list .last_widget.created'
|
307
|
+
|
308
|
+
def component_path
|
309
|
+
'/widgets'
|
310
|
+
end
|
311
|
+
|
312
|
+
def component_locator
|
313
|
+
'#widget_list'
|
314
|
+
end
|
315
|
+
|
316
|
+
def widgets
|
317
|
+
all('.widget_summary').map do |el|
|
318
|
+
WidgetDataContainer.new(el).to_hash
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def choose_to_create_new_widget
|
323
|
+
click_on 'New Widget'
|
324
|
+
end
|
325
|
+
|
326
|
+
def choose_to_delete_widget(widget_data)
|
327
|
+
find("#delete_#{widget_data['id']}").click_button('Delete')
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
class WidgetForm < Kookaburra::UIDriver::UIComponent
|
332
|
+
def component_locator
|
333
|
+
'#widget_form'
|
334
|
+
end
|
335
|
+
|
336
|
+
def submit(widget_data)
|
337
|
+
fill_in 'Name:', :with => widget_data['name']
|
338
|
+
click_on 'Save'
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class MyUIDriver < Kookaburra::UIDriver
|
343
|
+
ui_component :error_page, ErrorPage
|
344
|
+
ui_component :sign_in_screen, SignInScreen
|
345
|
+
ui_component :widget_list, WidgetList
|
346
|
+
ui_component :widget_form, WidgetForm
|
347
|
+
|
348
|
+
def sign_in(name)
|
349
|
+
address_bar.go_to sign_in_screen
|
350
|
+
sign_in_screen.sign_in(mental_model.users[name])
|
351
|
+
end
|
352
|
+
|
353
|
+
def error_on_purpose
|
354
|
+
address_bar.go_to error_page
|
355
|
+
end
|
356
|
+
|
357
|
+
def view_widget_list
|
358
|
+
address_bar.go_to widget_list
|
359
|
+
end
|
360
|
+
|
361
|
+
def create_widget(name, attributes = {})
|
362
|
+
assert widget_list.visible?, "Widget list is not visible!"
|
363
|
+
widget_list.choose_to_create_new_widget
|
364
|
+
widget_form.submit('name' => 'My Widget')
|
365
|
+
mental_model.widgets[name] = widget_list.last_widget_created.data
|
366
|
+
end
|
367
|
+
|
368
|
+
def delete_widget(name)
|
369
|
+
assert widget_list.visible?, "Widget list is not visible!"
|
370
|
+
widget_list.choose_to_delete_widget(mental_model.widgets.delete(name))
|
371
|
+
end
|
372
|
+
end
|