Merge lp:~henninge/launchpad/bug-824435-failure-reporting-2 into lp:launchpad

Proposed by Henning Eggers
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 13766
Proposed branch: lp:~henninge/launchpad/bug-824435-failure-reporting-2
Merge into: lp:launchpad
Prerequisite: lp:~henninge/launchpad/bug-824435-failure-reporting
Diff against target: 964 lines (+479/-213)
12 files modified
lib/lp/app/javascript/client.js (+5/-6)
lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js (+10/-15)
lib/lp/app/javascript/picker/tests/test_picker_patcher.js (+20/-31)
lib/lp/app/javascript/testing/iorecorder.js (+0/-60)
lib/lp/app/javascript/testing/mockio.js (+147/-67)
lib/lp/app/javascript/testing/tests/test_mockio.html (+21/-0)
lib/lp/app/javascript/testing/tests/test_mockio.js (+242/-0)
lib/lp/app/javascript/tests/test_lp_client.html (+1/-1)
lib/lp/app/javascript/tests/test_lp_client.js (+14/-12)
lib/lp/code/javascript/requestbuild_overlay.js (+3/-6)
lib/lp/code/javascript/tests/test_requestbuild_overlay.html (+1/-1)
lib/lp/code/javascript/tests/test_requestbuild_overlay.js (+15/-14)
To merge this branch: bzr merge lp:~henninge/launchpad/bug-824435-failure-reporting-2
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+72546@code.launchpad.net

Commit message

[r=gmb][bug=824435][no-qa][incr] Merge lazr.testing.mockio and lp.testing.iorecorder to become lp.testing.mockio.

Description of the change

= Summary =

Since the Thunderdome we had two Javascript modules to mock Y.io in
tests: lazr.testing.mockio and lp.testing.iorecorder. They both were
pretty similar in their approach so merging the two was not only
necessary but also fairly easy. The new module is lp.testing.mockio
and contains the best of both previous modules.

I was surprised to find that these were only used in so few places but
that made transitioning the old code to the new mockio a quick task,
too.

I noticed this because I had started to extend IORecorder's
functionality for the orginial fixing of the error reporting in the
requestbuild_overlay code. So this branch is still part of that
series.

== Proposed fix ==

Merge iorecorder.js code into mockio.js and call it lp.testing.mockio.

Add tests for the new module, the old code was completely untested.

Also clean up the interface and provide some helpers to make
instrumenting the code under test less intrusive.

== Pre-implementation notes ==

I talked to Aaron and Deryck about this but the need for this merge
was really too blatantly obvious.

== Implementation details ==

There is a test in test_picker_patcher.js that was improved (i.e.
shortened) a great deal because the new MockIo learned from IORecorder
how to handle multiple requests.

The mockio.js file looks as if it is completely replace and although
that is almost true, I also added some indentation which changed
most lines in the file.

Looking at test_success_helper__status_override and its evil twin
test_failure_helper__status_override I think it would be better to
raise an exception when the status code does is not correct but I
don't know how to test for assertions being raised.

While iorecorder passed the "io" function to the instrumented function
under test, mockio passes a object that has an "io" method. I picked
the latter approach because it mirrors "Y.io" more closely and does
away with the need to Y.bind the io function.

I exposed MockHttpResponse in the module, like iorecorder did because
there is a test that uses just that.

Any thing else that needs explainin, just ask. ;-)

== Tests ==

firefox lib/lp/app/javascript/formoverlay/tests/test_formoverlay.html
firefox lib/lp/app/javascript/picker/tests/test_picker_patcher.html
firefox lib/lp/app/javascript/testing/tests/test_mockio.html
firefox lib/lp/app/javascript/tests/test_lp_client.html
firefox lib/lp/code/javascript/tests/test_requestbuild_overlay.html

== Demo and Q/A ==

No QA, only test code touched.

= Launchpad lint =

As the diff is already big enough, I will not fix the remaining lint
in requestbuild_overlay.js. That file will be worked on in the
follow-up branch anyway.

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/app/javascript/tests/test_lp_client.js
  lib/lp/code/javascript/tests/test_requestbuild_overlay.html
  lib/lp/app/javascript/client.js
  lib/lp/app/javascript/tests/test_lp_client.html
  lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js
  lib/lp/app/javascript/testing/tests/test_mockio.js
  lib/lp/code/javascript/requestbuild_overlay.js
  lib/lp/app/javascript/testing/tests/test_mockio.html
  lib/lp/code/javascript/tests/test_requestbuild_overlay.js
  lib/lp/app/javascript/picker/tests/test_picker_patcher.js
  lib/lp/app/javascript/testing/mockio.js

./lib/lp/code/javascript/requestbuild_overlay.js
      40: Expected '===' and instead saw '=='.
      45: Expected '!==' and instead saw '!='.
      70: Expected '===' and instead saw '=='.
      72: ['name'] is better written in dot notation.
     111: Expected '===' and instead saw '=='.
     112: Expected '===' and instead saw '=='.
     113: Expected '{' and instead saw 'header'.
     116: Expected '{' and instead saw 'header'.
     119: Expected '!==' and instead saw '!='.
     121: Expected '===' and instead saw '=='.
     224: Expected '!==' and instead saw '!='.
     225: Expected '{' and instead saw 'error_msg'.
     236: Use the array literal notation [].
     258: Expected '{' and instead saw 'return'.
     272: Expected ';' and instead saw 'if'.
     273: Expected '===' and instead saw '=='.
     287: Expected '{' and instead saw 'return'.
     321: ['builds'] is better written in dot notation.
     323: ['already_pending'] is better written in dot notation.
     325: ['errors'] is better written in dot notation.
     328: Expected '!==' and instead saw '!='.
     340: Expected '!==' and instead saw '!='.
     341: Expected '!==' and instead saw '!='.
     342: Expected '{' and instead saw 'error_header_text'.
     344: Expected '{' and instead saw 'error_header_text'.
     347: Move 'var' declarations to the top of the function.
     347: Stopping. (67% scanned).
       0: JSLINT had a fatal error.
     161: Line has trailing whitespace.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/javascript/client.js'
