Commit ad6ab698 authored by Romain Courteaud's avatar Romain Courteaud

Add service management on gadget.

A service is function which is executed outside the promise tree when the
gadget is attached to the DOM.
Services are stopped when the gadget is removed from the DOM.

You can declare a service with the "declareService" method, which takes a
function as parameter.

Service errors are reported to the parent gadget by acquisition on
"reportServiceError".
If no gadget catched the service error, the application will crash (document
body will display the raw error).
parent eea2661b
...@@ -61,7 +61,9 @@ module.exports = function (grunt) { ...@@ -61,7 +61,9 @@ module.exports = function (grunt) {
'document', 'document',
'DOMParser', 'DOMParser',
'Channel', 'Channel',
'XMLHttpRequest' 'XMLHttpRequest',
'MutationObserver',
'Node'
] ]
} }
}, },
......
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
* renderJs - Generic Gadget library renderer. * renderJs - Generic Gadget library renderer.
* http://www.renderjs.org/documentation * http://www.renderjs.org/documentation
*/ */
(function (document, window, RSVP, DOMParser, Channel, undefined) { (function (document, window, RSVP, DOMParser, Channel, MutationObserver,
Node, undefined) {
"use strict"; "use strict";
var gadget_model_dict = {}, var gadget_model_dict = {},
...@@ -13,8 +14,12 @@ ...@@ -13,8 +14,12 @@
stylesheet_registration_dict = {}, stylesheet_registration_dict = {},
gadget_loading_klass, gadget_loading_klass,
loading_klass_promise, loading_klass_promise,
renderJS; renderJS,
Monitor;
/////////////////////////////////////////////////////////////////
// Helper functions
/////////////////////////////////////////////////////////////////
function removeHash(url) { function removeHash(url) {
var index = url.indexOf('#'); var index = url.indexOf('#');
if (index > 0) { if (index > 0) {
...@@ -23,6 +28,144 @@ ...@@ -23,6 +28,144 @@
return url; return url;
} }
function letsCrash(e) {
if (e.constructor === XMLHttpRequest) {
e = {
readyState: e.readyState,
status: e.status,
statusText: e.statusText,
response_headers: e.getAllResponseHeaders()
};
}
if (e.constructor === Array ||
e.constructor === String ||
e.constructor === Object) {
try {
e = JSON.stringify(e);
} catch (ignore) {
}
}
document.getElementsByTagName('body')[0].textContent = e;
// XXX Do not crash the application if it fails
// Where to write the error?
/*global console*/
console.error(e.stack);
console.error(e);
}
/////////////////////////////////////////////////////////////////
// Service Monitor promise
/////////////////////////////////////////////////////////////////
function ResolvedMonitorError(message) {
this.name = "resolved";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
}
ResolvedMonitorError.prototype = new Error();
ResolvedMonitorError.prototype.constructor = ResolvedMonitorError;
Monitor = function () {
var monitor = this,
promise_list = [],
promise,
reject,
notify,
resolved;
if (!(this instanceof Monitor)) {
return new Monitor();
}
function canceller() {
var len = promise_list.length,
i;
for (i = 0; i < len; i += 1) {
promise_list[i].cancel();
}
// Clean it to speed up other canceller run
promise_list = [];
}
promise = new RSVP.Promise(function (done, fail, progress) {
reject = function (rejectedReason) {
if (resolved) {
return;
}
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason;
resolved = true;
canceller();
return fail(rejectedReason);
};
notify = progress;
}, canceller);
monitor.cancel = function () {
if (resolved) {
return;
}
resolved = true;
promise.cancel();
promise.fail(function (rejectedReason) {
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason;
});
};
monitor.then = function () {
return promise.then.apply(promise, arguments);
};
monitor.fail = function () {
return promise.fail.apply(promise, arguments);
};
monitor.monitor = function (promise_to_monitor) {
if (resolved) {
throw new ResolvedMonitorError();
}
var queue = new RSVP.Queue()
.push(function () {
return promise_to_monitor;
})
.push(function (fulfillmentValue) {
// Promise to monitor is fullfilled, remove it from the list
var len = promise_list.length,
sub_promise_to_monitor,
new_promise_list = [],
i;
for (i = 0; i < len; i += 1) {
sub_promise_to_monitor = promise_list[i];
if (!(sub_promise_to_monitor.isFulfilled ||
sub_promise_to_monitor.isRejected)) {
new_promise_list.push(sub_promise_to_monitor);
}
}
promise_list = new_promise_list;
}, function (rejectedReason) {
if (rejectedReason instanceof RSVP.CancellationError) {
if (!(promise_to_monitor.isFulfilled &&
promise_to_monitor.isRejected)) {
// The queue could be cancelled before the first push is run
promise_to_monitor.cancel();
}
}
reject(rejectedReason);
throw rejectedReason;
}, function (notificationValue) {
notify(notificationValue);
return notificationValue;
});
promise_list.push(queue);
return this;
};
};
Monitor.prototype = Object.create(Promise.prototype);
Monitor.prototype.constructor = Monitor;
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSGadget // RenderJSGadget
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
...@@ -38,8 +181,24 @@ ...@@ -38,8 +181,24 @@
RenderJSGadget.prototype.__required_css_list = []; RenderJSGadget.prototype.__required_css_list = [];
RenderJSGadget.prototype.__required_js_list = []; RenderJSGadget.prototype.__required_js_list = [];
function createMonitor(g) {
if (g.__monitor !== undefined) {
g.__monitor.cancel();
}
g.__monitor = new Monitor();
g.__monitor.fail(function (error) {
if (!(error instanceof RSVP.CancellationError)) {
return g.aq_reportServiceError(error);
}
}).fail(function (error) {
// Crash the application if the acquisition generates an error.
return letsCrash(error);
});
}
function clearGadgetInternalParameters(g) { function clearGadgetInternalParameters(g) {
g.__sub_gadget_dict = {}; g.__sub_gadget_dict = {};
createMonitor(g);
} }
function loadSubGadgetDOMDeclaration(g) { function loadSubGadgetDOMDeclaration(g) {
...@@ -75,6 +234,24 @@ ...@@ -75,6 +234,24 @@
return this; return this;
}; };
RenderJSGadget.__service_list = [];
RenderJSGadget.declareService = function (callback) {
this.__service_list.push(callback);
return this;
};
function startService(gadget) {
gadget.__monitor.monitor(new RSVP.Queue()
.push(function () {
var i,
service_list = gadget.constructor.__service_list;
for (i = 0; i < service_list.length; i += 1) {
gadget.__monitor.monitor(service_list[i].apply(gadget));
}
})
);
}
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSGadget.declareMethod // RenderJSGadget.declareMethod
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
...@@ -170,6 +347,8 @@ ...@@ -170,6 +347,8 @@
// Allow chain // Allow chain
return this; return this;
}; };
RenderJSGadget.declareAcquiredMethod("aq_reportServiceError",
"reportServiceError");
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSGadget.allowPublicAcquisition // RenderJSGadget.allowPublicAcquisition
...@@ -201,8 +380,12 @@ ...@@ -201,8 +380,12 @@
RenderJSGadget.call(this); RenderJSGadget.call(this);
} }
RenderJSEmbeddedGadget.__ready_list = RenderJSGadget.__ready_list.slice(); RenderJSEmbeddedGadget.__ready_list = RenderJSGadget.__ready_list.slice();
RenderJSEmbeddedGadget.__service_list =
RenderJSGadget.__service_list.slice();
RenderJSEmbeddedGadget.ready = RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready; RenderJSGadget.ready;
RenderJSEmbeddedGadget.declareService =
RenderJSGadget.declareService;
RenderJSEmbeddedGadget.prototype = new RenderJSGadget(); RenderJSEmbeddedGadget.prototype = new RenderJSGadget();
RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget; RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget;
...@@ -275,6 +458,9 @@ ...@@ -275,6 +458,9 @@
RenderJSIframeGadget.__ready_list = RenderJSGadget.__ready_list.slice(); RenderJSIframeGadget.__ready_list = RenderJSGadget.__ready_list.slice();
RenderJSIframeGadget.ready = RenderJSIframeGadget.ready =
RenderJSGadget.ready; RenderJSGadget.ready;
RenderJSIframeGadget.__service_list = RenderJSGadget.__service_list.slice();
RenderJSIframeGadget.declareService =
RenderJSGadget.declareService;
RenderJSIframeGadget.prototype = new RenderJSGadget(); RenderJSIframeGadget.prototype = new RenderJSGadget();
RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget; RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget;
...@@ -450,6 +636,14 @@ ...@@ -450,6 +636,14 @@
gadget_instance.__element.setAttribute("data-gadget-url", url); gadget_instance.__element.setAttribute("data-gadget-url", url);
gadget_instance.__element.setAttribute("data-gadget-sandbox", gadget_instance.__element.setAttribute("data-gadget-sandbox",
options.sandbox); options.sandbox);
gadget_instance.__element._gadget = gadget_instance;
if (document.contains(gadget_instance.__element)) {
// Put a timeout
queue.push(startService);
}
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
return gadget_instance; return gadget_instance;
}); });
...@@ -596,6 +790,7 @@ ...@@ -596,6 +790,7 @@
RenderJSGadget.call(this); RenderJSGadget.call(this);
}; };
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice(); tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareMethod = tmp_constructor.declareMethod =
RenderJSGadget.declareMethod; RenderJSGadget.declareMethod;
tmp_constructor.declareAcquiredMethod = tmp_constructor.declareAcquiredMethod =
...@@ -604,6 +799,8 @@ ...@@ -604,6 +799,8 @@
RenderJSGadget.allowPublicAcquisition; RenderJSGadget.allowPublicAcquisition;
tmp_constructor.ready = tmp_constructor.ready =
RenderJSGadget.ready; RenderJSGadget.ready;
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.prototype = new RenderJSGadget(); tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor; tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.__path = url; tmp_constructor.prototype.__path = url;
...@@ -771,6 +968,9 @@ ...@@ -771,6 +968,9 @@
last_acquisition_gadget.__acquired_method_dict = { last_acquisition_gadget.__acquired_method_dict = {
getTopURL: function () { getTopURL: function () {
return url; return url;
},
reportServiceError: function (param_list) {
letsCrash(param_list[0]);
} }
}; };
// Stop acquisition on the last acquisition gadget // Stop acquisition on the last acquisition gadget
...@@ -792,6 +992,9 @@ ...@@ -792,6 +992,9 @@
RenderJSGadget.allowPublicAcquisition; RenderJSGadget.allowPublicAcquisition;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice(); tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.ready = RenderJSGadget.ready; tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.prototype = new RenderJSGadget(); tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor; tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.__path = url; tmp_constructor.prototype.__path = url;
...@@ -812,6 +1015,7 @@ ...@@ -812,6 +1015,7 @@
// Create the root gadget instance and put it in the loading stack // Create the root gadget instance and put it in the loading stack
tmp_constructor = RenderJSEmbeddedGadget; tmp_constructor = RenderJSEmbeddedGadget;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice(); tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.prototype.__path = url; tmp_constructor.prototype.__path = url;
root_gadget = new RenderJSEmbeddedGadget(); root_gadget = new RenderJSEmbeddedGadget();
...@@ -864,6 +1068,8 @@ ...@@ -864,6 +1068,8 @@
return result; return result;
}; };
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.declareAcquiredMethod = tmp_constructor.declareAcquiredMethod =
RenderJSGadget.declareAcquiredMethod; RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition = tmp_constructor.allowPublicAcquisition =
...@@ -923,10 +1129,77 @@ ...@@ -923,10 +1129,77 @@
stylesheet_registration_dict[css_list[i]] = null; stylesheet_registration_dict[css_list[i]] = null;
} }
gadget_loading_klass = undefined; gadget_loading_klass = undefined;
}).then(function () {
// select the target node
var target = document.querySelector('body'),
// create an observer instance
observer = new MutationObserver(function (mutations) {
var i, k, len, len2, node, added_list;
mutations.forEach(function (mutation) {
if (mutation.type === 'childList') {
len = mutation.removedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.removedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
createMonitor(node._gadget);
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (node._gadget !== undefined) {
createMonitor(node._gadget);
}
}
}
}
len = mutation.addedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.addedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
if (document.contains(node)) {
startService(node._gadget);
}
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (document.contains(node)) {
if (node._gadget !== undefined) {
startService(node._gadget);
}
}
}
}
}
}
});
}),
// configuration of the observer:
config = {
childList: true,
subtree: true,
attributes: false,
characterData: false
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
return root_gadget; return root_gadget;
}).then(resolve, function (e) { }).then(resolve, function (e) {
reject(e); reject(e);
/*global console */
console.error(e); console.error(e);
throw e; throw e;
}); });
...@@ -957,6 +1230,10 @@ ...@@ -957,6 +1230,10 @@
}); });
} }
tmp_constructor.ready(function (g) {
return startService(g);
});
loading_gadget_promise.push(ready_wrapper); loading_gadget_promise.push(ready_wrapper);
for (i = 0; i < tmp_constructor.__ready_list.length; i += 1) { for (i = 0; i < tmp_constructor.__ready_list.length; i += 1) {
// Put a timeout? // Put a timeout?
...@@ -966,7 +1243,13 @@ ...@@ -966,7 +1243,13 @@
.push(ready_wrapper); .push(ready_wrapper);
} }
}); });
if (window.self !== window.top) { if (window.self === window.top) {
loading_gadget_promise
.fail(function (e) {
letsCrash(e);
throw e;
});
} else {
// Inform parent window that gadget is correctly loaded // Inform parent window that gadget is correctly loaded
loading_gadget_promise loading_gadget_promise
.then(function () { .then(function () {
...@@ -982,4 +1265,4 @@ ...@@ -982,4 +1265,4 @@
} }
bootstrap(); bootstrap();
}(document, window, RSVP, DOMParser, Channel)); }(document, window, RSVP, DOMParser, Channel, MutationObserver, Node));
...@@ -3,11 +3,15 @@ ...@@ -3,11 +3,15 @@
"use strict"; "use strict";
var gk = rJS(window), var gk = rJS(window),
ready_called = false; ready_called = false,
service_started = false;
gk.ready(function (g) { gk.ready(function (g) {
ready_called = true; ready_called = true;
}) })
.declareService(function () {
service_started = true;
})
.declareMethod('getBaseHref', function () { .declareMethod('getBaseHref', function () {
return document.querySelector('base') return document.querySelector('base')
.getAttribute('href'); .getAttribute('href');
...@@ -19,6 +23,9 @@ ...@@ -19,6 +23,9 @@
.declareMethod('wasReadyCalled', function () { .declareMethod('wasReadyCalled', function () {
return ready_called; return ready_called;
}) })
.declareMethod('wasServiceStarted', function () {
return service_started;
})
.declareMethod('isSubGadgetDictInitialize', function () { .declareMethod('isSubGadgetDictInitialize', function () {
return ((this.hasOwnProperty("__sub_gadget_dict")) && return ((this.hasOwnProperty("__sub_gadget_dict")) &&
(JSON.stringify(this.__sub_gadget_dict) === "{}")); (JSON.stringify(this.__sub_gadget_dict) === "{}"));
...@@ -26,6 +33,9 @@ ...@@ -26,6 +33,9 @@
.declareMethod('isAcquisitionDictInitialize', function () { .declareMethod('isAcquisitionDictInitialize', function () {
return (this.__acquired_method_dict !== undefined); return (this.__acquired_method_dict !== undefined);
}) })
.declareMethod('isServiceListInitialize', function () {
return (this.constructor.__service_list !== undefined);
})
.declareMethod('triggerError', function (value) { .declareMethod('triggerError', function (value) {
throw new Error("Manually triggered embedded error"); throw new Error("Manually triggered embedded error");
}) })
......
...@@ -1599,6 +1599,529 @@ ...@@ -1599,6 +1599,529 @@
deepEqual(Klass.__ready_list, [callback]); deepEqual(Klass.__ready_list, [callback]);
}); });
/////////////////////////////////////////////////////////////////
// RenderJSGadgetKlass.declareService
/////////////////////////////////////////////////////////////////
module("RenderJSGadgetKlass.declareService", {
setup: function () {
renderJS.clearGadgetKlassList();
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
this.server.autoRespondAfter = 5;
},
teardown: function () {
this.server.restore();
delete this.server;
}
});
test('is chainable', function () {
// Check that declareService is chainable
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
}, result;
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.__service_list = [];
Klass.declareService = RenderJSGadget.declareService;
result = Klass.declareService(function () {
return;
});
// declareService is chainable
equal(result, Klass);
});
test('store callback in the service_list property', function () {
// Check that declareService is chainable
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
},
callback = function () {return; };
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.__service_list = [];
Klass.declareService = RenderJSGadget.declareService;
Klass.declareService(callback);
// declareService is chainable
deepEqual(Klass.__service_list, [callback]);
});
/////////////////////////////////////////////////////////////////
// Service status
/////////////////////////////////////////////////////////////////
function declareServiceToCheck(klass, service_status) {
service_status.start_count = 0;
service_status.stop_count = 0;
service_status.status = undefined;
klass.declareService(function () {
service_status.start_count += 1;
return new RSVP.Queue()
.push(function () {
service_status.status = "started";
return RSVP.defer().promise;
})
.push(undefined, function (error) {
service_status.stop_count += 1;
if (error instanceof RSVP.CancellationError) {
service_status.status = "stopped";
} else {
service_status.status = "error";
}
throw error;
});
});
}
test('service untouched when gadget never in DOM', function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
service2 = {},
gadget = new RenderJSGadget(),
html_url = 'https://example.org/files/qunittest/test500.html';
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service1);
declareServiceToCheck(Klass, service2);
return gadget.declareGadget(
html_url
);
})
.then(function (g) {
return RSVP.delay(50);
})
.then(function () {
equal(service1.start_count, 0);
equal(service1.stop_count, 0);
equal(service1.status, undefined);
equal(service2.start_count, 0);
equal(service2.stop_count, 0);
equal(service2.status, undefined);
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('service started when gadget created in DOM', function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
service2 = {},
gadget = new RenderJSGadget(),
html_url = 'https://example.org/files/qunittest/test501.html';
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "<div></div>";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service1);
declareServiceToCheck(Klass, service2);
return gadget.declareGadget(
html_url,
{element: document.getElementById('qunit-fixture')
.querySelector("div")}
);
})
.then(function (g) {
return RSVP.delay(50);
})
.then(function () {
equal(service1.start_count, 1);
equal(service1.stop_count, 0);
equal(service1.status, "started");
equal(service2.start_count, 1);
equal(service2.stop_count, 0);
equal(service2.status, "started");
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('service started when gadget element added in DOM', function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
service2 = {},
gadget = new RenderJSGadget(),
html_url = 'https://example.org/files/qunittest/test502.html';
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "<div></div>";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service1);
declareServiceToCheck(Klass, service2);
return gadget.declareGadget(
html_url
);
})
.then(function (g) {
document
.getElementById('qunit-fixture')
.querySelector("div")
.appendChild(g.__element);
return RSVP.delay(50);
})
.then(function () {
equal(service1.start_count, 1);
equal(service1.stop_count, 0);
equal(service1.status, "started");
equal(service2.start_count, 1);
equal(service2.stop_count, 0);
equal(service2.status, "started");
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('service started when gadget parent element added in DOM', function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
service2 = {},
gadget = new RenderJSGadget(),
parent_element = document.createElement("div"),
html_url = 'https://example.org/files/qunittest/test503.html';
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service1);
declareServiceToCheck(Klass, service2);
return gadget.declareGadget(
html_url
);
})
.then(function (g) {
parent_element.appendChild(g.__element);
document
.getElementById('qunit-fixture')
.appendChild(parent_element);
return RSVP.delay(50);
})
.then(function () {
equal(service1.start_count, 1);
equal(service1.stop_count, 0);
equal(service1.status, "started");
equal(service2.start_count, 1);
equal(service2.stop_count, 0);
equal(service2.status, "started");
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('service stopped when gadget element removed from DOM', function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
service2 = {},
gadget = new RenderJSGadget(),
html_url = 'https://example.org/files/qunittest/test504.html';
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "<div></div>";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service1);
declareServiceToCheck(Klass, service2);
return gadget.declareGadget(
html_url,
{element: document.getElementById('qunit-fixture')
.querySelector("div")}
);
})
.then(function () {
return RSVP.delay(50);
})
.then(function () {
document
.getElementById('qunit-fixture')
.innerHTML = "";
return RSVP.delay(50);
})
.then(function () {
equal(service1.start_count, 1);
equal(service1.stop_count, 1);
equal(service1.status, "stopped");
equal(service2.start_count, 1);
equal(service2.stop_count, 1);
equal(service2.status, "stopped");
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('service stopped when gadget parent element removed from DOM',
function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
service2 = {},
gadget = new RenderJSGadget(),
parent_element = document.createElement("div"),
html_url = 'https://example.org/files/qunittest/test505.html';
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service1);
declareServiceToCheck(Klass, service2);
return gadget.declareGadget(
html_url
);
})
.then(function (g) {
parent_element.appendChild(g.__element);
document
.getElementById('qunit-fixture')
.appendChild(parent_element);
return RSVP.delay(50);
})
.then(function () {
document
.getElementById('qunit-fixture')
.innerHTML = "";
return RSVP.delay(50);
})
.then(function () {
equal(service1.start_count, 1);
equal(service1.stop_count, 1);
equal(service1.status, "stopped");
equal(service2.start_count, 1);
equal(service2.stop_count, 1);
equal(service2.status, "stopped");
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('service can be restarted', function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
service2 = {},
gadget = new RenderJSGadget(),
created_gadget,
html_url = 'https://example.org/files/qunittest/test506.html';
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "<div></div>";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service1);
declareServiceToCheck(Klass, service2);
return gadget.declareGadget(
html_url,
{element: document.getElementById('qunit-fixture')
.querySelector("div")}
);
})
.then(function (g) {
created_gadget = g;
return RSVP.delay(50);
})
.then(function () {
document.getElementById('qunit-fixture').innerHTML = "";
return RSVP.delay(50);
})
.then(function () {
document
.getElementById('qunit-fixture')
.appendChild(created_gadget.__element);
return RSVP.delay(50);
})
.then(function () {
equal(service1.start_count, 2);
equal(service1.stop_count, 1);
equal(service1.status, "started");
equal(service2.start_count, 2);
equal(service2.stop_count, 1);
equal(service2.status, "started");
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// Service error handling
/////////////////////////////////////////////////////////////////
test('Service error are reported to parent gadget', function () {
// Subclass RenderJSGadget to not pollute its namespace
var ParentKlass = function () {
RenderJSGadget.call(this);
},
gadget,
catched_error,
html_url = 'https://example.org/files/qunittest/test508.html';
ParentKlass.prototype = new RenderJSGadget();
ParentKlass.prototype.constructor = ParentKlass;
ParentKlass.prototype.__acquired_method_dict = {};
ParentKlass.allowPublicAcquisition = RenderJSGadget.allowPublicAcquisition;
ParentKlass.allowPublicAcquisition('reportServiceError',
function (argument_list) {
catched_error = argument_list[0];
return;
});
gadget = new ParentKlass();
// Subclass RenderJSGadget to not pollute its namespace
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "<div></div>";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
Klass.declareService(function () {
throw new Error("My service crashed!");
});
return gadget.declareGadget(
html_url,
{element: document.getElementById('qunit-fixture')
.querySelector("div")}
);
})
.then(function () {
return RSVP.delay(50);
})
.then(function () {
ok(catched_error instanceof Error);
equal(
catched_error.message,
"My service crashed!"
);
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('Service error stops the other services', function () {
// Subclass RenderJSGadget to not pollute its namespace
var ParentKlass = function () {
RenderJSGadget.call(this);
},
gadget,
service2 = {},
html_url = 'https://example.org/files/qunittest/test509.html';
ParentKlass.prototype = new RenderJSGadget();
ParentKlass.prototype.constructor = ParentKlass;
ParentKlass.prototype.__acquired_method_dict = {};
ParentKlass.allowPublicAcquisition = RenderJSGadget.allowPublicAcquisition;
ParentKlass.allowPublicAcquisition('reportServiceError',
function (argument_list) {
return;
});
gadget = new ParentKlass();
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "<div></div>";
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
declareServiceToCheck(Klass, service2);
Klass.declareService(function () {
throw new Error("My service crashed!");
});
return gadget.declareGadget(
html_url,
{element: document.getElementById('qunit-fixture')
.querySelector("div")}
);
})
.then(function () {
return RSVP.delay(50);
})
.then(function () {
equal(service2.start_count, 1);
equal(service2.stop_count, 1);
equal(service2.status, "stopped");
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSIframeGadget // RenderJSIframeGadget
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
...@@ -2731,6 +3254,14 @@ ...@@ -2731,6 +3254,14 @@
equal(result, true); equal(result, true);
}) })
// Check that service are started
.push(function () {
return new_gadget.wasServiceStarted();
})
.push(function (result) {
equal(result, true);
})
// Custom method accept parameter // Custom method accept parameter
// and return value // and return value
.push(function () { .push(function () {
...@@ -2769,6 +3300,14 @@ ...@@ -2769,6 +3300,14 @@
equal(result, true); equal(result, true);
}) })
// service_list is created on prototype
.push(function () {
return new_gadget.isServiceListInitialize();
})
.push(function (result) {
equal(result, true);
})
// acquire check correctly returns result // acquire check correctly returns result
.push(function () { .push(function () {
return new_gadget.callOKAcquire("param1", "param2"); return new_gadget.callOKAcquire("param1", "param2");
...@@ -3016,6 +3555,7 @@ ...@@ -3016,6 +3555,7 @@
ok(root_gadget.__aq_parent !== undefined); ok(root_gadget.__aq_parent !== undefined);
ok(root_gadget.hasOwnProperty("__sub_gadget_dict")); ok(root_gadget.hasOwnProperty("__sub_gadget_dict"));
deepEqual(root_gadget.__sub_gadget_dict, {}); deepEqual(root_gadget.__sub_gadget_dict, {});
deepEqual(root_gadget_klass.__service_list, []);
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return root_gadget.getTopURL().then(function (topURL) { return root_gadget.getTopURL().then(function (topURL) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment