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