2--- lib/lp/app/javascript/client.js 2011-07-15 18:03:04 +0000
3+++ lib/lp/app/javascript/client.js 2011-08-23 10:50:30 +0000
4@@ -200,11 +200,8 @@
5 on: on,
6 'arguments': [entry.lp_client, url, old_on_success, false]
7 };
8- var io = config.io;
9- if (!Y.Lang.isValue(io)) {
10- io = Y.io;
11- }
12- io(url, y_config);
13+ var io_provider = Y.lp.testing.mockio.io_provider_config(config);
14+ io_provider.io(url, y_config);
15 };
16
17
18@@ -1131,4 +1128,6 @@
19 Y.namespace('lp.client.plugins');
20 Y.lp.client.plugins.PATCHPlugin = PATCHPlugin;
21
22- }, "0.1", {"requires": ["plugin", "dump", "lazr.editor", "lp.client"]});
23+ }, "0.1", {"requires": [
24+ "plugin", "dump", "lazr.editor", "lp.testing.mockio",
25+ "lp.client"]});
26
27=== modified file 'lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js'
28--- lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js 2011-08-19 15:28:07 +0000
29+++ lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js 2011-08-23 10:50:30 +0000
30@@ -1,8 +1,8 @@
31-/* Copyright (c) 2008, Canonical Ltd. All rights reserved. */
32+/* Copyright (c) 2008-2011, Canonical Ltd. All rights reserved. */
33
34 YUI().use('lp.testing.runner', 'test', 'dump', 'console', 'node',
35 'lazr.formoverlay', 'event', 'event-simulate',
36- 'lazr.testing.mockio', function(Y) {
37+ 'lp.testing.mockio', function(Y) {
38
39 var Assert = Y.Assert; // For easy access to isTrue(), etc.
40
41@@ -442,16 +442,14 @@
42 var form_overlay = make_form_overlay({
43 headerContent: 'Form for testing'
44 });
45- var mock_io = new Y.lazr.testing.MockIo();
46+ var mock_io = new Y.lp.testing.mockio.MockIo();
47 form_overlay.loadFormContentAndRender(
48 'http://example.com/form', mock_io);
49
50 // loadFormContentAndRender calls .io() to issue an XHR. Simulate a
51 // successful response, to make sure that the form content gets
52 // set and rendered.
53- var response = Y.lazr.testing.MockIo.makeXhrSuccessResponse(
54- external_form_content);
55- mock_io.simulateXhr(response, false);
56+ mock_io.success({responseText: external_form_content});
57
58 Assert.areEqual(
59 external_form_content, form_overlay.get('form_content'),
60@@ -472,18 +470,17 @@
61 var form_overlay = make_form_overlay({
62 headerContent: 'Form for testing'
63 });
64- var mock_io = new Y.lazr.testing.MockIo();
65+ var mock_io = new Y.lp.testing.mockio.MockIo();
66 form_overlay.loadFormContentAndRender(
67 'http://example.com/form', mock_io);
68
69 // loadFormContentAndRender calls .io() to issue an XHR. Simulate a
70 // failed response, to make sure that the error message gets set
71 // and rendered.
72- var response = Y.lazr.testing.MockIo.makeXhrFailureResponse(
73- 'failure');
74- mock_io.simulateXhr(response, true);
75+ mock_io.failure();
76
77- var error_message = "Sorry, an error occurred while loading the form.";
78+ var error_message = "Sorry, an error occurred " +
79+ "while loading the form.";
80 Assert.areEqual(
81 error_message, form_overlay.get('form_content'),
82 "Failure to set form content.");
83@@ -505,7 +502,7 @@
84 headerContent: 'Form for testing',
85 form_submit_callback: submit_callback
86 });
87- var mock_io = new Y.lazr.testing.MockIo();
88+ var mock_io = new Y.lp.testing.mockio.MockIo();
89 form_overlay.loadFormContentAndRender(
90 'http://example.com/form', mock_io);
91
92@@ -513,9 +510,7 @@
93 // successful response, to make sure that the submit button get
94 // hooked up to the form_submit_call.
95 var external_form_content = '<div id="loaded-content"></div>';
96- var response = Y.lazr.testing.MockIo.makeXhrSuccessResponse(
97- external_form_content);
98- mock_io.simulateXhr(response, false);
99+ mock_io.success({responseText: external_form_content});
100 simulate(
101 form_overlay.form_node,
102 "input[type=submit]",
103
104=== modified file 'lib/lp/app/javascript/picker/tests/test_picker_patcher.js'
105--- lib/lp/app/javascript/picker/tests/test_picker_patcher.js 2011-08-22 12:13:22 +0000
106+++ lib/lp/app/javascript/picker/tests/test_picker_patcher.js 2011-08-23 10:50:30 +0000
107@@ -3,7 +3,7 @@
108 YUI().use('lp.testing.runner', 'test', 'console', 'node', 'lp', 'lp.client',
109 'event-focus', 'event-simulate', 'lazr.picker', 'lazr.person-picker',
110 'lp.app.picker', 'node-event-simulate', 'escape', 'event',
111- 'lazr.testing.mockio',
112+ 'lp.testing.mockio',
113 function(Y) {
114
115 var Assert = Y.Assert;
116@@ -364,7 +364,7 @@
117 },
118
119 create_picker: function() {
120- this.mock_io = new Y.lazr.testing.MockIo();
121+ this.mock_io = new Y.lp.testing.mockio.MockIo();
122 this.picker = Y.lp.app.picker.addPickerPatcher(
123 "Foo",
124 "foo/bar",
125@@ -373,24 +373,17 @@
126 {yio: this.mock_io});
127 },
128
129- make_error_response: function(status, oops) {
130- if (oops === undefined) {
131- oops = null;
132- }
133- return {
134- status: status,
135- getResponseHeader: function(header) {
136- if (header === 'X-Lazr-OopsId') {
137- return oops;
138- }
139- }
140- };
141+ get_oops_headers: function(oops) {
142+ var headers = {};
143+ headers['X-Lazr-OopsId'] = oops;
144+ return headers;
145 },
146
147 test_oops: function() {
148 // A 500 (ISE) with an OOPS ID informs the user that we've
149 // logged it, and gives them the OOPS ID.
150- this.mock_io.simulateXhr(this.make_error_response(500, 'OOPS'), true);
151+ this.mock_io.failure(
152+ {responseHeaders: this.get_oops_headers('OOPS')});
153 Assert.areEqual(
154 "Sorry, something went wrong with your search. We've recorded " +
155 "what happened, and we'll fix it as soon as possible. " +
156@@ -401,7 +394,8 @@
157 test_timeout: function() {
158 // A 503 (timeout) or 502/504 (proxy error) informs the user
159 // that they should retry, and gives them the OOPS ID.
160- this.mock_io.simulateXhr(this.make_error_response(503, 'OOPS'), true);
161+ this.mock_io.failure(
162+ {status: 503, responseHeaders: this.get_oops_headers('OOPS')});
163 Assert.areEqual(
164 "Sorry, something went wrong with your search. Trying again " +
165 "in a couple of minutes might work. (Error ID: OOPS)",
166@@ -411,7 +405,7 @@
167 test_other_error: function() {
168 // Any other type of error just displays a generic failure
169 // message, with no OOPS ID.
170- this.mock_io.simulateXhr(this.make_error_response(400), true);
171+ this.mock_io.failure({status: 400});
172 Assert.areEqual(
173 "Sorry, something went wrong with your search.",
174 this.picker.get('error'));
175@@ -451,25 +445,20 @@
176 // If an automated search (like loading branch suggestions) returns
177 // results and the user has submitted a search, then the results of
178 // the automated search are ignored so as not to confuse the user.
179- var mock_io = new Y.lazr.testing.MockIo();
180+ var mock_io = new Y.lp.testing.mockio.MockIo();
181 var picker = this.create_picker(mock_io);
182 // First an automated search is run.
183 picker.fire('search', 'guess', undefined, true);
184- // We have to stash away the mock IO's on-success handler because
185- // it'll get clobbered by the second searc if we don't.
186- automated_success = mock_io.cfg.on.success;
187 // Then the user initiates their own search.
188 picker.fire('search', 'test');
189- // Now to get ahold of the user-initiated search's on-success.
190- user_success = mock_io.cfg.on.success;
191- // Now, if the automated search returns...
192- mock_io.cfg.on.success = automated_success;
193- mock_io.simulateXhr(this.make_response(200, null, '{"entries": 1}'));
194+ // Two requests have been sent out.
195+ Y.Assert.areEqual(2, mock_io.requests.length);
196+ // Respond to the automated request.
197+ mock_io.requests[0].respond({responseText: '{"entries": 1}'});
198 // ... the results are ignored.
199 Assert.areNotEqual(1, picker.get('results'));
200- // But if the user's search returns, the results are kept.
201- mock_io.cfg.on.success = user_success;
202- mock_io.simulateXhr(this.make_response(200, null, '{"entries": 2}'));
203+ // Respond to the user request.
204+ mock_io.requests[1].respond({responseText: '{"entries": 2}'});
205 Assert.areEqual(2, picker.get('results'));
206 cleanup_widget(picker);
207 },
208@@ -478,11 +467,11 @@
209 // If an automated search (like loading branch suggestions) returns an
210 // error and the user has submitted a search, then the error from the
211 // automated search is ignored so as not to confuse the user.
212- var mock_io = new Y.lazr.testing.MockIo();
213+ var mock_io = new Y.lp.testing.mockio.MockIo();
214 var picker = this.create_picker(mock_io);
215 picker.fire('search', 'test');
216 picker.fire('search', 'guess', undefined, true);
217- mock_io.simulateXhr(this.make_response(500, 'OOPS'), true);
218+ mock_io.failure();
219 Assert.areEqual(null, picker.get('error'));
220 cleanup_widget(picker);
221 }
222
223=== removed file 'lib/lp/app/javascript/testing/iorecorder.js'
224--- lib/lp/app/javascript/testing/iorecorder.js 2011-08-19 19:32:11 +0000
225+++ lib/lp/app/javascript/testing/iorecorder.js 1970-01-01 00:00:00 +0000
226@@ -1,60 +0,0 @@
227-/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
228-
229-YUI.add('lp.testing.iorecorder', function(Y) {
230- var namespace = Y.namespace("lp.testing.iorecorder");
231-
232- function MockHttpResponse (status) {
233- this.status = status;
234- this.responseText = '[]';
235- this.responseHeaders = {};
236- }
237-
238- MockHttpResponse.prototype = {
239- setResponseHeader: function (header, value) {
240- this.responseHeaders[header] = value;
241- },
242-
243- getResponseHeader: function(header) {
244- return this.responseHeaders[header];
245- }
246- };
247- namespace.MockHttpResponse = MockHttpResponse;
248-
249-
250- function IORecorderRequest(url, config){
251- this.url = url;
252- this.config = config;
253- this.response = null;
254- }
255-
256-
257- IORecorderRequest.prototype.respond = function(status, value, headers){
258- this.response = new MockHttpResponse(status);
259- this.response.setResponseHeader(
260- 'Content-Type', headers['Content-Type']);
261- this.response.responseText = value;
262- var callback;
263- if (status === 200) {
264- callback = this.config.on.success;
265- } else {
266- callback = this.config.on.failure;
267- }
268- callback(null, this.response, this.config['arguments']);
269- };
270-
271-
272- IORecorderRequest.prototype.success = function(value, headers){
273- this.respond(200, value, headers);
274- };
275-
276-
277- function IORecorder(){
278- this.requests = [];
279- }
280-
281-
282- IORecorder.prototype.do_io = function(url, config){
283- this.requests.push(new IORecorderRequest(url, config));
284- };
285- namespace.IORecorder = IORecorder;
286-}, '0.1', {});
287
288=== modified file 'lib/lp/app/javascript/testing/mockio.js'
289--- lib/lp/app/javascript/testing/mockio.js 2011-07-06 05:13:30 +0000
290+++ lib/lp/app/javascript/testing/mockio.js 2011-08-23 10:50:30 +0000
291@@ -1,69 +1,149 @@
292-/* Copyright (c) 2009, Canonical Ltd. All rights reserved. */
293-
294-YUI.add('lazr.testing.mockio', function(Y) {
295-/**
296- * A utility module for use in YUI unit-tests with a helper for mocking Y.io.
297- *
298- * @module lazr.testing
299- */
300-var MockIo = function() {
301- this.uri = null;
302- this.cfg = null;
303-};
304-
305-/* Save the Y.io() arguments. */
306-MockIo.prototype.io = function(uri, cfg) {
307- this.uri = uri;
308- this.cfg = cfg;
309- return this; // Usually this isn't used, except for logging.
310-};
311-
312-/* Simulate the Xhr request/response cycle. */
313-MockIo.prototype.simulateXhr = function(response, is_failure) {
314- var cfg = this.cfg;
315- var context = cfg.context || this;
316- var args = cfg.arguments;
317- var tId = 'mockTId';
318- if (!response) {
319- response = {};
320- }
321-
322- // See the Y.io utility documentation for the signatures.
323- if (cfg.on.start) {
324- cfg.on.start.call(context, tId, args);
325- }
326- if (cfg.on.complete) {
327- cfg.on.complete.call(context, tId, response, args);
328- }
329- if (cfg.on.success && !is_failure) {
330- cfg.on.success.call(context, tId, response, args);
331- }
332- if (cfg.on.failure && is_failure) {
333- cfg.on.failure.call(context, tId, response, args);
334- }
335-};
336-
337-/* Make a successful XHR response object. */
338-MockIo.makeXhrSuccessResponse = function(responseText) {
339- var text = responseText || "";
340- return {
341- status: 200,
342- statusText: "OK",
343- responseText: text
344- };
345-};
346-
347-/* Make a failed XHR response object. */
348-MockIo.makeXhrFailureResponse = function(responseText) {
349- var text = responseText || "";
350- return {
351- status: 500,
352- statusText: "Internal Server Error",
353- responseText: text
354- };
355-};
356-
357-Y.namespace("lazr.testing");
358-Y.lazr.testing.MockIo = MockIo;
359+/* Copyright (c) 2009-2011, Canonical Ltd. All rights reserved. */
360+
361+YUI.add('lp.testing.mockio', function(Y) {
362+ /**
363+ * A utility module for use in YUI unit-tests with a helper for
364+ * mocking Y.io.
365+ *
366+ * @module lp.testing.mockio
367+ */
368+ var namespace = Y.namespace("lp.testing.mockio");
369+
370+ var MockHttpResponse = function(config) {
371+ if (config === undefined) {
372+ config = {};
373+ }
374+ if (config.status !== undefined) {
375+ this.status = config.status;
376+ } else {
377+ this.status = 200;
378+ }
379+ if (config.statusText !== undefined) {
380+ this.statusText = config.statusText;
381+ } else {
382+ if (this.isFailure()) {
383+ this.statusText = "Internal Server Error";
384+ } else {
385+ this.statusText = "OK";
386+ }
387+ }
388+ if (config.responseText !== undefined) {
389+ this.responseText = config.responseText;
390+ } else {
391+ this.responseText = '[]';
392+ }
393+ if (config.responseHeaders !== undefined) {
394+ this.responseHeaders = config.responseHeaders;
395+ } else {
396+ this.responseHeaders = {};
397+ }
398+ };
399+
400+ MockHttpResponse.prototype = {
401+ isFailure: function () {
402+ return this.status >= 400;
403+ },
404+
405+ setResponseHeader: function (header, value) {
406+ this.responseHeaders[header] = value;
407+ },
408+
409+ getResponseHeader: function(header) {
410+ return this.responseHeaders[header];
411+ }
412+ };
413+ namespace.MockHttpResponse = MockHttpResponse;
414+
415+ function MockHttpRequest(url, config){
416+ this.url = url;
417+ this.config = config;
418+ this.response = null;
419+ }
420+
421+ /* Simulate the Xhr request/response cycle. */
422+ MockHttpRequest.prototype.respond = function(response_config) {
423+ var context = this.config.context || Y,
424+ args = this.config['arguments'] || [],
425+ tId = 'mockTId',
426+ response = this.response = new MockHttpResponse(response_config);
427+
428+ // See the Y.io utility documentation for the signatures.
429+ if (this.config.on.start !== undefined) {
430+ this.config.on.start.call(context, tId, args);
431+ }
432+ if (this.config.on.complete !== undefined) {
433+ this.config.on.complete.call(context, tId, response, args);
434+ }
435+ if (this.config.on.success !== undefined && !response.isFailure()) {
436+ this.config.on.success.call(context, tId, response, args);
437+ }
438+ if (this.config.on.failure !== undefined && response.isFailure()) {
439+ this.config.on.failure.call(context, tId, response, args);
440+ }
441+ };
442+
443+ namespace.MockHttpRequest = MockHttpRequest;
444+
445+ var MockIo = function() {
446+ this.requests = [];
447+ this.last_request = null;
448+ };
449+
450+ /* Save the Y.io() arguments. */
451+ MockIo.prototype.io = function(url, config) {
452+ this.last_request = new MockHttpRequest(url, config);
453+ this.requests.push(this.last_request);
454+ return this; // Usually this isn't used, except for logging.
455+ };
456+
457+ /* Call respond method on last_request. */
458+ MockIo.prototype.respond = function(response_config) {
459+ this.last_request.respond(response_config);
460+ };
461+
462+ /* Call respond method with successful values. */
463+ MockIo.prototype.success = function(config) {
464+ if (config === undefined) {
465+ config = {};
466+ }
467+ if (config.status === undefined || config.status >= 400) {
468+ config.status = 200;
469+ }
470+ config.statusText = 'OK';
471+ this.respond(config);
472+ };
473+
474+ /* Call respond method with failed values. */
475+ MockIo.prototype.failure = function(config) {
476+ if (config === undefined) {
477+ config = {};
478+ }
479+ if (config.status === undefined || config.status < 400) {
480+ config.status = 500;
481+ }
482+ config.statusText = 'Internal Server Error';
483+ this.respond(config);
484+ };
485+
486+ namespace.MockIo = MockIo;
487+
488+ /* Helper to select the io_provider. */
489+ namespace.io_provider = function(mockio) {
490+ if (mockio === undefined) {
491+ return Y;
492+ }
493+ return mockio;
494+ };
495+
496+ /* Helper to select the io_provider from a config. */
497+ namespace.io_provider_config = function(config, key) {
498+ if (key === undefined) {
499+ key = 'io_provider';
500+ }
501+ if (config === undefined || config[key] === undefined) {
502+ return Y;
503+ }
504+ return config[key];
505+ };
506
507 }, '0.1', {});
508
509=== added directory 'lib/lp/app/javascript/testing/tests'
510=== added file 'lib/lp/app/javascript/testing/tests/test_mockio.html'
511--- lib/lp/app/javascript/testing/tests/test_mockio.html 1970-01-01 00:00:00 +0000
512+++ lib/lp/app/javascript/testing/tests/test_mockio.html 2011-08-23 10:50:30 +0000
513@@ -0,0 +1,21 @@
514+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
515+<html>
516+ <head>
517+ <title>Launchpad lp.testing.mockio module</title>
518+
519+ <!-- YUI and test setup -->
520+ <script type="text/javascript"
521+ src="../../../../../canonical/launchpad/icing/yui/yui/yui.js">
522+ </script>
523+ <link rel="stylesheet" href="../test.css" />
524+ <script type="text/javascript" src="../testrunner.js"></script>
525+
526+ <!-- The module under test -->
527+ <script type="text/javascript" src="../mockio.js"></script>
528+
529+ <!-- The test suite -->
530+ <script type="text/javascript" src="test_mockio.js"></script>
531+</head>
532+<body class="yui3-skin-sam">
533+</body>
534+</html>
535
536=== added file 'lib/lp/app/javascript/testing/tests/test_mockio.js'
537--- lib/lp/app/javascript/testing/tests/test_mockio.js 1970-01-01 00:00:00 +0000
538+++ lib/lp/app/javascript/testing/tests/test_mockio.js 2011-08-23 10:50:30 +0000
539@@ -0,0 +1,242 @@
540+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
541+
542+YUI().use('test', 'console', 'node-event-simulate',
543+ 'lp.testing.mockio', 'lp.testing.runner', function(Y) {
544+
545+var suite = new Y.Test.Suite("lp.testing.mockio Tests");
546+
547+var module = Y.lp.testing.mockio;
548+
549+var make_call_recorder = function() {
550+ var recorder;
551+ recorder = function() {
552+ recorder.call_count += 1;
553+ recorder.args = arguments;
554+ };
555+ recorder.call_count = 0;
556+ recorder.args = null;
557+ return recorder;
558+};
559+
560+suite.add(new Y.Test.Case({
561+ name: "lp.testing.mokio.MockIo",
562+
563+ test_url: "https://launchpad.dev/test/url",
564+
565+ setUp: function() {
566+ // Initialize call_count on recorders.
567+ this.test_config = {
568+ on: {
569+ start: make_call_recorder(),
570+ complete: make_call_recorder(),
571+ success: make_call_recorder(),
572+ failure: make_call_recorder()
573+ },
574+ context: {marker: "context"},
575+ 'arguments': ["arguments"]
576+ };
577+ },
578+
579+ _make_mockio: function() {
580+ var mockio = new module.MockIo();
581+ mockio.io(this.test_url, this.test_config);
582+ return mockio;
583+ },
584+
585+ test_respond_success: function() {
586+ // The success handler is called on success.
587+ var mockio = this._make_mockio();
588+ mockio.respond({status: 200});
589+ Y.Assert.areEqual(1, this.test_config.on.start.call_count);
590+ Y.Assert.areEqual(1, this.test_config.on.complete.call_count);
591+ Y.Assert.areEqual(1, this.test_config.on.success.call_count);
592+ Y.Assert.areEqual(0, this.test_config.on.failure.call_count);
593+ },
594+
595+ test_respond_failure: function() {
596+ // The failure handler is called on failure.
597+ var mockio = this._make_mockio();
598+ mockio.respond({status: 500});
599+ Y.Assert.areEqual(1, this.test_config.on.start.call_count);
600+ Y.Assert.areEqual(1, this.test_config.on.complete.call_count);
601+ Y.Assert.areEqual(0, this.test_config.on.success.call_count);
602+ Y.Assert.areEqual(1, this.test_config.on.failure.call_count);
603+ },
604+
605+ test_multiple_requests: function() {
606+ // Multiple requests are stored.
607+ var mockio = new module.MockIo();
608+ mockio.io(this.test_url, this.test_config);
609+ mockio.io(this.test_url, this.test_config);
610+ Y.Assert.areEqual(2, mockio.requests.length);
611+ },
612+
613+ test_last_request: function() {
614+ // The last request is available through last_request.
615+ var mockio = new module.MockIo();
616+ mockio.io("Request 1", this.test_config);
617+ mockio.io("Request 2", this.test_config);
618+ Y.Assert.areEqual("Request 2", mockio.last_request.url);
619+ },
620+
621+ test_status: function() {
622+ // The status is passed to the handler.
623+ var mockio = this._make_mockio();
624+ var expected_status = 503;
625+ mockio.respond({status: expected_status});
626+ Y.Assert.areEqual(
627+ expected_status, this.test_config.on.failure.args[1].status);
628+ },
629+
630+ test_statusText: function() {
631+ // The statusText is passed to the handler.
632+ var mockio = this._make_mockio();
633+ var expected_status_text = "All is well";
634+ mockio.respond({statusText: expected_status_text});
635+ Y.Assert.areEqual(
636+ expected_status_text,
637+ this.test_config.on.success.args[1].statusText);
638+ },
639+
640+ test_responseText: function() {
641+ // The responseText is passed to the handler.
642+ var mockio = this._make_mockio();
643+ var expected_response_text = "myresponse";
644+ mockio.respond({responseText: expected_response_text});
645+ Y.Assert.areEqual(
646+ expected_response_text,
647+ this.test_config.on.success.args[1].responseText);
648+ },
649+
650+ test_responseHeader: function() {
651+ // A response header is passed to the handler.
652+ var mockio = this._make_mockio();
653+ var response = new Y.lp.testing.mockio.MockHttpResponse();
654+ var expected_header_key = "X-My-Header",
655+ expected_header_val = "MyHeaderValue",
656+ response_headers = {};
657+ response.setResponseHeader(expected_header_key, expected_header_val);
658+ mockio.respond(response);
659+ var headers = this.test_config.on.success.args[1].responseHeaders;
660+ Y.Assert.areEqual(expected_header_val, headers[expected_header_key]);
661+ },
662+
663+ test_success_helper: function() {
664+ // The success helper creates a successful response.
665+ var mockio = this._make_mockio(),
666+ response_text = "Success!";
667+ mockio.success({responseText: response_text});
668+ Y.Assert.areEqual(1, this.test_config.on.success.call_count);
669+ Y.Assert.areEqual(0, this.test_config.on.failure.call_count);
670+ Y.Assert.areEqual(
671+ response_text, mockio.last_request.response.responseText);
672+ },
673+
674+ test_success_helper__own_status: function() {
675+ // The failure can define its own non-4xx or non-5xx status.
676+ var mockio = this._make_mockio(),
677+ status = 302;
678+ mockio.success({status: status});
679+ Y.Assert.areEqual(1, this.test_config.on.success.call_count);
680+ Y.Assert.areEqual(0, this.test_config.on.failure.call_count);
681+ Y.Assert.areEqual(status, mockio.last_request.response.status);
682+ },
683+
684+ test_success_helper__status_override: function() {
685+ // A status that is 4xx or 5xx is overridden to be 200.
686+ // This is to guard against foot shooting.
687+ var mockio = this._make_mockio(),
688+ own_status = 500,
689+ real_status = 200;
690+ mockio.success({status: own_status});
691+ Y.Assert.areEqual(1, this.test_config.on.success.call_count);
692+ Y.Assert.areEqual(0, this.test_config.on.failure.call_count);
693+ Y.Assert.areEqual(real_status, mockio.last_request.response.status);
694+ },
695+
696+ test_failure_helper: function() {
697+ // The failure helper creates a failed response.
698+ var mockio = this._make_mockio(),
699+ response_text = "Failure!";
700+ mockio.failure({responseText: response_text});
701+ Y.Assert.areEqual(0, this.test_config.on.success.call_count);
702+ Y.Assert.areEqual(1, this.test_config.on.failure.call_count);
703+ Y.Assert.areEqual(
704+ response_text, mockio.last_request.response.responseText);
705+ },
706+
707+ test_failure_helper__own_status: function() {
708+ // The failure can define its own 4xx or 5xx status.
709+ var mockio = this._make_mockio(),
710+ status = 404;
711+ mockio.failure({status: status});
712+ Y.Assert.areEqual(0, this.test_config.on.success.call_count);
713+ Y.Assert.areEqual(1, this.test_config.on.failure.call_count);
714+ Y.Assert.areEqual(status, mockio.last_request.response.status);
715+ },
716+
717+ test_failure_helper__status_override: function() {
718+ // A status that is not 4xx or 5xx is overridden to be 500.
719+ // This is to guard against foot shooting.
720+ var mockio = this._make_mockio(),
721+ own_status = 200,
722+ real_status = 500;
723+ mockio.failure({status: own_status});
724+ Y.Assert.areEqual(0, this.test_config.on.success.call_count);
725+ Y.Assert.areEqual(1, this.test_config.on.failure.call_count);
726+ Y.Assert.areEqual(real_status, mockio.last_request.response.status);
727+ }
728+}));
729+
730+suite.add(new Y.Test.Case({
731+ name: "lp.testing.mokio.io_provider",
732+
733+ setUp: function() {
734+ },
735+
736+ test_io_provider__default: function() {
737+ var mockio,
738+ io_provider = module.io_provider(mockio);
739+ Y.Assert.areSame(Y, io_provider);
740+ },
741+
742+ test_io_provider__mockio: function() {
743+ // If mockio is provided, it is picked as the io_provider.
744+ var mockio = new module.MockIo(),
745+ io_provider = module.io_provider(mockio);
746+ Y.Assert.areSame(mockio, io_provider);
747+ },
748+
749+ test_io_provider_config__default: function() {
750+ // If no io_provider is configured, Y is the io_provider.
751+ var io_provider = module.io_provider_config({});
752+ Y.Assert.areSame(Y, io_provider);
753+ },
754+
755+ test_io_provider_config__default_undefined: function() {
756+ // If no configuration is provided, Y is the io_provider.
757+ var io_provider = module.io_provider_config();
758+ Y.Assert.areSame(Y, io_provider);
759+ },
760+
761+ test_io_provider_config__mockio: function() {
762+ // If io_provider is configured, it is picked as the io_provider.
763+ var mockio = new module.MockIo(),
764+ io_provider = module.io_provider_config(
765+ {io_provider: mockio});
766+ Y.Assert.areSame(mockio, io_provider);
767+ },
768+
769+ test_io_provider_config__different_key: function() {
770+ // The io_provider can be stored with a different key.
771+ var mockio = new module.MockIo(),
772+ io_provider = module.io_provider_config(
773+ {my_io: mockio}, 'my_io');
774+ Y.Assert.areSame(mockio, io_provider);
775+ }
776+}));
777+
778+
779+Y.lp.testing.Runner.run(suite);
780+
781+});
782
783=== modified file 'lib/lp/app/javascript/tests/test_lp_client.html'
784--- lib/lp/app/javascript/tests/test_lp_client.html 2011-07-15 20:01:48 +0000
785+++ lib/lp/app/javascript/tests/test_lp_client.html 2011-08-23 10:50:30 +0000
786@@ -11,7 +11,7 @@
787 <script type="text/javascript"
788 src="../../../app/javascript/testing/testrunner.js"></script>
789 <script type="text/javascript"
790- src="../../../app/javascript/testing/iorecorder.js"></script>
791+ src="../../../app/javascript/testing/mockio.js"></script>
792
793 <!-- The module under test -->
794 <script type="text/javascript" src="../lp.js"></script>
795
796=== modified file 'lib/lp/app/javascript/tests/test_lp_client.js'
797--- lib/lp/app/javascript/tests/test_lp_client.js 2011-07-28 15:25:04 +0000
798+++ lib/lp/app/javascript/tests/test_lp_client.js 2011-08-23 10:50:30 +0000
799@@ -1,6 +1,6 @@
800 /* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
801
802-YUI().use('lp.testing.runner', 'lp.testing.iorecorder', 'test', 'console',
803+YUI().use('lp.testing.runner', 'lp.testing.mockio', 'test', 'console',
804 'lp.client', 'escape', function(Y) {
805
806 var Assert = Y.Assert; // For easy access to isTrue(), etc.
807@@ -74,11 +74,11 @@
808 Assert.areEqual(expected, actual);
809 },
810 test_load_model: function(){
811- var recorder = new Y.lp.testing.iorecorder.IORecorder();
812- Assert.areEqual(0, recorder.requests.length);
813+ var mockio = new Y.lp.testing.mockio.MockIo();
814+ Assert.areEqual(0, mockio.requests.length);
815 var mylist = [];
816 var config = {
817- io: Y.bind(recorder.do_io, recorder),
818+ io_provider: mockio,
819 on: {
820 success: Y.bind(mylist.push, mylist)
821 }
822@@ -87,11 +87,13 @@
823 var client = new Y.lp.client.Launchpad();
824 var context = new Y.lp.client.Entry(client, entry_repr, null);
825 Y.lp.client.load_model(context, '+myview', config);
826- var request = recorder.requests[0];
827- Assert.areEqual('/context/+myview/++model++', request.url);
828- request.success(
829- '{"boolean": true, "entry": {"resource_type_link": "foo"}}',
830- {'Content-Type': 'application/json'});
831+ Assert.areEqual(
832+ '/context/+myview/++model++', mockio.last_request.url);
833+ mockio.success({
834+ responseText:
835+ '{"boolean": true, "entry": {"resource_type_link": "foo"}}',
836+ responseHeaders: {'Content-Type': 'application/json'}
837+ });
838 var result = mylist[0];
839 Assert.areSame(true, result.boolean);
840 Assert.isInstanceOf(Y.lp.client.Entry, result.entry);
841@@ -125,8 +127,8 @@
842 Y.Assert.areNotSame(foo, bar);
843 },
844 test_wrap_resource_creates_mapping: function() {
845- // wrap_resource creates new mappings, rather than reusing the existing
846- // one.
847+ // wrap_resource creates new mappings, rather than reusing the
848+ // existing one.
849 var foo = {a: 'b'};
850 var bar = new Y.lp.client.Launchpad().wrap_resource(null, foo);
851 Y.Assert.areNotSame(foo, bar);
852@@ -294,7 +296,7 @@
853 setUp: function() {
854 this.client = new Y.lp.client.Launchpad();
855 this.args=[this.client, null, this._on_success, false];
856- this.response = new Y.lp.testing.iorecorder.MockHttpResponse();
857+ this.response = new Y.lp.testing.mockio.MockHttpResponse();
858 this.response.setResponseHeader('Content-Type', 'application/json');
859 },
860
861
862=== modified file 'lib/lp/code/javascript/requestbuild_overlay.js'
863--- lib/lp/code/javascript/requestbuild_overlay.js 2011-08-19 20:43:47 +0000
864+++ lib/lp/code/javascript/requestbuild_overlay.js 2011-08-23 10:50:30 +0000
865@@ -209,11 +209,8 @@
866 },
867 data: qs
868 };
869- var io = Y.io;
870- if ( config !== undefined && Y.Lang.isValue(config.io)) {
871- io = config.io;
872- }
873- io(submit_url, y_config);
874+ var io_provider = Y.lp.testing.mockio.io_provider_config(config);
875+ io_provider.io(submit_url, y_config);
876 });
877
878 // Wire up the processing hooks
879@@ -511,5 +508,5 @@
880 }
881 }, "0.1", {"requires": [
882 "dom", "node", "escape", "io-base", "lp.anim", "lazr.formoverlay",
883- "lp.client"
884+ "lp.client", "lp.testing.mockio"
885 ]});
886
887=== modified file 'lib/lp/code/javascript/tests/test_requestbuild_overlay.html'
888--- lib/lp/code/javascript/tests/test_requestbuild_overlay.html 2011-08-19 19:21:10 +0000
889+++ lib/lp/code/javascript/tests/test_requestbuild_overlay.html 2011-08-23 10:50:30 +0000
890@@ -18,7 +18,7 @@
891 <script type="text/javascript"
892 src="../../../app/javascript/testing/testrunner.js"></script>
893 <script type="text/javascript"
894- src="../../../app/javascript/testing/iorecorder.js"></script>
895+ src="../../../app/javascript/testing/mockio.js"></script>
896
897 <script type="text/javascript"
898 src="../../../app/javascript/client.js"></script>
899
900=== modified file 'lib/lp/code/javascript/tests/test_requestbuild_overlay.js'
901--- lib/lp/code/javascript/tests/test_requestbuild_overlay.js 2011-08-19 20:43:47 +0000
902+++ lib/lp/code/javascript/tests/test_requestbuild_overlay.js 2011-08-23 10:50:30 +0000
903@@ -1,7 +1,7 @@
904 /* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
905
906 YUI().use('test', 'console', 'node-event-simulate',
907- 'lp.testing.iorecorder', 'lp.testing.runner',
908+ 'lp.testing.mockio', 'lp.testing.runner',
909 'lp.code.requestbuild_overlay', function(Y) {
910
911 var suite = new Y.Test.Suite("lp.code.requestbuild_overlay Tests");
912@@ -17,7 +17,7 @@
913 name: "lp.code.requestbuild_overlay",
914
915 setUp: function() {
916- LP.cache['context'] = {
917+ LP.cache.context = {
918 web_link: "http://code.launchpad.dev/~foobar/myrecipe"};
919 // Prepare testbed.
920 var testbed = Y.one("#testbed").set('innerHTML', '');
921@@ -34,21 +34,22 @@
922 },
923
924 _makeRequest: function() {
925- var recorder = new Y.lp.testing.iorecorder.IORecorder();
926+ var mockio = new Y.lp.testing.mockio.MockIo();
927 var build_now_link = Y.one('#request-daily-build');
928 build_now_link.removeClass('unseen');
929- module.connect_requestdailybuild(
930- {io: Y.bind(recorder.do_io, recorder)});
931+ module.connect_requestdailybuild({io_provider: mockio});
932 build_now_link.simulate('click');
933-
934- Y.Assert.areSame(1, recorder.requests.length);
935- return recorder.requests[0];
936+
937+ Y.Assert.areSame(1, mockio.requests.length);
938+ return mockio;
939 },
940-
941+
942 test_requestbuild_success: function() {
943- var request = this._makeRequest();
944- request.success(
945- builds_target_markup, {'Content-Type': 'application/xhtml'});
946+ var mockio = this._makeRequest();
947+ mockio.success({
948+ responseText: builds_target_markup,
949+ responseHeaders: {'Content-Type': 'application/xhtml'}
950+ });
951
952 // The markup has been inserted.
953 Y.Assert.areSame(
954@@ -68,8 +69,8 @@
955
956
957 _testRequestbuildFailure: function(status, expected_message) {
958- var request = this._makeRequest();
959- request.respond(status, '', {});
960+ var mockio = this._makeRequest();
961+ mockio.respond({status: status});
962
963 // No build targets.
964 Y.Assert.areSame(