From d96d4e4d30be3b54cfb0c0fb0b49d82537db8adc Mon Sep 17 00:00:00 2001 From: Hardik Juneja Date: Wed, 17 Aug 2016 14:00:08 +0000 Subject: [PATCH 1/5] webrtc_gadget: Webrtc gadget which use JIO or Websocket for handshake --- .../gadget_webrtc_handshake_html.html | 23 + .../gadget_webrtc_handshake_html.xml | 259 ++++++++++ .../gadget_webrtc_handshake_js.js | 190 +++++++ .../gadget_webrtc_handshake_js.xml | 255 +++++++++ .../web_page_module/gadget_webrtc_html.html | 17 + .../web_page_module/gadget_webrtc_html.xml | 194 +++++++ .../gadget_webrtc_jio_bridge_html.html | 26 + .../gadget_webrtc_jio_bridge_html.xml | 259 ++++++++++ .../gadget_webrtc_jio_bridge_js.js | 278 ++++++++++ .../gadget_webrtc_jio_bridge_js.xml | 255 +++++++++ .../web_page_module/gadget_webrtc_js.xml | 486 ++++++++++++++++++ ...tc_serverless_demo_master_helper_html.html | 32 ++ ...rtc_serverless_demo_master_helper_html.xml | 259 ++++++++++ ...webrtc_serverless_demo_master_helper_js.js | 241 +++++++++ ...ebrtc_serverless_demo_master_helper_js.xml | 267 ++++++++++ ...et_webrtc_serverless_demo_master_html.html | 97 ++++ ...get_webrtc_serverless_demo_master_html.xml | 200 +++++++ ...gadget_webrtc_serverless_demo_master_js.js | 115 +++++ ...adget_webrtc_serverless_demo_master_js.xml | 196 +++++++ ...rtc_serverless_demo_slave_helper_html.html | 28 + ...brtc_serverless_demo_slave_helper_html.xml | 259 ++++++++++ ..._webrtc_serverless_demo_slave_helper_js.js | 133 +++++ ...webrtc_serverless_demo_slave_helper_js.xml | 267 ++++++++++ ...get_webrtc_serverless_demo_slave_html.html | 100 ++++ ...dget_webrtc_serverless_demo_slave_html.xml | 322 ++++++++++++ ...gadget_webrtc_serverless_demo_slave_js.xml | 458 +++++++++++++++++ ...adget_webrtc_websocket_handshake_html.html | 20 + ...gadget_webrtc_websocket_handshake_html.xml | 338 ++++++++++++ .../gadget_webrtc_websocket_handshake_js.js | 81 +++ .../gadget_webrtc_websocket_handshake_js.xml | 318 ++++++++++++ bt5/erp5_webrtc_gadget/bt/dependency_list | 1 + .../bt/template_format_version | 1 + ..._keep_last_workflow_history_only_path_list | 16 + bt5/erp5_webrtc_gadget/bt/template_path_list | 16 + bt5/erp5_webrtc_gadget/bt/title | 1 + 35 files changed, 6008 insertions(+) create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_js.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.js create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.js create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.js create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_js.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.html create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.xml create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.js create mode 100644 bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.xml create mode 100644 bt5/erp5_webrtc_gadget/bt/dependency_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_format_version create mode 100644 bt5/erp5_webrtc_gadget/bt/template_keep_last_workflow_history_only_path_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_path_list create mode 100644 bt5/erp5_webrtc_gadget/bt/title diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.html new file mode 100644 index 00000000000..8b2762a1029 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.html @@ -0,0 +1,23 @@ + + + + + + + WebRTC Handshake Gadget + + + + + + + + + + +
+ + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.xml new file mode 100644 index 00000000000..9c3d667ec97 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_html.xml @@ -0,0 +1,259 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + gadget_webrtc_handshake.html + + + description + JIO handshaking gadget for Webrtc Gadget + + + id + gadget_webrtc_handshake_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + title + Gadget to handle handshaking using JIO + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966649.39 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39247.53862.46899 + + + state + current + + + time + + + + + + + + + + + 1472631386.92 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js new file mode 100644 index 00000000000..8725a7a66d1 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js @@ -0,0 +1,190 @@ +/*jslint indent:2, maxlen:100, nomen:true*/ +/*global rJS, window, WebSocket, RSVP*/ + +(function (window, rJS, RSVP) { + "use strict"; + + function createJio(gadget, config) { + return gadget.getDeclaredGadget("jio_gadget") + .push(function (jio_gadget) { + return jio_gadget.createJio(config); + }) + .push(null, function(e) { + // TODO: Throw error in a correct way + throw new Error("Wrong Config", config); + }) + } + + window.promiseDoWhile = function (loopFunction, input) { + // calls loopFunction(input) until it returns a non positive value + + // this queue is to protect the inner loop queue from the + // `promiseDoWhile` caller, avoiding it to enqueue the inner + // loop queue. + return new RSVP.Queue() + .push(function () { + // here is the inner loop queue + var loop_queue = new RSVP.Queue(); + function iterate(previous_iteration_result) { + if (!previous_iteration_result) { + return input; + } + loop_queue.push(iterate); + return loopFunction(input); + } + return loop_queue + .push(function () { + return loopFunction(input); + }) + .push(iterate); + }); + }; + + rJS(window) + .ready(function (g) { + g.props = {}; + }) + + .declareMethod('create_room', function(roomid) { + var gadget = this; + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (jio_gadget) { + return jio_gadget.put(roomid, {}); + }) + .push(null, function(e){ + console.log(e); + }) + }) + + .declareMethod('register', function(roomid, peerid, config) { + var gadget = this; + + return new RSVP.Queue() + .push(function() { + return createJio(gadget, config); + }) + .push(function() { + return gadget.create_room(roomid); + }) + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (jio_gadget) { + return jio_gadget.putAttachment(roomid, peerid, ''); + }); + // TODO: create new function, show registered peers? + }) + + .declareMethod('handle_offer_answer', function (roomid, options) { + var gadget = this, + jio_gadget; + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (jio_gadget) { + return jio_gadget.putAttachment(roomid, options.name, options.data); + }) + }) + + .declareMethod('get_answer', function (roomid, attachment) { + var gadget = this, + jio_gadget; + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (jio_gadget) { + return jio_gadget.getAttachment(roomid, attachment); + }); + }) + + .declareMethod('get_offers', function (roomid) { + var gadget = this, + jio_gadget; + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (jio_gadget) { + return jio_gadget.allAttachments(roomid); + }); + }) + + .declareMethod('wait_until_available', function (roomid, query, callbackPromise) { + // Pool the document until available + var gadget = this, + response, + offers = []; + + var action = function(response) { + return gadget.get_offers(roomid) + .push(function(response) { + var offers = []; + for (var doc_name in response) { + if(doc_name.split('_').length === 1 && doc_name === query) { + return gadget.get_answer(roomid, query); + } else if (doc_name.split('_').length === 2 + && doc_name.split('_')[0]+'_' === query) { + offers.push(gadget.get_answer(roomid, doc_name)); + } + } + return RSVP.all(offers); + }) + .push(function(responses) { + if (responses.constructor !== Array ) { + responses = [responses]; + } + if (!responses.length) { + return new RSVP.Queue() + .push(function() { + return RSVP.delay(5000); + }) + .push(function() { + return true; + }); + } + var blobs = responses; + + var texts = []; + for (var blob in blobs) { + texts.push(jIO.util.readBlobAsText(blobs[blob])); + } + return new RSVP.Queue() + .push(function() { + return RSVP.all(texts); + }) + .push(function(o){ + var a = []; + for (var offer in o) { + a.push(o[offer].target.result); + } + + if (typeof callbackPromise === "function") { + return callbackPromise(a); + } else { + offers = a; + return false; + } + }); + }); + }; + + return promiseDoWhile(action, callbackPromise); + }) + + .declareMethod('close', function (roomid, peerid, to) { + var gadget = this; + + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (jio_gadget) { + return jio_gadget.removeAttachment(roomid, peerid+"_"+to); + }); + }); +})(window, rJS, RSVP); \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml new file mode 100644 index 00000000000..12f2ac29ef8 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml @@ -0,0 +1,255 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + default_reference + gadget_webrtc_handshake.js + + + description + JIO handshaking gadget for Webrtc Gadget + + + id + gadget_webrtc_handshake_js + + + language + en + + + portal_type + Web Script + + + short_title + + + + + + title + Gadget to handle handshaking using JIO + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966645.61 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 954.14301.62370.56661 + + + state + current + + + time + + + + + + + + + + + 1475066759.35 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.html new file mode 100644 index 00000000000..0ca8fab89b7 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.html @@ -0,0 +1,17 @@ + + + + + + + WebRTC Gadget + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.xml new file mode 100644 index 00000000000..9745a2f6242 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_html.xml @@ -0,0 +1,194 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + gadget_webrtc.html + + + description + Webrtc Gadget + + + id + gadget_webrtc_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + title + webrtc gadget + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966610.71 + UTC + + + + + + + validation_state + published_alive + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.html new file mode 100644 index 00000000000..eed04e0e0eb --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.html @@ -0,0 +1,26 @@ + + + + + + + WebRTC DataChannel Gadget + + + + + + + + +
+ +
+ +
+ + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.xml new file mode 100644 index 00000000000..431609be62a --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_html.xml @@ -0,0 +1,259 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + gadget_webrtc_jio_bridge.html + + + description + Bridge for Webrtc and JIO Gadgets + + + id + gadget_webrtc_jio_bridge_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + title + Connector for webrtc and jio gadgets + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966513.56 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.57024.40223.50090 + + + state + current + + + time + + + + + + + + + + + 1473697957.39 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js new file mode 100644 index 00000000000..0243e08b2d9 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js @@ -0,0 +1,278 @@ +/*jslint indent:2, maxlen:100, nomen:true*/ +/*global rJS, RSVP, window*/ + +(function (rJS, window, RSVP) { + "use strict"; + + var connection_options = [ + { + // No need for stun server on the same lan / ipv6 + iceServers: [ + {'url': 'stun:23.21.150.121'}, + {url: 'stun:stun.1.google.com:19302'} + ] + }, + { + 'optional': [{DtlsSrtpKeyAgreement: true}] + } + ], + data_channel_options = {reliable: true}, + offer_contraints = { + mandatory: { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + } + }; + + function declareSubGadget(gadget, url) { + var container_element = gadget.props.element.querySelector("." + url.split(".")[0]), + element = document.createElement("div"); + + container_element.innerHTML = ""; + container_element.appendChild(element); + return gadget.declareGadget(url, { + element: element, + scope: url, + sandbox: "public" + }); + } + + function dropSubGadget(gadget, scope) { + return gadget.getDeclaredGadget(scope) + .push(function (result) { + return result.getElement(); + }) + .push(function (element) { + if (element.parentElement) { + element.parentElement.removeChild(element); + } + return gadget.dropGadget(scope); + }); + } + + function createOffer(gadget, title) { + var webrtc = gadget.props.webrtc_connections["webrtc" + gadget.props.counter]; + return webrtc.createConnection.apply(webrtc, connection_options) + .push(function () { + return webrtc.createDataChannel(title, data_channel_options); + }) + .push(function () { + return webrtc.createOffer(offer_contraints); + }) + .push(function (local_description) { + return webrtc.setLocalDescription(local_description); + }) + .push(function () { + return gadget.props.description_defer.promise; + }); + } + + function registerAnswer (gadget, description) { + return gadget.props.webrtc_connections["webrtc" + gadget.props.counter] + .setRemoteDescription(description) + .push(function () { + return gadget.props.channel_defer.promise; + }); + } + + function createAnswer(gadget, description) { + var webrtc = gadget.props.webrtc_connections["webrtc" + gadget.props.counter]; + + return webrtc.createConnection.apply(webrtc, connection_options) + .push(function () { + return webrtc.setRemoteDescription(description); + }) + .push(function () { + return webrtc.createAnswer(offer_contraints); + }) + .push(function (local_description) { + return webrtc.setLocalDescription(local_description); + }) + .push(function () { + return gadget.props.description_defer.promise; + }); + } + + function waitForConnection (gadget) { + return gadget.props.channel_defer.promise; + } + + function handleConnection(gadget, handler_gadget, options, offer) { + // create a new webrtc gadget instance + var new_element = document.createElement("div"); + gadget.props.element.querySelector(".gadget_webrtc").appendChild(new_element); + var scope = "webrtc" + gadget.props.counter; + return gadget.declareGadget("gadget_webrtc.html", { + scope: scope, + element: new_element + }) + .push(function(webrtc_gadget) { + gadget.props.webrtc_connections[scope] = webrtc_gadget; + if (options.initiator === true) { + return createOffer(gadget, options.peerid) + .push(function (description) { + var params = {'name': options.to+'_'+options.peerid, + 'data' : new Blob([JSON.stringify({ from: options.peerid, + action: "offer", + data: description})], + {type : "application/json"})}; + return handler_gadget.handle_offer_answer(options.roomid, params); + }) + .push(function(){ + return handler_gadget.wait_until_available(options.roomid, options.peerid+'_', + function(response){ + return registerAnswer(gadget, JSON.parse(response).data) + .push(function () { + return handler_gadget.close(options.roomid, + options.peerid, + options.to, + options.listner); + }) + .push(function () { + return false; + }); + }); + }); + } else { + if (!offer.data) { + throw new Error("No offer provided in connect function"); + } + return createAnswer(gadget, offer.data) + .push(function (answer) { + var params = {name: options.to+'_'+options.peerid, + data: new Blob([JSON.stringify({ to: options.to, + action: "answer", + data: answer})], + {type : "application/json"}) }; + return handler_gadget.handle_offer_answer(options.roomid, params); + }) + .push(function () { + return handler_gadget.close(options.roomid, options.peerid, options.to, options.listner); + }) + .push(function(){ + return RSVP.any([ + RSVP.Queue() + .push(function () { + return RSVP.delay(20000); + }) + .push(function () { + console.info('-- webrtc client disappears...'); + return dropSubGadget(gadget, "webrtc"+gadget.props.counter); + }), + waitForConnection(gadget) + ]); + }); + } + }); + } + + rJS(window) + .ready(function (g) { + g.props = {}; + g.props.description_defer = RSVP.defer(); + g.props.channel_defer = RSVP.defer(); + g.props.counter = 0; + g.props.webrtc_connections = {}; + + return g.getElement() + .push(function (element) { + g.props.element = element; + }); + }) + .declareAcquiredMethod("notifyDataChannelMessage", "notifyDataChannelMessage") + + .allowPublicAcquisition('notifyDataChannelClosed', function (argument_list, scope) { + var gadget = this; + return dropSubGadget(this, scope) + .push(function () { + return updateInfo(gadget); + }); + }) + + .allowPublicAcquisition("notifyDescriptionCalculated", function (args) { + this.props.description_defer.resolve(args[0]); + this.props.description_defer = RSVP.defer(); + }) + + .allowPublicAcquisition("notifyDataChannelOpened", function () { + this.props.channel_defer.resolve(); + }) + + .declareMethod('send', function (args, to) { + //TODO: Add sent-to by name, functionality here + var webrtc = this.props.webrtc_connections[to]; + return webrtc.send.apply( + webrtc, + arguments + ); + }) + + .declareMethod('connect', function(options){ + var gadget = this, + jsonOffer, + handler, + roomid = options.roomid, + peerid = options.peerid; + + if (options.type === "websocket") { + handler = "websocket_handshake_gadget"; + } else if (options.type === "jio") { + handler = "handshake_gadget"; + } + + gadget.props.handler_gadget = handler; + + return this.getDeclaredGadget(handler) + .push(function (handler_gadget){ + return handler_gadget.register(options.roomid, options.peerid, options.config); + }) + .push(function (){ + + function connectCallback (offer, handler_gadget) { + return RSVP.Queue() + .push(function(){ + if (options.preConnection) { + return options.preConnection(options, offer); + } else { + return; + } + }) + .push(function() { + return handleConnection(gadget, handler_gadget, options, offer); + }) + .push(function() { + if (options.postConnection) { + return options.postConnection(options, offer); + } else { + return; + } + }); + } + + if (gadget.props.handler_gadget) { + return gadget.getDeclaredGadget(gadget.props.handler_gadget) + .push(function(handler_gadget) { + if (options.listner) { + return handler_gadget.wait_until_available(roomid, peerid+'_', function(offers){ + var connections = []; + // TODO: have a array for channel_defer for concurency? + gadget.props.channel_defer = RSVP.defer(); + for (var offer in offers) { + jsonOffer = JSON.parse(offers[offer]); + if (jsonOffer.action === "offer") { + gadget.props.counter += 1; + connections.push(connectCallback(jsonOffer, handler_gadget)); + } + } + return RSVP.all(connections); + }); + } else { + return handleConnection(gadget, handler_gadget, options); + } + }); + } else { + throw new Error("Peer is not registered"); + } + }); + }); +}(rJS, window, RSVP)); \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml new file mode 100644 index 00000000000..51ffd1bd29d --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml @@ -0,0 +1,255 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + default_reference + gadget_webrtc_jio_bridge.js + + + description + Bridge for Webrtc and JIO Gadgets + + + id + gadget_webrtc_jio_bridge_js + + + language + en + + + portal_type + Web Script + + + short_title + + + + + + title + Connector for webrtc and jio gadgets (js) + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966500.93 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 954.14300.3742.31044 + + + state + current + + + time + + + + + + + + + + + 1475066648.69 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_js.xml new file mode 100644 index 00000000000..b2446cc190a --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_js.xml @@ -0,0 +1,486 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + default_reference + gadget_webrtc.js + + + description + Webrtc Gadget + + + id + gadget_webrtc_js + + + language + + + + + + portal_type + Web Manifest + + + short_title + + + + + + text_content + /*jslint indent: 2, maxlen: 80, nomen: true */,\n +/*global rJS, RSVP, window*/\n +(function (rJS, RSVP, window) {\n + "use strict";\n +\n + var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection ||\n + window.webkitRTCPeerConnection || window.msRTCPeerConnection,\n + RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||\n + window.webkitRTCSessionDescription || window.msRTCSessionDescription;\n +\n + function enqueueDefer(gadget, callback) {\n + var deferred = gadget.props.current_deferred;\n +\n + // Unblock queue\n + if (deferred !== undefined) {\n + deferred.resolve("Another event added");\n + }\n +\n + // Add next callback\n + try {\n + gadget.props.service_queue.push(callback);\n + } catch (error) {\n + throw new Error("Connection gadget already crashed... " +\n + gadget.props.service_queue.rejectedReason.toString());\n + }\n +\n + // Block the queue\n + deferred = RSVP.defer();\n + gadget.props.current_deferred = deferred;\n + gadget.props.service_queue.push(function () {\n + return deferred.promise;\n + });\n +\n + }\n +\n + function deferOnIceCandidate(candidate) {\n + var gadget = this;\n +\n + enqueueDefer(gadget, function () {\n + // Firing this callback with a null candidate indicates that\n + // trickle ICE gathering has finished, and all the candidates\n + // are now present in pc.localDescription. Waiting until now\n + // to create the answer saves us from having to send offer +\n + // answer + iceCandidates separately.\n + if (candidate.candidate === null) {\n + return gadget.notifyDescriptionCalculated(JSON.stringify(gadget.props.connection.localDescription));\n + }\n + });\n + }\n +\n + function deferDataChannelOnOpen() {\n + var gadget = this;\n + enqueueDefer(gadget, function () {\n + return gadget.notifyDataChannelOpened();\n + });\n + }\n +\n + function deferDataChannelOnClose() {\n + var gadget = this;\n + enqueueDefer(gadget, function () {\n + return gadget.notifyDataChannelClosed();\n + });\n + }\n +\n + function deferDataChannelOnMessage(evt) {\n + var gadget = this;\n + enqueueDefer(gadget, function () {\n + return gadget.notifyDataChannelMessage(evt.data);\n +// var data = JSON.parse(evt.data);\n +// console.log(data.message);\n + });\n + }\n +\n + function deferServerDisconnection(gadget) {\n + enqueueDefer(gadget, function () {\n + // Try to auto connection\n + if (gadget.props.connection !== undefined) {\n + gadget.props.connection.disconnect();\n + delete gadget.props.connection;\n + }\n + });\n + }\n +\n +// function deferOfferSuccessCallback(description) {\n +// var gadget = this;\n +// enqueueDefer(gadget, function () {\n +// gadget.props.connection.setLocalDescription(description);\n +// });\n +// }\n +\n + function deferErrorHandler(error) {\n + enqueueDefer(this, function () {\n + throw error;\n + });\n + }\n +\n + function deferServerConnection(gadget) {\n + deferServerDisconnection(gadget);\n +\n + }\n +\n + function listenToChannelEvents(gadget) {\n + gadget.props.channel.onopen = deferDataChannelOnOpen.bind(gadget);\n + gadget.props.channel.onclose = deferDataChannelOnClose.bind(gadget);\n + gadget.props.channel.onmessage = deferDataChannelOnMessage.bind(gadget);\n + gadget.props.channel.onerror = deferErrorHandler.bind(gadget);\n + }\n +\n + rJS(window)\n + .ready(function (g) {\n + g.props = {};\n + })\n +\n + .declareAcquiredMethod(\'notifyDescriptionCalculated\',\n + \'notifyDescriptionCalculated\')\n + .declareAcquiredMethod(\'notifyDataChannelOpened\',\n + \'notifyDataChannelOpened\')\n + .declareAcquiredMethod(\'notifyDataChannelMessage\',\n + \'notifyDataChannelMessage\')\n + .declareAcquiredMethod(\'notifyDataChannelClosed\',\n + \'notifyDataChannelClosed\')\n +\n + .declareService(function () {\n + /////////////////////////\n + // Handle WebRTC connection\n + /////////////////////////\n + var context = this;\n +\n + context.props.service_queue = new RSVP.Queue();\n + deferServerConnection(context);\n +\n + return new RSVP.Queue()\n + .push(function () {\n + return context.props.service_queue;\n + })\n + .push(function () {\n + // XXX Handle cancellation\n + throw new Error("Service should not have been stopped!");\n + })\n + .push(undefined, function (error) {\n + // Always disconnect in case of error\n + if (context.props.connection !== undefined) {\n + context.props.connection.close();\n + }\n + throw error;\n + });\n + })\n +\n +\n + .declareMethod(\'createConnection\', function (configuration, constraints) {\n + this.props.connection = new RTCPeerConnection(configuration, constraints);\n + this.props.connection.onicecandidate = deferOnIceCandidate.bind(this);\n + var context = this;\n + this.props.connection.ondatachannel = function (evt) {\n + context.props.channel = evt.channel;\n + listenToChannelEvents(context);\n + };\n + })\n +\n + .declareMethod(\'createDataChannel\', function (title, options) {\n + // XXX Improve to support multiple data channel\n + this.props.channel = this.props.connection.createDataChannel(title, options);\n + listenToChannelEvents(this);\n + // console.log("Channel type: " + this.props.channel.binarytype);\n + })\n +\n + .declareMethod(\'createOffer\', function (constraints) {\n + var gadget = this;\n + return new RSVP.Promise(function (resolve, reject) {\n + gadget.props.connection.createOffer(\n + resolve,\n + reject,\n + constraints\n + );\n + });\n + })\n +\n + .declareMethod(\'setRemoteDescription\', function (description) {\n + var gadget = this;\n + return new RSVP.Promise(function (resolve, reject) {\n + gadget.props.connection.setRemoteDescription(\n + new RTCSessionDescription(JSON.parse(description)),\n + resolve,\n + reject\n + );\n + });\n + })\n +\n + .declareMethod(\'setLocalDescription\', function (description) {\n + var gadget = this;\n + return new RSVP.Promise(function (resolve, reject) {\n + gadget.props.connection.setLocalDescription(\n + new RTCSessionDescription(description),\n + resolve,\n + reject\n + );\n + });\n + })\n +\n + .declareMethod(\'createAnswer\', function (constraints) {\n + var gadget = this;\n + return new RSVP.Promise(function (resolve, reject) {\n + gadget.props.connection.createAnswer(\n + resolve,\n + reject,\n + constraints\n + );\n + });\n + })\n +\n + .declareMethod(\'send\', function (message) {\n + this.props.channel.send(message);\n + })\n +\n + .declareMethod(\'close\', function () {\n + // XXX Of course, this will fail if connection is not open yet...\n + this.props.channel.close();\n + this.props.connection.close();\n + delete this.props.channel;\n + delete this.props.connection;\n + });\n +\n +}(rJS, RSVP, window));\n + + + + title + webrtc gadget + + + version + + + + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966491.22 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.51044.11136.7543 + + + state + current + + + time + + + + + + + + + + + 1475065036.81 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.html new file mode 100644 index 00000000000..61d0a741d97 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.html @@ -0,0 +1,32 @@ + + + + + + + Share jIO + + + + + + + +
+
+
+

This is a renderJS gadget which shares a jIO storage via WebRTC.

+

Peer list

+

Connected peers: 0

+

+      
+
+
+ +
+
+
+
+
+ + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.xml new file mode 100644 index 00000000000..eabd5c605d6 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_html.xml @@ -0,0 +1,259 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + gadget_webrtc_serverless_demo_master_helper.html + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_master_helper_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + title + share jio with slaves + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1472731728.11 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39310.7473.35379 + + + state + current + + + time + + + + + + + + + + + 1472635088.36 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.js new file mode 100644 index 00000000000..fe16c26215b --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.js @@ -0,0 +1,241 @@ +/*global window, rJS, document, RSVP, console, DOMException */ +/*jslint indent: 2, maxerr: 3 */ +(function (window, rJS, document, RSVP, console, DOMException) { + "use strict"; + + window.promiseDoWhile = function (loopFunction, input) { + // calls loopFunction(input) until it returns a non positive value + + // this queue is to protect the inner loop queue from the + // `promiseDoWhile` caller, avoiding it to enqueue the inner + // loop queue. + return new RSVP.Queue() + .push(function () { + // here is the inner loop queue + var loop_queue = new RSVP.Queue(); + function iterate(previous_iteration_result) { + if (!previous_iteration_result) { + return input; + } + loop_queue.push(iterate); + return loopFunction(input); + } + return loop_queue + .push(function () { + return loopFunction(input); + }) + .push(iterate); + }); + }; + + function dropSubGadget(gadget, scope) { + return gadget.getDeclaredGadget(scope) + .push(function (result) { + return result.getElement(); + }) + .push(function (element) { + if (element.parentElement) { + element.parentElement.removeChild(element); + } + delete gadget.state_parameter_dict.scope_ip[scope]; + return gadget.dropGadget(scope); + }); + } + + function getWebRTCScopeList(gadget) { + var result_list = [], + element_list = gadget.state_parameter_dict.element.querySelector(".gadget_webrtc_jio_bridge") + .childNodes, + i; + for (i = 0; i < element_list.length; i += 1) { + result_list.push(element_list[i].getAttribute("data-gadget-scope")); + } + return result_list; + } + + function updateInfo(gadget) { + var scope_list = getWebRTCScopeList(gadget), + i, + result = ""; + for (i = 0; i < scope_list.length; i += 1) { + result += gadget.state_parameter_dict.scope_ip[scope_list[i]] + "\n"; + } + gadget.state_parameter_dict.element.querySelector(".info").textContent = result; + gadget.state_parameter_dict.element.querySelector(".peer_count").textContent = i; + } + + function sendWebRTC(gadget, rtc_gadget, scope, message) { + return rtc_gadget.send(message) + .push(undefined, function (error) { + if ((error instanceof DOMException) && (error.name === 'InvalidStateError')) { + return dropSubGadget(gadget, scope) + .push(function () { + return updateInfo(gadget); + }, function (error) { + console.log("-- Can not drop remote subgadget " + scope); + console.log(error); + return; + }); + } + throw error; + }); + } + + rJS(window) + + .ready(function (gadget) { + // Initialize the gadget local parameters + gadget.state_parameter_dict = { + websocket_initialized: false, + counter: 0, + connecting: false, + scope_ip: {} + }; + return gadget.getElement() + .push(function (element) { + gadget.state_parameter_dict.element = element; + }) + .push(function () { + return updateInfo(gadget); + }); + }) + + .declareAcquiredMethod("jio_allDocs", "jio_allDocs") + .declareAcquiredMethod("jio_post", "jio_post") + .declareAcquiredMethod("jio_put", "jio_put") + .declareAcquiredMethod("jio_get", "jio_get") + .declareAcquiredMethod("jio_repair", "jio_repair") + + .allowPublicAcquisition('notifyDataChannelClosed', function (argument_list, scope) { + /*jslint unparam:true*/ + var gadget = this; + return dropSubGadget(this, scope) + .push(function () { + return updateInfo(gadget); + }); + }) + + .allowPublicAcquisition("notifyDataChannelMessage", function (argument_list, scope) { + var json = JSON.parse(argument_list[0]), + rtc_gadget, + context = this; + return context.getDeclaredGadget(scope) + .push(function (g) { + rtc_gadget = g; + // Call jio API + return context["jio_" + json.method_name].apply(context, json.argument_list); + }) + .push(function (result) { + return sendWebRTC(context, rtc_gadget, scope, JSON.stringify({ + id: json.id, + result: result, + type: "jio_response" + })); + }, function (error) { + return sendWebRTC(context, rtc_gadget, scope, JSON.stringify({ + id: json.id, + result: error, + type: "error" + })); + }); + }) + + .declareMethod('connect', function(handshake_gadget, room_id, offer) { + var gadget = this, + scope, + rtc_gadget; + + return new RSVP.Queue() + .push(function(response) { + gadget.state_parameter_dict.connecting = true; + gadget.state_parameter_dict.counter += 1; + + var new_element = document.createElement("div"); + gadget.state_parameter_dict.element.querySelector(".gadget_webrtc_jio_bridge").appendChild(new_element); + scope = "webrtc" + gadget.state_parameter_dict.counter; + + return gadget.declareGadget("gadget_webrtc_jio_bridge.html", { + scope: scope, + element: new_element + }) + .push(function(gg){ + rtc_gadget = gg; + // https://github.com/diafygi/webrtc-ips + var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/, + ip_list = [], + ip_dict = {}, + ip_addr, + line_list = JSON.parse(offer.data).sdp.split('\n'), + i; + + for (i = 0; i < line_list.length; i += 1) { + if (line_list[i].indexOf('a=candidate:') === 0) { + ip_addr = ip_regex.exec(line_list[i])[1]; + if (!ip_addr.match(/^[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}$/)) { + // Hide ipv6 + if (!ip_dict[ip_addr]) { + ip_list.push(ip_addr); + ip_dict[ip_addr] = true; + } + } + } + } + gadget.state_parameter_dict.scope_ip[scope] = ip_list; + return rtc_gadget.connect({ + roomid: room_id, + peerid: 'master', + to: offer.from, + offer: JSON.stringify(offer.data) + }); + }) + .push(function () { + return RSVP.any([ + RSVP.Queue() + .push(function () { + return RSVP.delay(20000); + }) + .push(function () { + console.info('-- webrtc client disappears...'); + return dropSubGadget(gadget, scope); + }), + rtc_gadget.waitForConnection() + ]); + }) + .push(function () { + gadget.state_parameter_dict.connecting = false; + return updateInfo(gadget); + }); + }) + }) + .declareMethod('initiate', function (roomid) { + var gadget = this, + handshake_gadget, + rtc_gadget, + blob; + return this.getDeclaredGadget('gadget_handshake.html') + .push(function (gad) { + handshake_gadget = gad; + roomid = "/"+roomid+"/"; + return handshake_gadget.create_room(roomid); + }) + .push(function () { + return gadget.declareGadget("gadget_webrtc_jio_bridge.html") + }) + .push(function (rg) { + // register peer + rtc_gadget = rg; + return rtc_gadget.register(roomid, 'master'); + }) + .push(function (r) { + var peerid = 'master'; + return handshake_gadget.wait_until_available(roomid, peerid+'_', function (offers) { + var connections = []; + for (var offer in offers) { + connections.push(gadget.connect(handshake_gadget, roomid, JSON.parse(offers[offer].target.result))); + } + return RSVP.all(connections); + }); + }); + }); + +}(window, rJS, document, RSVP, console, DOMException)); \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.xml new file mode 100644 index 00000000000..5ebd127ea6f --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_helper_js.xml @@ -0,0 +1,267 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + categories + + + + + + content_md5 + + + + + + contributors + + + + + + default_reference + gadget_webrtc_serverless_demo_master_helper.js + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_master_helper_js + + + language + en + + + portal_type + Web Script + + + short_title + + + + + + title + OfficeJS Share jIO Page JS + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1472731710.8 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39310.18911.56729 + + + state + current + + + time + + + + + + + + + + + 1472635108.35 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.html new file mode 100644 index 00000000000..6597d9bfcf3 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.html @@ -0,0 +1,97 @@ + + + + + + Serverless WebRTC - Master + + + + + + + + + + + + + + + +
+

Master

+
+ +
+
+

Create a Room

+
+ + +
+
+ + + +
+ + +
+ + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.xml new file mode 100644 index 00000000000..7cb73157cdc --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_html.xml @@ -0,0 +1,200 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + gadget_webrtc_serverless_demo_master.html + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_master_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + title + Master for demo WebRTC application + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + edit_workflow + + AAAAAAAAAAM= + + + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39288.14205.2013 + + + state + current + + + time + + + + + + + + + + + 1472633817.03 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.js new file mode 100644 index 00000000000..d4d5233a17b --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.js @@ -0,0 +1,115 @@ +/*global window, rJS, document, RSVP, console, DOMException */ +/*jslint indent: 2, maxerr: 3 */ +(function (window, rJS, document, RSVP, console, DOMException) { + "use strict"; + + ///////////////////////////////////////////////////////////////// + // Some functions + ///////////////////////////////////////////////////////////////// + function createJio(gadget) { + return gadget.getDeclaredGadget("jio_gadget") + .push(function (jio_gadget) { + return jio_gadget.createJio({ + type: "query", + sub_storage: { + type: "uuid", + sub_storage: { + "type": "indexeddb", + "database": "serverless" + } + } + }); + }); + } + + + ///////////////////////////////////////////////////////////////// + // Gadget behaviour + ///////////////////////////////////////////////////////////////// + rJS(window) + ///////////////////////////////////////////////////////////////// + // ready + ///////////////////////////////////////////////////////////////// + .ready(function (gadget) { + gadget.state_parameter_dict = {}; + return gadget.getElement() + .push(function (element) { + gadget.state_parameter_dict.element = element; + }); + }) + + // Configure jIO to indexeddb + .ready(function (gadget) { + return createJio(gadget); + }) + + ///////////////////////////////////////////////////////////////// + // acquired methods + ///////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////// + // published methods + ///////////////////////////////////////////////////////////////// + .allowPublicAcquisition("jio_post", function (my_param_list) { + var gadget = this; + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (my_gadget) { + return my_gadget.post.apply(my_gadget, my_param_list); + }); + }) + .allowPublicAcquisition("jio_get", function (my_param_list) { + var gadget = this; + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (my_gadget) { + return my_gadget.get.apply(my_gadget, my_param_list); + }); + }) + .allowPublicAcquisition("jio_allDocs", function (my_param_list) { + var gadget = this; + return new RSVP.Queue() + .push(function () { + return gadget.getDeclaredGadget("jio_gadget"); + }) + .push(function (my_gadget) { + return my_gadget.allDocs.apply(my_gadget, my_param_list); + }); + }) + + ///////////////////////////////////////////////////////////////// + // declared methods + ///////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////// + // declared services + ///////////////////////////////////////////////////////////////// + .declareService(function () { + var gadget = this; + return new RSVP.Queue() + .push(function() { + return gadget.getElement() + }) + .push(function (element) { + function initializeWebrtcConnecton(my_event) { + var value = element.querySelector(".ops-socket-connector").value; + return gadget.getDeclaredGadget('share_storage_via_webrtc') + .push(function(g){ + g.initiate(value); + }) + } + + // Listen to form submit + return loopEventListener( + element, + 'submit', + false, + initializeWebrtcConnecton + ); + }) + }); +}(window, rJS, document, RSVP, console, DOMException)); diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.xml new file mode 100644 index 00000000000..0c92d2933aa --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_master_js.xml @@ -0,0 +1,196 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + default_reference + gadget_webrtc_serverless_demo_master.js + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_master_js + + + language + en + + + portal_type + Web Script + + + short_title + + + + + + title + Master for demo WebRTC application + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + edit_workflow + + AAAAAAAAAAM= + + + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39307.56761.32460 + + + state + current + + + time + + + + + + + + + + + 1472634987.37 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.html new file mode 100644 index 00000000000..c9e930648f5 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.html @@ -0,0 +1,28 @@ + + + + + + Jio Gadget + + + + + + + + + + +
+ +
+ +
+ + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.xml new file mode 100644 index 00000000000..97dacbe0e04 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_html.xml @@ -0,0 +1,259 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + gadget_webrtc_serverless_demo_slave_helper.html + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_slave_helper_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + title + helper for webrtc demo application slave + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966544.8 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39280.31348.37853 + + + state + current + + + time + + + + + + + + + + + 1472633497.48 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.js new file mode 100644 index 00000000000..4c1402e69b4 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.js @@ -0,0 +1,133 @@ +/*global window, rJS, document, RSVP */ +/*jslint indent: 2, maxerr: 3 */ +(function (window, rJS, document, RSVP) { + "use strict"; + + var timeout = 600000; + + function S4() { + return ('0000' + Math.floor( + Math.random() * 0x10000 /* 65536 */ + ).toString(16)).slice(-4); + } + + function UUID() { + return S4() + S4() + "-" + + S4() + "-" + + S4() + "-" + + S4() + "-" + + S4() + S4() + S4(); + } + + function wrapJioAccess(gadget, method_name, argument_list) { + return gadget.getDeclaredGadget('gadget_webrtc_jio_bridge.html') + .push(function (rtc_gadget) { + gadget.state_parameter_dict.message_count += 1; + gadget.state_parameter_dict.message_dict[gadget.state_parameter_dict.message_count] = RSVP.defer(); + return RSVP.all([ + rtc_gadget.send(JSON.stringify({ + id: gadget.state_parameter_dict.message_count, + type: "jio_query", + method_name: method_name, + argument_list: Array.prototype.slice.call(argument_list) + })), + RSVP.any([ + RSVP.timeout(timeout), + gadget.state_parameter_dict.message_dict[gadget.state_parameter_dict.message_count].promise + ]) + ]); + }) + .push(function (result_list) { + return result_list[1]; + }); + } + + function declareSubGadget(gadget, url) { + var container_element = gadget.state_parameter_dict.element.querySelector("." + url.split(".")[0]), + element = document.createElement("div"); + + container_element.innerHTML = ""; + container_element.appendChild(element); + return gadget.declareGadget(url, { + element: element, + scope: url, + sandbox: "public" + }); + } + + rJS(window) + + .ready(function (gadget) { + // Initialize the gadget local parameters + gadget.state_parameter_dict = {}; + return gadget.getElement() + .push(function (element) { + gadget.state_parameter_dict.element = element; + }); + }) + + .allowPublicAcquisition("notifyDataChannelMessage", function (argument_list) { + var json = JSON.parse(argument_list[0]), + context = this; + if (json.type === "jio_response") { + context.state_parameter_dict.message_dict[json.id].resolve(json.result); + } else if (json.type === "error") { + context.state_parameter_dict.message_dict[json.id].reject(json.result); + } + // Drop all other kind of messages + }) + + .allowPublicAcquisition("notifyWebSocketMessage", function (argument_list) { + + var json = JSON.parse(argument_list[0]), + gadget = this; + + if (json.action === "answer") { + if (json.to === gadget.state_parameter_dict.uuid) { + gadget.state_parameter_dict.answer_defer.resolve(json.data); + } + } + + }) + + .declareMethod('createJio', function (options) { + var context = this, + roomid = "/"+options.room+"/", + rtc_gadget, + handshake_gadget; + + return declareSubGadget(context, 'gadget_webrtc_jio_bridge.html') + .push(function (gadget) { + context.state_parameter_dict.uuid = UUID(); + context.state_parameter_dict.message_count = 0; + context.state_parameter_dict.message_dict = {}; + rtc_gadget = gadget; + + // register peer + return rtc_gadget.register(roomid, context.state_parameter_dict.uuid); + }) + .push(function(peers){ + var options = { roomid: roomid, + peerid: context.state_parameter_dict.uuid, + initiator: true, + to: 'master' } + return rtc_gadget.connect(options); + }) + }) + + .declareMethod('allDocs', function () { + return wrapJioAccess(this, 'allDocs', arguments); + }) + .declareMethod('get', function () { + return wrapJioAccess(this, 'get', arguments); + }) + .declareMethod('put', function () { + return wrapJioAccess(this, 'put', arguments); + }) + .declareMethod('post', function () { + return wrapJioAccess(this, 'post', arguments); + }) + .declareMethod('remove', function () { + return wrapJioAccess(this, 'remove', arguments); + }) +}(window, rJS, document, RSVP)); \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.xml new file mode 100644 index 00000000000..196d3efb153 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_helper_js.xml @@ -0,0 +1,267 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + categories + + + + + + content_md5 + + + + + + contributors + + + + + + default_reference + gadget_webrtc_serverless_demo_slave_helper.js + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_slave_helper_js + + + language + en + + + portal_type + Web Script + + + short_title + + + + + + title + helper for webrtc demo application slave (js) + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1471966535.92 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39312.52960.8294 + + + state + current + + + time + + + + + + + + + + + 1472635259.04 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.html new file mode 100644 index 00000000000..37071c17bff --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.html @@ -0,0 +1,100 @@ + + + + + + Serverless WebRTC - Slave + + + + + + + + + + + + + + + + +
+ + +
+

Status:

+ +
+ +
+
+

Welcome Minions of WWW!

+ + +
+
+ +
+
+

Create Records

+ + + +
+
+ +
+
+

Query Records

+ + +
+ +
+ + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.xml new file mode 100644 index 00000000000..06e547342ea --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_html.xml @@ -0,0 +1,322 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + gadget_webrtc_serverless_demo_slave.html + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_slave_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + title + Slave for demo WebRTC application + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + processing_status_workflow + + AAAAAAAAAAU= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1472830277.08 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.40762.36916.46574 + + + state + current + + + time + + + + + + + + + + + 1472722249.04 + UTC + + + + + + + + + + + + + + + + + + + + + action + detect_converted_file + + + actor + zope + + + comment + + + + error_message + + + + external_processing_state + converted + + + serial + 0.0.0.0 + + + time + + + + + + + + + + + 1472722195.95 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_js.xml new file mode 100644 index 00000000000..48ace45a190 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_serverless_demo_slave_js.xml @@ -0,0 +1,458 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + default_reference + gadget_webrtc_serverless_demo_slave.js + + + description + Webrtc with JIO demo App + + + id + gadget_webrtc_serverless_demo_slave_js + + + language + + + + + + portal_type + Web Manifest + + + short_title + + + + + + text_content + + + + title + Slave for demo WebRTC application + + + version + + + + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + edit_workflow + + AAAAAAAAAAM= + + + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.39271.9120.48793 + + + state + current + + + time + + + + + + + + + + + 1472632772.63 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.html b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.html new file mode 100644 index 00000000000..d798b6f2b51 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.html @@ -0,0 +1,20 @@ + + + + + + + Share jIO + + + + + + + +
+
+
+
+ + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.xml new file mode 100644 index 00000000000..6320bbeca52 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_html.xml @@ -0,0 +1,338 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + categories + + + + + + content_md5 + + + + + + content_type + text/html + + + contributors + + + + + + default_reference + gadget_webrtc_websocket_handshake.html + + + description + Websocket handshaking gadget for Webrtc Gadget + + + id + gadget_webrtc_websocket_handshake_html + + + language + en + + + portal_type + Web Page + + + short_title + + + + + + text_content_substitution_mapping_method_id + WebPage_getRenderJSSubstitutionMappingDict + + + title + Gadget to handle handshaking using websocket + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + processing_status_workflow + + AAAAAAAAAAU= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1472825428.9 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 953.42501.63327.60416 + + + state + current + + + time + + + + + + + + + + + 1474538090.89 + UTC + + + + + + + + + + + + + + + + + + + + + action + detect_converted_file + + + actor + zope + + + comment + + + + error_message + + + + external_processing_state + converted + + + serial + 0.0.0.0 + + + time + + + + + + + + + + + 1472810439.97 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.js new file mode 100644 index 00000000000..8787d080aad --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.js @@ -0,0 +1,81 @@ +/*jslint indent:2, maxlen:80, nomen:true */ +/*global rJS, window, WebSocket, RSVP*/ + +(function (window, rJS, RSVP) { + "use strict"; + + rJS(window) + .ready(function (g) { + g.props = {}; + g.props.notify_websocket_message = function() {}; + }) + + .allowPublicAcquisition("notifyWebSocketMessage", function (argument_list) { + if ( JSON.parse(argument_list[0]).from !== this.props.peerid) { + return this.props.notify_websocket_message(argument_list); + } + }) + + .allowPublicAcquisition("notifyWebSocketClosed", function (argument_list) { + return; + }) + + .declareMethod('create_room', function(roomid) { + this.props.roomid = roomid; + return RSVP.promise; + }) + + .declareMethod('register', function(roomid, peerid, config) { + var gadget = this; + gadget.props.peerid = peerid; + return gadget.getDeclaredGadget('gadget_websocket') + .push(function (socket_gadget) { + gadget.props.ws_url = config.url; + return socket_gadget.createSocket(config.url); + }); + }) + + .declareMethod('handle_offer_answer', function (roomid, offer) { + var gadget = this; + return new RSVP.Queue() + .push(function() { + return jIO.util.readBlobAsText(offer.data); + }) + .push(function (result) { + offer = result.target.result; + return gadget.getDeclaredGadget("gadget_websocket"); + }) + .push(function (g) { + return g.send(offer); + }); + }) + + .declareMethod('get_answer', function (roomid, attachment) { + var gadget = this; + return; + }) + + .declareMethod('get_offers', function (roomid) { + var gadget = this; + return; + }) + + .declareMethod('wait_until_available', function (roomid, query, prom) { + // execute given function on each websocket message + var gadget = this, + response, + offers; + + gadget.props.notify_websocket_message = prom; + }) + + .declareMethod('close', function (roomid, peerid, to, listner) { + if (!listner) { + var gadget = this; + return gadget.getDeclaredGadget("gadget_websocket") + .push(function (socket_gadget) { + return socket_gadget.close(); + }); + } + }); +})(window, rJS, RSVP); \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.xml new file mode 100644 index 00000000000..9b528e8e4be --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_websocket_handshake_js.xml @@ -0,0 +1,318 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + default_reference + gadget_webrtc_websocket_handshake.js + + + description + Websocket handshaking gadget for Webrtc Gadget + + + id + gadget_webrtc_websocket_handshake_js + + + language + en + + + portal_type + Web Script + + + short_title + + + + + + title + Gadget to handle handshaking using websocket + + + version + 001 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + document_publication_workflow + + AAAAAAAAAAM= + + + + edit_workflow + + AAAAAAAAAAQ= + + + + processing_status_workflow + + AAAAAAAAAAU= + + + + + + + + + + + + + + + + + + + action + publish_alive + + + actor + zope + + + comment + + + + error_message + + + + time + + + + + + + + + + + 1472825439.19 + UTC + + + + + + + validation_state + published_alive + + + + + + + + + + + + + + + + + action + edit + + + actor + zope + + + comment + + + + + + error_message + + + + serial + 954.15519.18357.4829 + + + state + current + + + time + + + + + + + + + + + 1475139801.28 + UTC + + + + + + + + + + + + + + + + + + + + + action + detect_converted_file + + + actor + zope + + + comment + + + + error_message + + + + external_processing_state + converted + + + serial + 0.0.0.0 + + + time + + + + + + + + + + + 1472808633.09 + UTC + + + + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/bt/dependency_list b/bt5/erp5_webrtc_gadget/bt/dependency_list new file mode 100644 index 00000000000..6b7b89794e2 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/dependency_list @@ -0,0 +1 @@ +erp5_web_renderjs_ui \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_format_version b/bt5/erp5_webrtc_gadget/bt/template_format_version new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_format_version @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_keep_last_workflow_history_only_path_list b/bt5/erp5_webrtc_gadget/bt/template_keep_last_workflow_history_only_path_list new file mode 100644 index 00000000000..1f6d2e5dcf2 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_keep_last_workflow_history_only_path_list @@ -0,0 +1,16 @@ +web_page_module/gadget_webrtc_handshake_html +web_page_module/gadget_webrtc_handshake_js +web_page_module/gadget_webrtc_html +web_page_module/gadget_webrtc_jio_bridge_html +web_page_module/gadget_webrtc_jio_bridge_js +web_page_module/gadget_webrtc_js +web_page_module/gadget_webrtc_serverless_demo_master_helper_html +web_page_module/gadget_webrtc_serverless_demo_master_helper_js +web_page_module/gadget_webrtc_serverless_demo_master_html +web_page_module/gadget_webrtc_serverless_demo_master_js +web_page_module/gadget_webrtc_serverless_demo_slave_helper_html +web_page_module/gadget_webrtc_serverless_demo_slave_helper_js +web_page_module/gadget_webrtc_serverless_demo_slave_html +web_page_module/gadget_webrtc_serverless_demo_slave_js +web_page_module/gadget_webrtc_websocket_handshake_js +web_page_module/gadget_webrtc_websocket_handshake_html \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_path_list b/bt5/erp5_webrtc_gadget/bt/template_path_list new file mode 100644 index 00000000000..0a54b046676 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_path_list @@ -0,0 +1,16 @@ +web_page_module/gadget_webrtc_handshake_html +web_page_module/gadget_webrtc_handshake_js +web_page_module/gadget_webrtc_html +web_page_module/gadget_webrtc_jio_bridge_html +web_page_module/gadget_webrtc_jio_bridge_js +web_page_module/gadget_webrtc_js +web_page_module/gadget_webrtc_serverless_demo_master_helper_html +web_page_module/gadget_webrtc_serverless_demo_master_helper_js +web_page_module/gadget_webrtc_serverless_demo_master_html +web_page_module/gadget_webrtc_serverless_demo_master_js +web_page_module/gadget_webrtc_serverless_demo_slave_helper_html +web_page_module/gadget_webrtc_serverless_demo_slave_helper_js +web_page_module/gadget_webrtc_serverless_demo_slave_html +web_page_module/gadget_webrtc_serverless_demo_slave_js +web_page_module/gadget_webrtc_websocket_handshake_html +web_page_module/gadget_webrtc_websocket_handshake_js \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/title b/bt5/erp5_webrtc_gadget/bt/title new file mode 100644 index 00000000000..df026ad4fda --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/title @@ -0,0 +1 @@ +erp5_webrtc_gadget \ No newline at end of file -- 2.30.9 From 8282cf9dfa2d6a339c46280e48d6cc9ed96da74d Mon Sep 17 00:00:00 2001 From: Hardik Juneja Date: Wed, 28 Sep 2016 14:18:57 +0000 Subject: [PATCH 2/5] erp5_ui_test: Add high level unit tests for webrtc gadget --- .../portal_tests/webrtc_ui_zuite.xml | 26 + .../testGadgetWebrtcQunitTests.xml | 58 + .../testGadgetWebrtcQunitTests.zpt | 53 + .../web_page_module/qunit-2.0.1_css.css | 415 ++ .../web_page_module/qunit-2.0.1_css.xml | 119 + .../web_page_module/qunit-2.0.1_js.js | 4437 +++++++++++++++++ .../web_page_module/qunit-2.0.1_js.xml | 119 + .../webrtc_unit_test_html.html | 32 + .../web_page_module/webrtc_unit_test_html.xml | 119 + .../web_page_module/webrtc_unit_test_js.js | 231 + .../web_page_module/webrtc_unit_test_js.xml | 119 + .../web_site_module/webrtc_unit_test.xml | 473 ++ bt5/erp5_ui_test/bt/dependency_list | 3 +- bt5/erp5_ui_test/bt/template_path_list | 9 +- 14 files changed, 6211 insertions(+), 2 deletions(-) create mode 100644 bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite.xml create mode 100644 bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.xml create mode 100644 bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.zpt create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.css create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.xml create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.js create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.xml create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.html create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.xml create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.js create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.xml create mode 100644 bt5/erp5_ui_test/PathTemplateItem/web_site_module/webrtc_unit_test.xml diff --git a/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite.xml b/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite.xml new file mode 100644 index 00000000000..4148acec82b --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite.xml @@ -0,0 +1,26 @@ + + + + + + + + + + _objects + + + + + + id + webrtc_ui_zuite + + + title + + + + + + diff --git a/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.xml b/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.xml new file mode 100644 index 00000000000..49f505c2f56 --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.xml @@ -0,0 +1,58 @@ + + + + + + + + + + _bind_names + + + + + + + + + + _asgns + + + + name_subpath + traverse_subpath + + + + + + + + + + + content_type + text/html + + + expand + 0 + + + id + testGadgetWebrtcQunitTests + + + output_encoding + utf-8 + + + title + + + + + + diff --git a/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.zpt b/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.zpt new file mode 100644 index 00000000000..0b988a57835 --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/portal_tests/webrtc_ui_zuite/testGadgetWebrtcQunitTests.zpt @@ -0,0 +1,53 @@ + + + +Test Webrtc Gadget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Test OfficeJS UI
open${base_url}/bar_module/ListBoxZuite_reset
assertTextPresentReset Successfully.
open${base_url}/web_site_module/webrtc_unit_test/
pause3000
waitForElementPresent//*[@id="qunit-testresult"]/span[3]
assertTextPresent0 failed
assertTextNotPresent0 passed
+ + \ No newline at end of file diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.css b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.css new file mode 100644 index 00000000000..02df8a6b892 --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.css @@ -0,0 +1,415 @@ +/*! + * QUnit 2.0.1 + * https://qunitjs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2016-07-23T19:39Z + */ + +/** Font Family and Sizes */ + +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; +} + +#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-tests { font-size: smaller; } + + +/** Resets */ + +#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { + margin: 0; + padding: 0; +} + + +/** Header (excluding toolbar) */ + +#qunit-header { + padding: 0.5em 0 0.5em 1em; + + color: #8699A4; + background-color: #0D3349; + + font-size: 1.5em; + line-height: 1em; + font-weight: 400; + + border-radius: 5px 5px 0 0; +} + +#qunit-header a { + text-decoration: none; + color: #C2CCD1; +} + +#qunit-header a:hover, +#qunit-header a:focus { + color: #FFF; +} + +#qunit-banner { + height: 5px; +} + +#qunit-filteredTest { + padding: 0.5em 1em 0.5em 1em; + color: #366097; + background-color: #F4FF77; +} + +#qunit-userAgent { + padding: 0.5em 1em 0.5em 1em; + color: #FFF; + background-color: #2B81AF; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} + + +/** Toolbar */ + +#qunit-testrunner-toolbar { + padding: 0.5em 1em 0.5em 1em; + color: #5E740B; + background-color: #EEE; +} + +#qunit-testrunner-toolbar .clearfix { + height: 0; + clear: both; +} + +#qunit-testrunner-toolbar label { + display: inline-block; +} + +#qunit-testrunner-toolbar input[type=checkbox], +#qunit-testrunner-toolbar input[type=radio] { + margin: 3px; + vertical-align: -2px; +} + +#qunit-testrunner-toolbar input[type=text] { + box-sizing: border-box; + height: 1.6em; +} + +.qunit-url-config, +.qunit-filter, +#qunit-modulefilter { + display: inline-block; + line-height: 2.1em; +} + +.qunit-filter, +#qunit-modulefilter { + float: right; + position: relative; + margin-left: 1em; +} + +.qunit-url-config label { + margin-right: 0.5em; +} + +#qunit-modulefilter-search { + box-sizing: border-box; + width: 400px; +} + +#qunit-modulefilter-search-container:after { + position: absolute; + right: 0.3em; + content: "\25bc"; + color: black; +} + +#qunit-modulefilter-dropdown { + /* align with #qunit-modulefilter-search */ + box-sizing: border-box; + width: 400px; + position: absolute; + right: 0; + top: 50%; + margin-top: 0.8em; + + border: 1px solid #D3D3D3; + border-top: none; + border-radius: 0 0 .25em .25em; + color: #000; + background-color: #F5F5F5; + z-index: 99; +} + +#qunit-modulefilter-dropdown a { + color: inherit; + text-decoration: none; +} + +#qunit-modulefilter-dropdown .clickable.checked { + font-weight: bold; + color: #000; + background-color: #D2E0E6; +} + +#qunit-modulefilter-dropdown .clickable:hover { + color: #FFF; + background-color: #0D3349; +} + +#qunit-modulefilter-actions { + display: block; + overflow: auto; + + /* align with #qunit-modulefilter-dropdown-list */ + font: smaller/1.5em sans-serif; +} + +#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * { + box-sizing: border-box; + max-height: 2.8em; + display: block; + padding: 0.4em; +} + +#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button { + float: right; + font: inherit; +} + +#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child { + /* insert padding to align with checkbox margins */ + padding-left: 3px; +} + +#qunit-modulefilter-dropdown-list { + max-height: 200px; + overflow-y: auto; + margin: 0; + border-top: 2px groove threedhighlight; + padding: 0.4em 0 0; + font: smaller/1.5em sans-serif; +} + +#qunit-modulefilter-dropdown-list li { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#qunit-modulefilter-dropdown-list .clickable { + display: block; + padding-left: 0.15em; +} + + +/** Tests: Pass/Fail */ + +#qunit-tests { + list-style-position: inside; +} + +#qunit-tests li { + padding: 0.4em 1em 0.4em 1em; + border-bottom: 1px solid #FFF; + list-style-position: inside; +} + +#qunit-tests > li { + display: none; +} + +#qunit-tests li.running, +#qunit-tests li.pass, +#qunit-tests li.fail, +#qunit-tests li.skipped { + display: list-item; +} + +#qunit-tests.hidepass { + position: relative; +} + +#qunit-tests.hidepass li.running, +#qunit-tests.hidepass li.pass { + visibility: hidden; + position: absolute; + width: 0; + height: 0; + padding: 0; + border: 0; + margin: 0; +} + +#qunit-tests li strong { + cursor: pointer; +} + +#qunit-tests li.skipped strong { + cursor: default; +} + +#qunit-tests li a { + padding: 0.5em; + color: #C2CCD1; + text-decoration: none; +} + +#qunit-tests li p a { + padding: 0.25em; + color: #6B6464; +} +#qunit-tests li a:hover, +#qunit-tests li a:focus { + color: #000; +} + +#qunit-tests li .runtime { + float: right; + font-size: smaller; +} + +.qunit-assert-list { + margin-top: 0.5em; + padding: 0.5em; + + background-color: #FFF; + + border-radius: 5px; +} + +.qunit-source { + margin: 0.6em 0 0.3em; +} + +.qunit-collapsed { + display: none; +} + +#qunit-tests table { + border-collapse: collapse; + margin-top: 0.2em; +} + +#qunit-tests th { + text-align: right; + vertical-align: top; + padding: 0 0.5em 0 0; +} + +#qunit-tests td { + vertical-align: top; +} + +#qunit-tests pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +#qunit-tests del { + color: #374E0C; + background-color: #E0F2BE; + text-decoration: none; +} + +#qunit-tests ins { + color: #500; + background-color: #FFCACA; + text-decoration: none; +} + +/*** Test Counts */ + +#qunit-tests b.counts { color: #000; } +#qunit-tests b.passed { color: #5E740B; } +#qunit-tests b.failed { color: #710909; } + +#qunit-tests li li { + padding: 5px; + background-color: #FFF; + border-bottom: none; + list-style-position: inside; +} + +/*** Passing Styles */ + +#qunit-tests li li.pass { + color: #3C510C; + background-color: #FFF; + border-left: 10px solid #C6E746; +} + +#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } +#qunit-tests .pass .test-name { color: #366097; } + +#qunit-tests .pass .test-actual, +#qunit-tests .pass .test-expected { color: #999; } + +#qunit-banner.qunit-pass { background-color: #C6E746; } + +/*** Failing Styles */ + +#qunit-tests li li.fail { + color: #710909; + background-color: #FFF; + border-left: 10px solid #EE5757; + white-space: pre; +} + +#qunit-tests > li:last-child { + border-radius: 0 0 5px 5px; +} + +#qunit-tests .fail { color: #000; background-color: #EE5757; } +#qunit-tests .fail .test-name, +#qunit-tests .fail .module-name { color: #000; } + +#qunit-tests .fail .test-actual { color: #EE5757; } +#qunit-tests .fail .test-expected { color: #008000; } + +#qunit-banner.qunit-fail { background-color: #EE5757; } + +/*** Skipped tests */ + +#qunit-tests .skipped { + background-color: #EBECE9; +} + +#qunit-tests .qunit-skipped-label { + background-color: #F4FF77; + display: inline-block; + font-style: normal; + color: #366097; + line-height: 1.8em; + padding: 0 0.5em; + margin: -0.4em 0.4em -0.4em 0; +} + +/** Result */ + +#qunit-testresult { + padding: 0.5em 1em 0.5em 1em; + + color: #2B81AF; + background-color: #D2E0E6; + + border-bottom: 1px solid #FFF; +} +#qunit-testresult .module-name { + font-weight: 700; +} + +/** Fixture */ + +#qunit-fixture { + position: absolute; + top: -10000px; + left: -10000px; + width: 1000px; + height: 1000px; +} \ No newline at end of file diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.xml b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.xml new file mode 100644 index 00000000000..1f783fe5bb0 --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_css.xml @@ -0,0 +1,119 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/css + + + default_reference + qunit-2.0.1.css + + + description + + + + + + id + qunit-2.0.1_css + + + language + + + + + + portal_type + Web Style + + + short_title + + + + + + title + Qunit-2.0.1 (css) + + + version + + + + + + + + diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.js b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.js new file mode 100644 index 00000000000..47c904cf329 --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.js @@ -0,0 +1,4437 @@ +/*! + * QUnit 2.0.1 + * https://qunitjs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2016-07-23T19:39Z + */ + +( function( global ) { + +var QUnit = {}; + +var Date = global.Date; +var now = Date.now || function() { + return new Date().getTime(); +}; + +var setTimeout = global.setTimeout; +var clearTimeout = global.clearTimeout; + +// Store a local window from the global to allow direct references. +var window = global.window; + +var defined = { + document: window && window.document !== undefined, + setTimeout: setTimeout !== undefined, + sessionStorage: ( function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }() ) +}; + +var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ); +var globalStartCalled = false; +var runStarted = false; + +var autorun = false; + +var toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty; + +// Returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { + if ( result[ i ] === b[ j ] ) { + result.splice( i, 1 ); + i--; + break; + } + } + } + return result; +} + +// From jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; +} + +/** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ +function objectValues ( obj ) { + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[ key ]; + vals[ key ] = val === Object( val ) ? objectValues( val ) : val; + } + } + return vals; +} + +function extend( a, b, undefOnly ) { + for ( var prop in b ) { + if ( hasOwn.call( b, prop ) ) { + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { + a[ prop ] = b[ prop ]; + } + } + } + + return a; +} + +function objectType( obj ) { + if ( typeof obj === "undefined" ) { + return "undefined"; + } + + // Consider: typeof null === object + if ( obj === null ) { + return "null"; + } + + var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), + type = match && match[ 1 ]; + + switch ( type ) { + case "Number": + if ( isNaN( obj ) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Set": + case "Map": + case "Date": + case "RegExp": + case "Function": + case "Symbol": + return type.toLowerCase(); + } + if ( typeof obj === "object" ) { + return "object"; + } +} + +// Safe object type checking +function is( type, obj ) { + return QUnit.objectType( obj ) === type; +} + +// Doesn't support IE9, it will return undefined on these browsers +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 4 : offset; + + var stack, include, i; + + if ( e.stack ) { + stack = e.stack.split( "\n" ); + if ( /^error$/i.test( stack[ 0 ] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; + } +} + +function sourceFromStacktrace( offset ) { + var error = new Error(); + + // Support: Safari <=7 only, IE <=10 - 11 only + // Not all browsers generate the `stack` property for `new Error()`, see also #636 + if ( !error.stack ) { + try { + throw error; + } catch ( err ) { + error = err; + } + } + + return extractStacktrace( error, offset ); +} + +/** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ +var config = { + + // The queue of tests to run + queue: [], + + // Block until document ready + blocking: true, + + // By default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + // By default, modify document.title when suite is done + altertitle: true, + + // HTML Reporter: collapse every test except the first failing test + // If false, all failing tests will be expanded + collapse: true, + + // By default, scroll to top of the page when suite is done + scrolltop: true, + + // Depth up-to which object will be dumped + maxDepth: 5, + + // When enabled, all tests must call expect() + requireExpects: false, + + // Placeholder for user-configurable form-exposed URL parameters + urlConfig: [], + + // Set of all modules. + modules: [], + + // Stack of nested modules + moduleStack: [], + + // The first unnamed module + currentModule: { + name: "", + tests: [] + }, + + callbacks: {} +}; + +// Push a loose unnamed module to the modules collection +config.modules.push( config.currentModule ); + +// Register logging callbacks +function registerLoggingCallbacks( obj ) { + var i, l, key, + callbackNames = [ "begin", "done", "log", "testStart", "testDone", + "moduleStart", "moduleDone" ]; + + function registerLoggingCallback( key ) { + var loggingCallback = function( callback ) { + if ( objectType( callback ) !== "function" ) { + throw new Error( + "QUnit logging methods require a callback function as their first parameters." + ); + } + + config.callbacks[ key ].push( callback ); + }; + + return loggingCallback; + } + + for ( i = 0, l = callbackNames.length; i < l; i++ ) { + key = callbackNames[ i ]; + + // Initialize key collection of logging callback + if ( objectType( config.callbacks[ key ] ) === "undefined" ) { + config.callbacks[ key ] = []; + } + + obj[ key ] = registerLoggingCallback( key ); + } +} + +function runLoggingCallbacks( key, args ) { + var i, l, callbacks; + + callbacks = config.callbacks[ key ]; + for ( i = 0, l = callbacks.length; i < l; i++ ) { + callbacks[ i ]( args ); + } +} + +( function() { + if ( !defined.document ) { + return; + } + + // `onErrorFnPrev` initialized at top of scope + // Preserve other handlers + var onErrorFnPrev = window.onerror; + + // Cover uncaught exceptions + // Returning true will suppress the default browser handler, + // returning false will let it run. + window.onerror = function( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend( function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: true } ) ); + } + return false; + } + + return ret; + }; +}() ); + +// Figure out if we're running the tests from a server or not +QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); + +// Expose the current QUnit version +QUnit.version = "2.0.1"; + +extend( QUnit, { + + // Call on start of module test to prepend name to all tests + module: function( name, testEnvironment, executeNow ) { + var module, moduleFns; + var currentModule = config.currentModule; + + if ( arguments.length === 2 ) { + if ( objectType( testEnvironment ) === "function" ) { + executeNow = testEnvironment; + testEnvironment = undefined; + } + } + + module = createModule(); + + if ( testEnvironment && ( testEnvironment.setup || testEnvironment.teardown ) ) { + console.warn( + "Module's `setup` and `teardown` are not hooks anymore on QUnit 2.0, use " + + "`beforeEach` and `afterEach` instead\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); + } + + moduleFns = { + before: setHook( module, "before" ), + beforeEach: setHook( module, "beforeEach" ), + afterEach: setHook( module, "afterEach" ), + after: setHook( module, "after" ) + }; + + if ( objectType( executeNow ) === "function" ) { + config.moduleStack.push( module ); + setCurrentModule( module ); + executeNow.call( module.testEnvironment, moduleFns ); + config.moduleStack.pop(); + module = module.parentModule || currentModule; + } + + setCurrentModule( module ); + + function createModule() { + var parentModule = config.moduleStack.length ? + config.moduleStack.slice( -1 )[ 0 ] : null; + var moduleName = parentModule !== null ? + [ parentModule.name, name ].join( " > " ) : name; + var module = { + name: moduleName, + parentModule: parentModule, + tests: [], + moduleId: generateHash( moduleName ), + testsRun: 0 + }; + + var env = {}; + if ( parentModule ) { + parentModule.childModule = module; + extend( env, parentModule.testEnvironment ); + delete env.beforeEach; + delete env.afterEach; + } + extend( env, testEnvironment ); + module.testEnvironment = env; + + config.modules.push( module ); + return module; + } + + function setCurrentModule( module ) { + config.currentModule = module; + } + + }, + + test: test, + + skip: skip, + + only: only, + + start: function( count ) { + var globalStartAlreadyCalled = globalStartCalled; + + if ( !config.current ) { + globalStartCalled = true; + + if ( runStarted ) { + throw new Error( "Called start() while test already started running" ); + } else if ( globalStartAlreadyCalled || count > 1 ) { + throw new Error( "Called start() outside of a test context too many times" ); + } else if ( config.autostart ) { + throw new Error( "Called start() outside of a test context when " + + "QUnit.config.autostart was true" ); + } else if ( !config.pageLoaded ) { + + // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it + config.autostart = true; + return; + } + } else { + throw new Error( + "QUnit.start cannot be called inside a test context. This feature is removed in " + + "QUnit 2.0. For async tests, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); + } + + scheduleBegin(); + }, + + config: config, + + is: is, + + objectType: objectType, + + extend: extend, + + load: function() { + config.pageLoaded = true; + + // Initialize the configuration options + extend( config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: 0, + updateRate: 1000, + autostart: true, + filter: "" + }, true ); + + if ( !runStarted ) { + config.blocking = false; + + if ( config.autostart ) { + scheduleBegin(); + } + } + }, + + stack: function( offset ) { + offset = ( offset || 0 ) + 2; + return sourceFromStacktrace( offset ); + } +} ); + +registerLoggingCallbacks( QUnit ); + +function scheduleBegin() { + + runStarted = true; + + // Add a slight delay to allow definition of more modules and tests. + if ( defined.setTimeout ) { + setTimeout( function() { + begin(); + }, 13 ); + } else { + begin(); + } +} + +function begin() { + var i, l, + modulesLog = []; + + // If the test run hasn't officially begun yet + if ( !config.started ) { + + // Record the time of the test run's beginning + config.started = now(); + + // Delete the loose unnamed module if unused. + if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { + config.modules.shift(); + } + + // Avoid unnecessary information by not logging modules' test environments + for ( i = 0, l = config.modules.length; i < l; i++ ) { + modulesLog.push( { + name: config.modules[ i ].name, + tests: config.modules[ i ].tests + } ); + } + + // The test run is officially beginning now + runLoggingCallbacks( "begin", { + totalTests: Test.count, + modules: modulesLog + } ); + } + + config.blocking = false; + process( true ); +} + +function process( last ) { + function next() { + process( last ); + } + var start = now(); + config.depth = ( config.depth || 0 ) + 1; + + while ( config.queue.length && !config.blocking ) { + if ( !defined.setTimeout || config.updateRate <= 0 || + ( ( now() - start ) < config.updateRate ) ) { + if ( config.current ) { + + // Reset async tracking for each phase of the Test lifecycle + config.current.usedAsync = false; + } + config.queue.shift()(); + } else { + setTimeout( next, 13 ); + break; + } + } + config.depth--; + if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { + done(); + } +} + +function done() { + var runtime, passed; + + autorun = true; + + // Log the last module results + if ( config.previousModule ) { + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started + } ); + } + delete config.previousModule; + + runtime = now() - config.started; + passed = config.stats.all - config.stats.bad; + + runLoggingCallbacks( "done", { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + } ); +} + +function setHook( module, hookName ) { + if ( module.testEnvironment === undefined ) { + module.testEnvironment = {}; + } + + return function( callback ) { + module.testEnvironment[ hookName ] = callback; + }; +} + +var unitSampler, + focused = false, + priorityCount = 0; + +function Test( settings ) { + var i, l; + + ++Test.count; + + this.expected = null; + extend( this, settings ); + this.assertions = []; + this.semaphore = 0; + this.usedAsync = false; + this.module = config.currentModule; + this.stack = sourceFromStacktrace( 3 ); + + // Register unique strings + for ( i = 0, l = this.module.tests; i < l.length; i++ ) { + if ( this.module.tests[ i ].name === this.testName ) { + this.testName += " "; + } + } + + this.testId = generateHash( this.module.name, this.testName ); + + this.module.tests.push( { + name: this.testName, + testId: this.testId + } ); + + if ( settings.skip ) { + + // Skipped tests will fully ignore any sent callback + this.callback = function() {}; + this.async = false; + this.expected = 0; + } else { + this.assert = new Assert( this ); + } +} + +Test.count = 0; + +Test.prototype = { + before: function() { + if ( + + // Emit moduleStart when we're switching from one module to another + this.module !== config.previousModule || + + // They could be equal (both undefined) but if the previousModule property doesn't + // yet exist it means this is the first test in a suite that isn't wrapped in a + // module, in which case we'll just emit a moduleStart event for 'undefined'. + // Without this, reporters can get testStart before moduleStart which is a problem. + !hasOwn.call( config, "previousModule" ) + ) { + if ( hasOwn.call( config, "previousModule" ) ) { + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started + } ); + } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0, started: now() }; + runLoggingCallbacks( "moduleStart", { + name: this.module.name, + tests: this.module.tests + } ); + } + + config.current = this; + + if ( this.module.testEnvironment ) { + delete this.module.testEnvironment.before; + delete this.module.testEnvironment.beforeEach; + delete this.module.testEnvironment.afterEach; + delete this.module.testEnvironment.after; + } + this.testEnvironment = extend( {}, this.module.testEnvironment ); + + this.started = now(); + runLoggingCallbacks( "testStart", { + name: this.testName, + module: this.module.name, + testId: this.testId + } ); + + if ( !config.pollution ) { + saveGlobal(); + } + }, + + run: function() { + var promise; + + config.current = this; + + this.callbackStarted = now(); + + if ( config.notrycatch ) { + runTest( this ); + return; + } + + try { + runTest( this ); + } catch ( e ) { + this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); + + // Else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + internalRecover( this ); + } + } + + function runTest( test ) { + promise = test.callback.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise ); + } + }, + + after: function() { + checkPollution(); + }, + + queueHook: function( hook, hookName, hookOwner ) { + var promise, + test = this; + return function runHook() { + if ( hookName === "before" ) { + if ( hookOwner.testsRun !== 0 ) { + return; + } + + test.preserveEnvironment = true; + } + + if ( hookName === "after" && hookOwner.testsRun !== numberOfTests( hookOwner ) - 1 ) { + return; + } + + config.current = test; + if ( config.notrycatch ) { + callHook(); + return; + } + try { + callHook(); + } catch ( error ) { + test.pushFailure( hookName + " failed on " + test.testName + ": " + + ( error.message || error ), extractStacktrace( error, 0 ) ); + } + + function callHook() { + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); + } + }; + }, + + // Currently only used for module level hooks, can be used to add global level ones + hooks: function( handler ) { + var hooks = []; + + function processHooks( test, module ) { + if ( module.parentModule ) { + processHooks( test, module.parentModule ); + } + if ( module.testEnvironment && + QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) { + hooks.push( test.queueHook( module.testEnvironment[ handler ], handler, module ) ); + } + } + + // Hooks are ignored on skipped tests + if ( !this.skip ) { + processHooks( this, this.module ); + } + return hooks; + }, + + finish: function() { + config.current = this; + if ( config.requireExpects && this.expected === null ) { + this.pushFailure( "Expected number of assertions to be defined, but expect() was " + + "not called.", this.stack ); + } else if ( this.expected !== null && this.expected !== this.assertions.length ) { + this.pushFailure( "Expected " + this.expected + " assertions, but " + + this.assertions.length + " were run", this.stack ); + } else if ( this.expected === null && !this.assertions.length ) { + this.pushFailure( "Expected at least one assertion, but none were run - call " + + "expect(0) to accept zero assertions.", this.stack ); + } + + var i, + skipped = !!this.skip, + bad = 0; + + this.runtime = now() - this.started; + + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + for ( i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[ i ].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + notifyTestsRan( this.module ); + runLoggingCallbacks( "testDone", { + name: this.testName, + module: this.module.name, + skipped: skipped, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length, + runtime: skipped ? 0 : this.runtime, + + // HTML Reporter use + assertions: this.assertions, + testId: this.testId, + + // Source of Test + source: this.stack + } ); + + config.current = undefined; + }, + + preserveTestEnvironment: function() { + if ( this.preserveEnvironment ) { + this.module.testEnvironment = this.testEnvironment; + this.testEnvironment = extend( {}, this.module.testEnvironment ); + } + }, + + queue: function() { + var priority, + test = this; + + if ( !this.valid() ) { + return; + } + + function run() { + + // Each of these can by async + synchronize( [ + function() { + test.before(); + }, + + test.hooks( "before" ), + + function() { + test.preserveTestEnvironment(); + }, + + test.hooks( "beforeEach" ), + + function() { + test.run(); + }, + + test.hooks( "afterEach" ).reverse(), + test.hooks( "after" ).reverse(), + + function() { + test.after(); + }, + + function() { + test.finish(); + } + ] ); + } + + // Prioritize previously failed tests, detected from sessionStorage + priority = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); + + return synchronize( run, priority, config.seed ); + }, + + pushResult: function( resultInfo ) { + + // Destructure of resultInfo = { result, actual, expected, message, negative } + var source, + details = { + module: this.module.name, + name: this.testName, + result: resultInfo.result, + message: resultInfo.message, + actual: resultInfo.actual, + expected: resultInfo.expected, + testId: this.testId, + negative: resultInfo.negative || false, + runtime: now() - this.started + }; + + if ( !resultInfo.result ) { + source = sourceFromStacktrace(); + + if ( source ) { + details.source = source; + } + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push( { + result: !!resultInfo.result, + message: resultInfo.message + } ); + }, + + pushFailure: function( message, source, actual ) { + if ( !( this instanceof Test ) ) { + throw new Error( "pushFailure() assertion outside test context, was " + + sourceFromStacktrace( 2 ) ); + } + + var details = { + module: this.module.name, + name: this.testName, + result: false, + message: message || "error", + actual: actual || null, + testId: this.testId, + runtime: now() - this.started + }; + + if ( source ) { + details.source = source; + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push( { + result: false, + message: message + } ); + }, + + resolvePromise: function( promise, phase ) { + var then, resume, message, + test = this; + if ( promise != null ) { + then = promise.then; + if ( QUnit.objectType( then ) === "function" ) { + resume = internalStop( test ); + then.call( + promise, + function() { resume(); }, + function( error ) { + message = "Promise rejected " + + ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + + " " + test.testName + ": " + ( error.message || error ); + test.pushFailure( message, extractStacktrace( error, 0 ) ); + + // Else next test will carry the responsibility + saveGlobal(); + + // Unblock + resume(); + } + ); + } + } + }, + + valid: function() { + var filter = config.filter, + regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ), + module = config.module && config.module.toLowerCase(), + fullName = ( this.module.name + ": " + this.testName ); + + function moduleChainNameMatch( testModule ) { + var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; + if ( testModuleName === module ) { + return true; + } else if ( testModule.parentModule ) { + return moduleChainNameMatch( testModule.parentModule ); + } else { + return false; + } + } + + function moduleChainIdMatch( testModule ) { + return inArray( testModule.moduleId, config.moduleId ) > -1 || + testModule.parentModule && moduleChainIdMatch( testModule.parentModule ); + } + + // Internally-generated tests are always valid + if ( this.callback && this.callback.validTest ) { + return true; + } + + if ( config.moduleId && config.moduleId.length > 0 && + !moduleChainIdMatch( this.module ) ) { + + return false; + } + + if ( config.testId && config.testId.length > 0 && + inArray( this.testId, config.testId ) < 0 ) { + + return false; + } + + if ( module && !moduleChainNameMatch( this.module ) ) { + return false; + } + + if ( !filter ) { + return true; + } + + return regexFilter ? + this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) : + this.stringFilter( filter, fullName ); + }, + + regexFilter: function( exclude, pattern, flags, fullName ) { + var regex = new RegExp( pattern, flags ); + var match = regex.test( fullName ); + + return match !== exclude; + }, + + stringFilter: function( filter, fullName ) { + filter = filter.toLowerCase(); + fullName = fullName.toLowerCase(); + + var include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.slice( 1 ); + } + + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; + } + + // Otherwise, do the opposite + return !include; + } +}; + +QUnit.pushFailure = function() { + if ( !QUnit.config.current ) { + throw new Error( "pushFailure() assertion outside test context, in " + + sourceFromStacktrace( 2 ) ); + } + + // Gets current test obj + var currentTest = QUnit.config.current; + + return currentTest.pushFailure.apply( currentTest, arguments ); +}; + +// Based on Java's String.hashCode, a simple but not +// rigorously collision resistant hashing function +function generateHash( module, testName ) { + var hex, + i = 0, + hash = 0, + str = module + "\x1C" + testName, + len = str.length; + + for ( ; i < len; i++ ) { + hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + hex = ( 0x100000000 + hash ).toString( 16 ); + if ( hex.length < 8 ) { + hex = "0000000" + hex; + } + + return hex.slice( -8 ); +} + +function synchronize( callback, priority, seed ) { + var last = !priority, + index; + + if ( QUnit.objectType( callback ) === "array" ) { + while ( callback.length ) { + synchronize( callback.shift() ); + } + return; + } + + if ( priority ) { + config.queue.splice( priorityCount++, 0, callback ); + } else if ( seed ) { + if ( !unitSampler ) { + unitSampler = unitSamplerGenerator( seed ); + } + + // Insert into a random position after all priority items + index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) ); + config.queue.splice( priorityCount + index, 0, callback ); + } else { + config.queue.push( callback ); + } + + if ( autorun && !config.blocking ) { + process( last ); + } +} + +function unitSamplerGenerator( seed ) { + + // 32-bit xorshift, requires only a nonzero seed + // http://excamera.com/sphinx/article-xorshift.html + var sample = parseInt( generateHash( seed ), 16 ) || -1; + return function() { + sample ^= sample << 13; + sample ^= sample >>> 17; + sample ^= sample << 5; + + // ECMAScript has no unsigned number type + if ( sample < 0 ) { + sample += 0x100000000; + } + + return sample / 0x100000000; + }; +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in global ) { + if ( hasOwn.call( global, key ) ) { + + // In Opera sometimes DOM element ids show up here, ignore them + if ( /^qunit-test-output/.test( key ) ) { + continue; + } + config.pollution.push( key ); + } + } + } +} + +function checkPollution() { + var newGlobals, + deletedGlobals, + old = config.pollution; + + saveGlobal(); + + newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); + } + + deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); + } +} + +// Will be exposed as QUnit.test +function test( testName, callback ) { + if ( focused ) { return; } + + var newTest; + + newTest = new Test( { + testName: testName, + callback: callback + } ); + + newTest.queue(); +} + +// Will be exposed as QUnit.skip +function skip( testName ) { + if ( focused ) { return; } + + var test = new Test( { + testName: testName, + skip: true + } ); + + test.queue(); +} + +// Will be exposed as QUnit.only +function only( testName, callback ) { + var newTest; + + if ( focused ) { return; } + + QUnit.config.queue.length = 0; + focused = true; + + newTest = new Test( { + testName: testName, + callback: callback + } ); + + newTest.queue(); +} + +// Put a hold on processing and return a function that will release it. +function internalStop( test ) { + var released = false; + + test.semaphore += 1; + config.blocking = true; + + // Set a recovery timeout, if so configured. + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout( function() { + QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); + internalRecover( test ); + }, config.testTimeout ); + } + + return function resume() { + if ( released ) { + return; + } + + released = true; + test.semaphore -= 1; + internalStart( test ); + }; +} + +// Forcefully release all processing holds. +function internalRecover( test ) { + test.semaphore = 0; + internalStart( test ); +} + +// Release a processing hold, scheduling a resumption attempt if no holds remain. +function internalStart( test ) { + + // If semaphore is non-numeric, throw error + if ( isNaN( test.semaphore ) ) { + test.semaphore = 0; + + QUnit.pushFailure( + "Invalid value on test.semaphore", + sourceFromStacktrace( 2 ) + ); + return; + } + + // Don't start until equal number of stop-calls + if ( test.semaphore > 0 ) { + return; + } + + // Throw an Error if start is called more often than stop + if ( test.semaphore < 0 ) { + test.semaphore = 0; + + QUnit.pushFailure( + "Tried to restart test while already started (test's semaphore was 0 already)", + sourceFromStacktrace( 2 ) + ); + return; + } + + // Add a slight delay to allow more assertions etc. + if ( defined.setTimeout ) { + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + config.timeout = setTimeout( function() { + if ( test.semaphore > 0 ) { + return; + } + + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + + begin(); + }, 13 ); + } else { + begin(); + } +} + +function numberOfTests( module ) { + var count = module.tests.length; + while ( module = module.childModule ) { + count += module.tests.length; + } + return count; +} + +function notifyTestsRan( module ) { + module.testsRun++; + while ( module = module.parentModule ) { + module.testsRun++; + } +} + +function Assert( testContext ) { + this.test = testContext; +} + +// Assert helpers +QUnit.assert = Assert.prototype = { + + // Specify the number of expected assertions to guarantee that failed test + // (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if ( arguments.length === 1 ) { + this.test.expected = asserts; + } else { + return this.test.expected; + } + }, + + // Put a hold on processing and return a function that will release it a maximum of once. + async: function( count ) { + var resume, + test = this.test, + popped = false, + acceptCallCount = count; + + if ( typeof acceptCallCount === "undefined" ) { + acceptCallCount = 1; + } + + test.usedAsync = true; + resume = internalStop( test ); + + return function done() { + + if ( popped ) { + test.pushFailure( "Too many calls to the `assert.async` callback", + sourceFromStacktrace( 2 ) ); + return; + } + acceptCallCount -= 1; + if ( acceptCallCount > 0 ) { + return; + } + + popped = true; + resume(); + }; + }, + + // Exports test.push() to the user API + // Alias of pushResult. + push: function( result, actual, expected, message, negative ) { + var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert; + return currentAssert.pushResult( { + result: result, + actual: actual, + expected: expected, + message: message, + negative: negative + } ); + }, + + pushResult: function( resultInfo ) { + + // Destructure of resultInfo = { result, actual, expected, message, negative } + var assert = this, + currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; + + // Backwards compatibility fix. + // Allows the direct use of global exported assertions and QUnit.assert.* + // Although, it's use is not recommended as it can leak assertions + // to other tests from async tests, because we only get a reference to the current test, + // not exactly the test where assertion were intended to be called. + if ( !currentTest ) { + throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); + } + + if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { + currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", + sourceFromStacktrace( 2 ) ); + + // Allow this assertion to continue running anyway... + } + + if ( !( assert instanceof Assert ) ) { + assert = currentTest.assert; + } + + return assert.test.pushResult( resultInfo ); + }, + + ok: function( result, message ) { + message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + + QUnit.dump.parse( result ) ); + this.pushResult( { + result: !!result, + actual: result, + expected: true, + message: message + } ); + }, + + notOk: function( result, message ) { + message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + + QUnit.dump.parse( result ) ); + this.pushResult( { + result: !result, + actual: result, + expected: false, + message: message + } ); + }, + + equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + this.pushResult( { + result: expected == actual, + actual: actual, + expected: expected, + message: message + } ); + }, + + notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + this.pushResult( { + result: expected != actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); + }, + + propEqual: function( actual, expected, message ) { + actual = objectValues( actual ); + expected = objectValues( expected ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); + }, + + notPropEqual: function( actual, expected, message ) { + actual = objectValues( actual ); + expected = objectValues( expected ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); + }, + + deepEqual: function( actual, expected, message ) { + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); + }, + + notDeepEqual: function( actual, expected, message ) { + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); + }, + + strictEqual: function( actual, expected, message ) { + this.pushResult( { + result: expected === actual, + actual: actual, + expected: expected, + message: message + } ); + }, + + notStrictEqual: function( actual, expected, message ) { + this.pushResult( { + result: expected !== actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); + }, + + "throws": function( block, expected, message ) { + var actual, expectedType, + expectedOutput = expected, + ok = false, + currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; + + // 'expected' is optional unless doing string comparison + if ( QUnit.objectType( expected ) === "string" ) { + if ( message == null ) { + message = expected; + expected = null; + } else { + throw new Error( + "throws/raises does not accept a string value for the expected argument.\n" + + "Use a non-string object value (e.g. regExp) instead if it's necessary." + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); + } + } + + currentTest.ignoreGlobalErrors = true; + try { + block.call( currentTest.testEnvironment ); + } catch ( e ) { + actual = e; + } + currentTest.ignoreGlobalErrors = false; + + if ( actual ) { + expectedType = QUnit.objectType( expected ); + + // We don't want to validate thrown error + if ( !expected ) { + ok = true; + expectedOutput = null; + + // Expected is a regexp + } else if ( expectedType === "regexp" ) { + ok = expected.test( errorString( actual ) ); + + // Expected is a constructor, maybe an Error constructor + } else if ( expectedType === "function" && actual instanceof expected ) { + ok = true; + + // Expected is an Error object + } else if ( expectedType === "object" ) { + ok = actual instanceof expected.constructor && + actual.name === expected.name && + actual.message === expected.message; + + // Expected is a validation function which returns true if validation passed + } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { + expectedOutput = null; + ok = true; + } + } + + currentTest.assert.pushResult( { + result: ok, + actual: actual, + expected: expectedOutput, + message: message + } ); + } +}; + +// Provide an alternative to assert.throws(), for environments that consider throws a reserved word +// Known to us are: Closure Compiler, Narwhal +( function() { + /*jshint sub:true */ + Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation +}() ); + +function errorString( error ) { + var name, message, + resultErrorString = error.toString(); + if ( resultErrorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return resultErrorString; + } +} + +// Test for equality any JavaScript type. +// Author: Philippe Rathé +QUnit.equiv = ( function() { + + // Stack to decide between skip/abort functions + var callers = []; + + // Stack to avoiding loops from circular referencing + var parents = []; + var parentsB = []; + + var getProto = Object.getPrototypeOf || function( obj ) { + + /*jshint proto: true */ + return obj.__proto__; + }; + + function useStrictEquality( b, a ) { + + // To catch short annotation VS 'new' annotation of a declaration. e.g.: + // `var i = 1;` + // `var j = new Number(1);` + if ( typeof a === "object" ) { + a = a.valueOf(); + } + if ( typeof b === "object" ) { + b = b.valueOf(); + } + + return a === b; + } + + function compareConstructors( a, b ) { + var protoA = getProto( a ); + var protoB = getProto( b ); + + // Comparing constructors is more strict than using `instanceof` + if ( a.constructor === b.constructor ) { + return true; + } + + // Ref #851 + // If the obj prototype descends from a null constructor, treat it + // as a null prototype. + if ( protoA && protoA.constructor === null ) { + protoA = null; + } + if ( protoB && protoB.constructor === null ) { + protoB = null; + } + + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( ( protoA === null && protoB === Object.prototype ) || + ( protoB === null && protoA === Object.prototype ) ) { + return true; + } + + return false; + } + + function getRegExpFlags( regexp ) { + return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; + } + + var callbacks = { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + "symbol": useStrictEquality, + "date": useStrictEquality, + + "nan": function() { + return true; + }, + + "regexp": function( b, a ) { + return a.source === b.source && + + // Include flags in the comparison + getRegExpFlags( a ) === getRegExpFlags( b ); + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[ callers.length - 1 ]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop, aCircular, bCircular; + + len = a.length; + if ( len !== b.length ) { + + // Safe and faster + return false; + } + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { + parents.pop(); + parentsB.pop(); + return false; + } + } + } + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + parents.pop(); + parentsB.pop(); + return false; + } + } + parents.pop(); + parentsB.pop(); + return true; + }, + + "set": function( b, a ) { + var innerEq, + outerEq = true; + + if ( a.size !== b.size ) { + return false; + } + + a.forEach( function( aVal ) { + innerEq = false; + + b.forEach( function( bVal ) { + if ( innerEquiv( bVal, aVal ) ) { + innerEq = true; + } + } ); + + if ( !innerEq ) { + outerEq = false; + } + } ); + + return outerEq; + }, + + "map": function( b, a ) { + var innerEq, + outerEq = true; + + if ( a.size !== b.size ) { + return false; + } + + a.forEach( function( aVal, aKey ) { + innerEq = false; + + b.forEach( function( bVal, bKey ) { + if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) { + innerEq = true; + } + } ); + + if ( !innerEq ) { + outerEq = false; + } + } ); + + return outerEq; + }, + + "object": function( b, a ) { + var i, j, loop, aCircular, bCircular; + + // Default to true + var eq = true; + var aProperties = []; + var bProperties = []; + + if ( compareConstructors( a, b ) === false ) { + return false; + } + + // Stack constructor before traversing properties + callers.push( a.constructor ); + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + + // Be strict: don't ensure hasOwnProperty and go deep + for ( i in a ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { + eq = false; + break; + } + } + } + aProperties.push( i ); + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + eq = false; + break; + } + } + + parents.pop(); + parentsB.pop(); + + // Unstack, we are done + callers.pop(); + + for ( i in b ) { + + // Collect b's properties + bProperties.push( i ); + } + + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } + }; + + function typeEquiv( a, b ) { + var type = QUnit.objectType( a ); + return QUnit.objectType( b ) === type && callbacks[ type ]( b, a ); + } + + // The real equiv function + function innerEquiv( a, b ) { + + // We're done when there's nothing more to compare + if ( arguments.length < 2 ) { + return true; + } + + // Require type-specific equality + return ( a === b || typeEquiv( a, b ) ) && + + // ...across all consecutive argument pairs + ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) ); + } + + return innerEquiv; +}() ); + +// Based on jsDump by Ariel Flesler +// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html +QUnit.dump = ( function() { + function quote( str ) { + return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; + } + function literal( o ) { + return o + ""; + } + function join( pre, arr, post ) { + var s = dump.separator(), + base = dump.indent(), + inner = dump.indent( 1 ); + if ( arr.join ) { + arr = arr.join( "," + s + inner ); + } + if ( !arr ) { + return pre + post; + } + return [ pre, inner + arr, base + post ].join( s ); + } + function array( arr, stack ) { + var i = arr.length, + ret = new Array( i ); + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Array]"; + } + + this.up(); + while ( i-- ) { + ret[ i ] = this.parse( arr[ i ], undefined, stack ); + } + this.down(); + return join( "[", ret, "]" ); + } + + function isArray( obj ) { + return ( + + //Native Arrays + toString.call( obj ) === "[object Array]" || + + // NodeList objects + ( typeof obj.length === "number" && obj.item !== undefined ) && + ( obj.length ? + obj.item( 0 ) === obj[ 0 ] : + ( obj.item( 0 ) === null && obj[ 0 ] === undefined ) + ) + ); + } + + var reName = /^function (\w+)/, + dump = { + + // The objType is used mostly internally, you can fix a (custom) type in advance + parse: function( obj, objType, stack ) { + stack = stack || []; + var res, parser, parserType, + inStack = inArray( obj, stack ); + + if ( inStack !== -1 ) { + return "recursion(" + ( inStack - stack.length ) + ")"; + } + + objType = objType || this.typeOf( obj ); + parser = this.parsers[ objType ]; + parserType = typeof parser; + + if ( parserType === "function" ) { + stack.push( obj ); + res = parser.call( this, obj, stack ); + stack.pop(); + return res; + } + return ( parserType === "string" ) ? parser : this.parsers.error; + }, + typeOf: function( obj ) { + var type; + + if ( obj === null ) { + type = "null"; + } else if ( typeof obj === "undefined" ) { + type = "undefined"; + } else if ( QUnit.is( "regexp", obj ) ) { + type = "regexp"; + } else if ( QUnit.is( "date", obj ) ) { + type = "date"; + } else if ( QUnit.is( "function", obj ) ) { + type = "function"; + } else if ( obj.setInterval !== undefined && + obj.document !== undefined && + obj.nodeType === undefined ) { + type = "window"; + } else if ( obj.nodeType === 9 ) { + type = "document"; + } else if ( obj.nodeType ) { + type = "node"; + } else if ( isArray( obj ) ) { + type = "array"; + } else if ( obj.constructor === Error.prototype.constructor ) { + type = "error"; + } else { + type = typeof obj; + } + return type; + }, + + separator: function() { + return this.multiline ? this.HTML ? "
" : "\n" : this.HTML ? " " : " "; + }, + + // Extra can be a number, shortcut for increasing-calling-decreasing + indent: function( extra ) { + if ( !this.multiline ) { + return ""; + } + var chr = this.indentChar; + if ( this.HTML ) { + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + } + return new Array( this.depth + ( extra || 0 ) ).join( chr ); + }, + up: function( a ) { + this.depth += a || 1; + }, + down: function( a ) { + this.depth -= a || 1; + }, + setParser: function( name, parser ) { + this.parsers[ name ] = parser; + }, + + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + depth: 1, + maxDepth: QUnit.config.maxDepth, + + // This is the list of parsers, to modify them, use dump.setParser + parsers: { + window: "[Window]", + document: "[Document]", + error: function( error ) { + return "Error(\"" + error.message + "\")"; + }, + unknown: "[Unknown]", + "null": "null", + "undefined": "undefined", + "function": function( fn ) { + var ret = "function", + + // Functions never have name in IE + name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; + + if ( name ) { + ret += " " + name; + } + ret += "("; + + ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, dump.parse( fn, "functionCode" ), "}" ); + }, + array: array, + nodelist: array, + "arguments": array, + object: function( map, stack ) { + var keys, key, val, i, nonEnumerableProperties, + ret = []; + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Object]"; + } + + dump.up(); + keys = []; + for ( key in map ) { + keys.push( key ); + } + + // Some properties are not always enumerable on Error objects. + nonEnumerableProperties = [ "message", "name" ]; + for ( i in nonEnumerableProperties ) { + key = nonEnumerableProperties[ i ]; + if ( key in map && inArray( key, keys ) < 0 ) { + keys.push( key ); + } + } + keys.sort(); + for ( i = 0; i < keys.length; i++ ) { + key = keys[ i ]; + val = map[ key ]; + ret.push( dump.parse( key, "key" ) + ": " + + dump.parse( val, undefined, stack ) ); + } + dump.down(); + return join( "{", ret, "}" ); + }, + node: function( node ) { + var len, i, val, + open = dump.HTML ? "<" : "<", + close = dump.HTML ? ">" : ">", + tag = node.nodeName.toLowerCase(), + ret = open + tag, + attrs = node.attributes; + + if ( attrs ) { + for ( i = 0, len = attrs.length; i < len; i++ ) { + val = attrs[ i ].nodeValue; + + // IE6 includes all attributes in .attributes, even ones not explicitly + // set. Those have values like undefined, null, 0, false, "" or + // "inherit". + if ( val && val !== "inherit" ) { + ret += " " + attrs[ i ].nodeName + "=" + + dump.parse( val, "attribute" ); + } + } + } + ret += close; + + // Show content of TextNode or CDATASection + if ( node.nodeType === 3 || node.nodeType === 4 ) { + ret += node.nodeValue; + } + + return ret + open + "/" + tag + close; + }, + + // Function calls it internally, it's the arguments part of the function + functionArgs: function( fn ) { + var args, + l = fn.length; + + if ( !l ) { + return ""; + } + + args = new Array( l ); + while ( l-- ) { + + // 97 is 'a' + args[ l ] = String.fromCharCode( 97 + l ); + } + return " " + args.join( ", " ) + " "; + }, + + // Object calls it internally, the key part of an item in a map + key: quote, + + // Function calls it internally, it's the content of the function + functionCode: "[code]", + + // Node calls it internally, it's a html attribute value + attribute: quote, + string: quote, + date: quote, + regexp: literal, + number: literal, + "boolean": literal, + symbol: function( sym ) { + return sym.toString(); + } + }, + + // If true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + + // Indentation unit + indentChar: " ", + + // If true, items in a collection, are separated by a \n, else just a space. + multiline: true + }; + + return dump; +}() ); + +// Back compat +QUnit.jsDump = QUnit.dump; + +function applyDeprecated( name ) { + return function() { + throw new Error( + name + " is removed in QUnit 2.0.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); + }; +} + +Object.keys( Assert.prototype ).forEach( function( key ) { + QUnit[ key ] = applyDeprecated( "`QUnit." + key + "`" ); +} ); + +QUnit.asyncTest = function() { + throw new Error( + "asyncTest is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); +}; + +QUnit.stop = function() { + throw new Error( + "QUnit.stop is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); +}; + +function resetThrower() { + throw new Error( + "QUnit.reset is removed in QUnit 2.0 without replacement.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); +} + +Object.defineProperty( QUnit, "reset", { + get: function() { + return resetThrower; + }, + set: resetThrower +} ); + +if ( defined.document ) { + if ( window.QUnit ) { + throw new Error( "QUnit has already been defined." ); + } + + [ + "test", + "module", + "expect", + "start", + "ok", + "notOk", + "equal", + "notEqual", + "propEqual", + "notPropEqual", + "deepEqual", + "notDeepEqual", + "strictEqual", + "notStrictEqual", + "throws", + "raises" + ].forEach( function( key ) { + window[ key ] = applyDeprecated( "The global `" + key + "`" ); + } ); + + window.QUnit = QUnit; +} + +// For nodejs +if ( typeof module !== "undefined" && module && module.exports ) { + module.exports = QUnit; + + // For consistency with CommonJS environments' exports + module.exports.QUnit = QUnit; +} + +// For CommonJS with exports, but without module.exports, like Rhino +if ( typeof exports !== "undefined" && exports ) { + exports.QUnit = QUnit; +} + +if ( typeof define === "function" && define.amd ) { + define( function() { + return QUnit; + } ); + QUnit.config.autostart = false; +} + +// Get a reference to the global object, like window in browsers +}( ( function() { + return this; +}() ) ) ); + +( function() { + +if ( typeof window === "undefined" || !window.document ) { + return; +} + +var config = QUnit.config, + hasOwn = Object.prototype.hasOwnProperty; + +// Stores fixture HTML for resetting later +function storeFixture() { + + // Avoid overwriting user-defined values + if ( hasOwn.call( config, "fixture" ) ) { + return; + } + + var fixture = document.getElementById( "qunit-fixture" ); + if ( fixture ) { + config.fixture = fixture.innerHTML; + } +} + +QUnit.begin( storeFixture ); + +// Resets the fixture DOM element if available. +function resetFixture() { + if ( config.fixture == null ) { + return; + } + + var fixture = document.getElementById( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; + } +} + +QUnit.testStart( resetFixture ); + +}() ); + +( function() { + +// Only interact with URLs via window.location +var location = typeof window !== "undefined" && window.location; +if ( !location ) { + return; +} + +var urlParams = getUrlParams(); + +QUnit.urlParams = urlParams; + +// Match module/test by inclusion in an array +QUnit.config.moduleId = [].concat( urlParams.moduleId || [] ); +QUnit.config.testId = [].concat( urlParams.testId || [] ); + +// Exact case-insensitive match of the module name +QUnit.config.module = urlParams.module; + +// Regular expression or case-insenstive substring match against "moduleName: testName" +QUnit.config.filter = urlParams.filter; + +// Test order randomization +if ( urlParams.seed === true ) { + + // Generate a random seed if the option is specified without a value + QUnit.config.seed = Math.random().toString( 36 ).slice( 2 ); +} else if ( urlParams.seed ) { + QUnit.config.seed = urlParams.seed; +} + +// Add URL-parameter-mapped config values with UI form rendering data +QUnit.config.urlConfig.push( + { + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the " + + "global object (`window` in Browsers). Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + + "exceptions in IE reasonable. Stored as query-strings." + } +); + +QUnit.begin( function() { + var i, option, + urlConfig = QUnit.config.urlConfig; + + for ( i = 0; i < urlConfig.length; i++ ) { + + // Options can be either strings or objects with nonempty "id" properties + option = QUnit.config.urlConfig[ i ]; + if ( typeof option !== "string" ) { + option = option.id; + } + + if ( QUnit.config[ option ] === undefined ) { + QUnit.config[ option ] = urlParams[ option ]; + } + } +} ); + +function getUrlParams() { + var i, param, name, value; + var urlParams = {}; + var params = location.search.slice( 1 ).split( "&" ); + var length = params.length; + + for ( i = 0; i < length; i++ ) { + if ( params[ i ] ) { + param = params[ i ].split( "=" ); + name = decodeQueryParam( param[ 0 ] ); + + // Allow just a key to turn on a flag, e.g., test.html?noglobals + value = param.length === 1 || + decodeQueryParam( param.slice( 1 ).join( "=" ) ) ; + if ( urlParams[ name ] ) { + urlParams[ name ] = [].concat( urlParams[ name ], value ); + } else { + urlParams[ name ] = value; + } + } + } + + return urlParams; +} + +function decodeQueryParam( param ) { + return decodeURIComponent( param.replace( /\+/g, "%20" ) ); +} + +// Don't load the HTML Reporter on non-browser environments +if ( typeof window === "undefined" || !window.document ) { + return; +} + +QUnit.init = function() { + throw new Error( + "QUnit.init is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); +}; + +var config = QUnit.config, + document = window.document, + collapseNext = false, + hasOwn = Object.prototype.hasOwnProperty, + unfilteredUrl = setUrl( { filter: undefined, module: undefined, + moduleId: undefined, testId: undefined } ), + defined = { + sessionStorage: ( function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }() ) + }, + modulesList = []; + +// Escape text for attribute or text content. +function escapeText( s ) { + if ( !s ) { + return ""; + } + s = s + ""; + + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch ( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + } ); +} + +function addEvent( elem, type, fn ) { + elem.addEventListener( type, fn, false ); +} + +function removeEvent( elem, type, fn ) { + elem.removeEventListener( type, fn, false ); +} + +function addEvents( elems, type, fn ) { + var i = elems.length; + while ( i-- ) { + addEvent( elems[ i ], type, fn ); + } +} + +function hasClass( elem, name ) { + return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; +} + +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += ( elem.className ? " " : "" ) + name; + } +} + +function toggleClass( elem, name, force ) { + if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) { + addClass( elem, name ); + } else { + removeClass( elem, name ); + } +} + +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + + // Class name may appear multiple times + while ( set.indexOf( " " + name + " " ) >= 0 ) { + set = set.replace( " " + name + " ", " " ); + } + + // Trim for prettiness + elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); +} + +function id( name ) { + return document.getElementById && document.getElementById( name ); +} + +function interceptNavigation( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); + } + + return false; +} + +function getUrlConfigHtml() { + var i, j, val, + escaped, escapedTooltip, + selection = false, + urlConfig = config.urlConfig, + urlConfigHtml = ""; + + for ( i = 0; i < urlConfig.length; i++ ) { + + // Options can be either strings or objects with nonempty "id" properties + val = config.urlConfig[ i ]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; + } + + escaped = escapeText( val.id ); + escapedTooltip = escapeText( val.tooltip ); + + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + + return urlConfigHtml; +} + +// Handle "click" events on toolbar checkboxes and "change" for select menus. +// Updates the URL with the new state of `config.urlConfig` values. +function toolbarChanged() { + var updatedUrl, value, tests, + field = this, + params = {}; + + // Detect if field is a select menu or a checkbox + if ( "selectedIndex" in field ) { + value = field.options[ field.selectedIndex ].value || undefined; + } else { + value = field.checked ? ( field.defaultValue || true ) : undefined; + } + + params[ field.name ] = value; + updatedUrl = setUrl( params ); + + // Check if we can apply the change without a page refresh + if ( "hidepassed" === field.name && "replaceState" in window.history ) { + QUnit.urlParams[ field.name ] = value; + config[ field.name ] = value || false; + tests = id( "qunit-tests" ); + if ( tests ) { + toggleClass( tests, "hidepass", value || false ); + } + window.history.replaceState( null, "", updatedUrl ); + } else { + window.location = updatedUrl; + } +} + +function setUrl( params ) { + var key, arrValue, i, + querystring = "?", + location = window.location; + + params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); + + for ( key in params ) { + + // Skip inherited or undefined properties + if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) { + + // Output a parameter for each value of this key (but usually just one) + arrValue = [].concat( params[ key ] ); + for ( i = 0; i < arrValue.length; i++ ) { + querystring += encodeURIComponent( key ); + if ( arrValue[ i ] !== true ) { + querystring += "=" + encodeURIComponent( arrValue[ i ] ); + } + querystring += "&"; + } + } + } + return location.protocol + "//" + location.host + + location.pathname + querystring.slice( 0, -1 ); +} + +function applyUrlParams() { + var i, + selectedModules = [], + modulesList = id( "qunit-modulefilter-dropdown-list" ).getElementsByTagName( "input" ), + filter = id( "qunit-filter-input" ).value; + + for ( i = 0; i < modulesList.length; i++ ) { + if ( modulesList[ i ].checked ) { + selectedModules.push( modulesList[ i ].value ); + } + } + + window.location = setUrl( { + filter: ( filter === "" ) ? undefined : filter, + moduleId: ( selectedModules.length === 0 ) ? undefined : selectedModules, + + // Remove module and testId filter + module: undefined, + testId: undefined + } ); +} + +function toolbarUrlConfigContainer() { + var urlConfigContainer = document.createElement( "span" ); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass( urlConfigContainer, "qunit-url-config" ); + + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "change", toolbarChanged ); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); + + return urlConfigContainer; +} + +function toolbarLooseFilter() { + var filter = document.createElement( "form" ), + label = document.createElement( "label" ), + input = document.createElement( "input" ), + button = document.createElement( "button" ); + + addClass( filter, "qunit-filter" ); + + label.innerHTML = "Filter: "; + + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; + + button.innerHTML = "Go"; + + label.appendChild( input ); + + filter.appendChild( label ); + filter.appendChild( document.createTextNode( " " ) ); + filter.appendChild( button ); + addEvent( filter, "submit", interceptNavigation ); + + return filter; +} + +function moduleListHtml () { + var i, checked, + html = ""; + + for ( i = 0; i < config.modules.length; i++ ) { + if ( config.modules[ i ].name !== "" ) { + checked = config.moduleId.indexOf( config.modules[ i ].moduleId ) > -1; + html += "
  • "; + } + } + + return html; +} + +function toolbarModuleFilter () { + var allCheckbox, commit, reset, + moduleFilter = document.createElement( "form" ), + label = document.createElement( "label" ), + moduleSearch = document.createElement( "input" ), + dropDown = document.createElement( "div" ), + actions = document.createElement( "span" ), + dropDownList = document.createElement( "ul" ), + dirty = false; + + moduleSearch.id = "qunit-modulefilter-search"; + addEvent( moduleSearch, "input", searchInput ); + addEvent( moduleSearch, "input", searchFocus ); + addEvent( moduleSearch, "focus", searchFocus ); + addEvent( moduleSearch, "click", searchFocus ); + + label.id = "qunit-modulefilter-search-container"; + label.innerHTML = "Module: "; + label.appendChild( moduleSearch ); + + actions.id = "qunit-modulefilter-actions"; + actions.innerHTML = + "" + + "" + + ""; + allCheckbox = actions.lastChild.firstChild; + commit = actions.firstChild; + reset = commit.nextSibling; + addEvent( commit, "click", applyUrlParams ); + + dropDownList.id = "qunit-modulefilter-dropdown-list"; + dropDownList.innerHTML = moduleListHtml(); + + dropDown.id = "qunit-modulefilter-dropdown"; + dropDown.style.display = "none"; + dropDown.appendChild( actions ); + dropDown.appendChild( dropDownList ); + addEvent( dropDown, "change", selectionChange ); + selectionChange(); + + moduleFilter.id = "qunit-modulefilter"; + moduleFilter.appendChild( label ); + moduleFilter.appendChild( dropDown ) ; + addEvent( moduleFilter, "submit", interceptNavigation ); + addEvent( moduleFilter, "reset", function() { + + // Let the reset happen, then update styles + window.setTimeout( selectionChange ); + } ); + + // Enables show/hide for the dropdown + function searchFocus() { + if ( dropDown.style.display !== "none" ) { + return; + } + + dropDown.style.display = "block"; + addEvent( document, "click", hideHandler ); + addEvent( document, "keydown", hideHandler ); + + // Hide on Escape keydown or outside-container click + function hideHandler( e ) { + var inContainer = moduleFilter.contains( e.target ); + + if ( e.keyCode === 27 || !inContainer ) { + if ( e.keyCode === 27 && inContainer ) { + moduleSearch.focus(); + } + dropDown.style.display = "none"; + removeEvent( document, "click", hideHandler ); + removeEvent( document, "keydown", hideHandler ); + moduleSearch.value = ""; + searchInput(); + } + } + } + + // Processes module search box input + function searchInput() { + var i, item, + searchText = moduleSearch.value.toLowerCase(), + listItems = dropDownList.children; + + for ( i = 0; i < listItems.length; i++ ) { + item = listItems[ i ]; + if ( !searchText || item.textContent.toLowerCase().indexOf( searchText ) > -1 ) { + item.style.display = ""; + } else { + item.style.display = "none"; + } + } + } + + // Processes selection changes + function selectionChange( evt ) { + var i, item, + checkbox = evt && evt.target || allCheckbox, + modulesList = dropDownList.getElementsByTagName( "input" ), + selectedNames = []; + + toggleClass( checkbox.parentNode, "checked", checkbox.checked ); + + dirty = false; + if ( checkbox.checked && checkbox !== allCheckbox ) { + allCheckbox.checked = false; + removeClass( allCheckbox.parentNode, "checked" ); + } + for ( i = 0; i < modulesList.length; i++ ) { + item = modulesList[ i ]; + if ( !evt ) { + toggleClass( item.parentNode, "checked", item.checked ); + } else if ( checkbox === allCheckbox && checkbox.checked ) { + item.checked = false; + removeClass( item.parentNode, "checked" ); + } + dirty = dirty || ( item.checked !== item.defaultChecked ); + if ( item.checked ) { + selectedNames.push( item.parentNode.textContent ); + } + } + + commit.style.display = reset.style.display = dirty ? "" : "none"; + moduleSearch.placeholder = selectedNames.join( ", " ) || allCheckbox.parentNode.textContent; + moduleSearch.title = "Type to filter list. Current selection:\n" + + ( selectedNames.join( "\n" ) || allCheckbox.parentNode.textContent ); + } + + return moduleFilter; +} + +function appendToolbar() { + var toolbar = id( "qunit-testrunner-toolbar" ); + + if ( toolbar ) { + toolbar.appendChild( toolbarUrlConfigContainer() ); + toolbar.appendChild( toolbarModuleFilter() ); + toolbar.appendChild( toolbarLooseFilter() ); + toolbar.appendChild( document.createElement( "div" ) ).className = "clearfix"; + } +} + +function appendHeader() { + var header = id( "qunit-header" ); + + if ( header ) { + header.innerHTML = "" + header.innerHTML + + " "; + } +} + +function appendBanner() { + var banner = id( "qunit-banner" ); + + if ( banner ) { + banner.className = ""; + } +} + +function appendTestResults() { + var tests = id( "qunit-tests" ), + result = id( "qunit-testresult" ); + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + tests.innerHTML = ""; + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
     "; + } +} + +function appendFilteredTest() { + var testId = QUnit.config.testId; + if ( !testId || testId.length <= 0 ) { + return ""; + } + return "
    Rerunning selected tests: " + + escapeText( testId.join( ", " ) ) + + " Run all tests
    "; +} + +function appendUserAgent() { + var userAgent = id( "qunit-userAgent" ); + + if ( userAgent ) { + userAgent.innerHTML = ""; + userAgent.appendChild( + document.createTextNode( + "QUnit " + QUnit.version + "; " + navigator.userAgent + ) + ); + } +} + +function appendInterface() { + var qunit = id( "qunit" ); + + if ( qunit ) { + qunit.innerHTML = + "

    " + escapeText( document.title ) + "

    " + + "

    " + + "
    " + + appendFilteredTest() + + "

    " + + "
      "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); +} + +function appendTestsList( modules ) { + var i, l, x, z, test, moduleObj; + + for ( i = 0, l = modules.length; i < l; i++ ) { + moduleObj = modules[ i ]; + + for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { + test = moduleObj.tests[ x ]; + + appendTest( test.name, test.testId, moduleObj.name ); + } + } +} + +function appendTest( name, testId, moduleName ) { + var title, rerunTrigger, testBlock, assertList, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + title = document.createElement( "strong" ); + title.innerHTML = getNameHtml( name, moduleName ); + + rerunTrigger = document.createElement( "a" ); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl( { testId: testId } ); + + testBlock = document.createElement( "li" ); + testBlock.appendChild( title ); + testBlock.appendChild( rerunTrigger ); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild( assertList ); + + tests.appendChild( testBlock ); +} + +// HTML Reporter initialization and load +QUnit.begin( function( details ) { + var i, moduleObj, tests; + + // Sort modules by name for the picker + for ( i = 0; i < details.modules.length; i++ ) { + moduleObj = details.modules[ i ]; + if ( moduleObj.name ) { + modulesList.push( moduleObj.name ); + } + } + modulesList.sort( function( a, b ) { + return a.localeCompare( b ); + } ); + + // Initialize QUnit elements + appendInterface(); + appendTestsList( details.modules ); + tests = id( "qunit-tests" ); + if ( tests && config.hidepassed ) { + addClass( tests, "hidepass" ); + } +} ); + +QUnit.done( function( details ) { + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + html = [ + "Tests completed in ", + details.runtime, + " milliseconds.
      ", + "", + details.passed, + " assertions of ", + details.total, + " passed, ", + details.failed, + " failed." + ].join( "" ); + + if ( banner ) { + banner.className = details.failed ? "qunit-fail" : "qunit-pass"; + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && document.title ) { + + // Show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + ( details.failed ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // Clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // Scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo( 0, 0 ); + } +} ); + +function getNameHtml( name, module ) { + var nameHtml = ""; + + if ( module ) { + nameHtml = "" + escapeText( module ) + ": "; + } + + nameHtml += "" + escapeText( name ) + ""; + + return nameHtml; +} + +QUnit.testStart( function( details ) { + var running, testBlock, bad; + + testBlock = id( "qunit-test-output-" + details.testId ); + if ( testBlock ) { + testBlock.className = "running"; + } else { + + // Report later registered tests + appendTest( details.name, details.testId, details.module ); + } + + running = id( "qunit-testresult" ); + if ( running ) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); + + running.innerHTML = ( bad ? + "Rerunning previously failed test:
      " : + "Running:
      " ) + + getNameHtml( details.name, details.module ); + } + +} ); + +function stripHtml( string ) { + + // Strip tags, html entity and whitespaces + return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\"/g, "" ).replace( /\s+/g, "" ); +} + +QUnit.log( function( details ) { + var assertList, assertLi, + message, expected, actual, diff, + showDiff = false, + testItem = id( "qunit-test-output-" + details.testId ); + + if ( !testItem ) { + return; + } + + message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); + message = "" + message + ""; + message += "@ " + details.runtime + " ms"; + + // The pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if ( !details.result && hasOwn.call( details, "expected" ) ) { + if ( details.negative ) { + expected = "NOT " + QUnit.dump.parse( details.expected ); + } else { + expected = QUnit.dump.parse( details.expected ); + } + + actual = QUnit.dump.parse( details.actual ); + message += ""; + + if ( actual !== expected ) { + + message += ""; + + // Don't show diff if actual or expected are booleans + if ( !( /^(true|false)$/.test( actual ) ) && + !( /^(true|false)$/.test( expected ) ) ) { + diff = QUnit.diff( expected, actual ); + showDiff = stripHtml( diff ).length !== + stripHtml( expected ).length + + stripHtml( actual ).length; + } + + // Don't show diff if expected and actual are totally different + if ( showDiff ) { + message += ""; + } + } else if ( expected.indexOf( "[object Array]" ) !== -1 || + expected.indexOf( "[object Object]" ) !== -1 ) { + message += ""; + } else { + message += ""; + } + + if ( details.source ) { + message += ""; + } + + message += "
      Expected:
      " +
      +			escapeText( expected ) +
      +			"
      Result:
      " +
      +				escapeText( actual ) + "
      Diff:
      " +
      +					diff + "
      Message: " + + "Diff suppressed as the depth of object is more than current max depth (" + + QUnit.config.maxDepth + ").

      Hint: Use QUnit.dump.maxDepth to " + + " run with a higher max depth or " + + "Rerun without max depth.

      Message: " + + "Diff suppressed as the expected and actual results have an equivalent" + + " serialization
      Source:
      " +
      +				escapeText( details.source ) + "
      "; + + // This occurs when pushFailure is set and we have an extracted stack trace + } else if ( !details.result && details.source ) { + message += "" + + "" + + "
      Source:
      " +
      +			escapeText( details.source ) + "
      "; + } + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + assertLi = document.createElement( "li" ); + assertLi.className = details.result ? "pass" : "fail"; + assertLi.innerHTML = message; + assertList.appendChild( assertLi ); +} ); + +QUnit.testDone( function( details ) { + var testTitle, time, testItem, assertList, + good, bad, testCounts, skipped, sourceName, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + testItem = id( "qunit-test-output-" + details.testId ); + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + good = details.passed; + bad = details.failed; + + // Store result when possible + if ( config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); + } + } + + if ( bad === 0 ) { + + // Collapse the passing tests + addClass( assertList, "qunit-collapsed" ); + } else if ( bad && config.collapse && !collapseNext ) { + + // Skip collapsing the first failing test + collapseNext = true; + } else { + + // Collapse remaining tests + addClass( assertList, "qunit-collapsed" ); + } + + // The testItem.firstChild is the test name + testTitle = testItem.firstChild; + + testCounts = bad ? + "" + bad + ", " + "" + good + ", " : + ""; + + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ")"; + + if ( details.skipped ) { + testItem.className = "skipped"; + skipped = document.createElement( "em" ); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore( skipped, testTitle ); + } else { + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + } ); + + testItem.className = bad ? "fail" : "pass"; + + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore( time, assertList ); + } + + // Show the source of the test when showing assertions + if ( details.source ) { + sourceName = document.createElement( "p" ); + sourceName.innerHTML = "Source: " + details.source; + addClass( sourceName, "qunit-source" ); + if ( bad === 0 ) { + addClass( sourceName, "qunit-collapsed" ); + } + addEvent( testTitle, "click", function() { + toggleClass( sourceName, "qunit-collapsed" ); + } ); + testItem.appendChild( sourceName ); + } +} ); + +// Avoid readyState issue with phantomjs +// Ref: #818 +var notPhantom = ( function( p ) { + return !( p && p.version && p.version.major > 0 ); +} )( window.phantom ); + +if ( notPhantom && document.readyState === "complete" ) { + QUnit.load(); +} else { + addEvent( window, "load", QUnit.load ); +} + +/* + * This file is a modified version of google-diff-match-patch's JavaScript implementation + * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), + * modifications are licensed as more fully set forth in LICENSE.txt. + * + * The original source of google-diff-match-patch is attributable and licensed as follows: + * + * Copyright 2006 Google Inc. + * https://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More Info: + * https://code.google.com/p/google-diff-match-patch/ + * + * Usage: QUnit.diff(expected, actual) + * + */ +QUnit.diff = ( function() { + function DiffMatchPatch() { + } + + // DIFF FUNCTIONS + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1, + DIFF_INSERT = 1, + DIFF_EQUAL = 0; + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean=} optChecklines Optional speedup flag. If present and false, + * then don't run a line-level diff first to identify the changed areas. + * Defaults to true, which does a faster, slightly less optimal diff. + * @return {!Array.} Array of diff tuples. + */ + DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) { + var deadline, checklines, commonlength, + commonprefix, commonsuffix, diffs; + + // The diff must be complete in up to 1 second. + deadline = ( new Date() ).getTime() + 1000; + + // Check for null inputs. + if ( text1 === null || text2 === null ) { + throw new Error( "Null input. (DiffMain)" ); + } + + // Check for equality (speedup). + if ( text1 === text2 ) { + if ( text1 ) { + return [ + [ DIFF_EQUAL, text1 ] + ]; + } + return []; + } + + if ( typeof optChecklines === "undefined" ) { + optChecklines = true; + } + + checklines = optChecklines; + + // Trim off common prefix (speedup). + commonlength = this.diffCommonPrefix( text1, text2 ); + commonprefix = text1.substring( 0, commonlength ); + text1 = text1.substring( commonlength ); + text2 = text2.substring( commonlength ); + + // Trim off common suffix (speedup). + commonlength = this.diffCommonSuffix( text1, text2 ); + commonsuffix = text1.substring( text1.length - commonlength ); + text1 = text1.substring( 0, text1.length - commonlength ); + text2 = text2.substring( 0, text2.length - commonlength ); + + // Compute the diff on the middle block. + diffs = this.diffCompute( text1, text2, checklines, deadline ); + + // Restore the prefix and suffix. + if ( commonprefix ) { + diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); + } + if ( commonsuffix ) { + diffs.push( [ DIFF_EQUAL, commonsuffix ] ); + } + this.diffCleanupMerge( diffs ); + return diffs; + }; + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, preIns, preDel, postIns, postDel; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + + // Is there an insertion operation before the last equality. + preIns = false; + + // Is there a deletion operation before the last equality. + preDel = false; + + // Is there an insertion operation after the last equality. + postIns = false; + + // Is there a deletion operation after the last equality. + postDel = false; + while ( pointer < diffs.length ) { + + // Equality found. + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { + if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) { + + // Candidate found. + equalities[ equalitiesLength++ ] = pointer; + preIns = postIns; + preDel = postDel; + lastequality = diffs[ pointer ][ 1 ]; + } else { + + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = null; + } + postIns = postDel = false; + + // An insertion or deletion. + } else { + + if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { + postDel = true; + } else { + postIns = true; + } + + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || + ( ( lastequality.length < 2 ) && + ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { + + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); + + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = null; + if ( preIns && preDel ) { + + // No changes made which could affect previous entry, keep going. + postIns = postDel = true; + equalitiesLength = 0; + } else { + equalitiesLength--; // Throw away the previous equality. + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + postIns = postDel = false; + } + changes = true; + } + } + pointer++; + } + + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; + + /** + * Convert a diff array into a pretty HTML report. + * @param {!Array.} diffs Array of diff tuples. + * @param {integer} string to be beautified. + * @return {string} HTML representation. + */ + DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { + var op, data, x, + html = []; + for ( x = 0; x < diffs.length; x++ ) { + op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal) + data = diffs[ x ][ 1 ]; // Text of change. + switch ( op ) { + case DIFF_INSERT: + html[ x ] = "" + escapeText( data ) + ""; + break; + case DIFF_DELETE: + html[ x ] = "" + escapeText( data ) + ""; + break; + case DIFF_EQUAL: + html[ x ] = "" + escapeText( data ) + ""; + break; + } + } + return html.join( "" ); + }; + + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerstart; + + // Quick check for common null cases. + if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { + return 0; + } + + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerstart = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( pointerstart, pointermid ) === + text2.substring( pointerstart, pointermid ) ) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerend; + + // Quick check for common null cases. + if ( !text1 || + !text2 || + text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { + return 0; + } + + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerend = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) === + text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { + var diffs, longtext, shorttext, i, hm, + text1A, text2A, text1B, text2B, + midCommon, diffsA, diffsB; + + if ( !text1 ) { + + // Just add some text (speedup). + return [ + [ DIFF_INSERT, text2 ] + ]; + } + + if ( !text2 ) { + + // Just delete some text (speedup). + return [ + [ DIFF_DELETE, text1 ] + ]; + } + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + i = longtext.indexOf( shorttext ); + if ( i !== -1 ) { + + // Shorter text is inside the longer text (speedup). + diffs = [ + [ DIFF_INSERT, longtext.substring( 0, i ) ], + [ DIFF_EQUAL, shorttext ], + [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] + ]; + + // Swap insertions for deletions if diff is reversed. + if ( text1.length > text2.length ) { + diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; + } + return diffs; + } + + if ( shorttext.length === 1 ) { + + // Single character string. + // After the previous speedup, the character can't be an equality. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + } + + // Check to see if the problem can be split in two. + hm = this.diffHalfMatch( text1, text2 ); + if ( hm ) { + + // A half-match was found, sort out the return data. + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + midCommon = hm[ 4 ]; + + // Send both pairs off for separate processing. + diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); + diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); + + // Merge the results. + return diffsA.concat( [ + [ DIFF_EQUAL, midCommon ] + ], diffsB ); + } + + if ( checklines && text1.length > 100 && text2.length > 100 ) { + return this.diffLineMode( text1, text2, deadline ); + } + + return this.diffBisect( text1, text2, deadline ); + }; + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + * @private + */ + DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) { + var longtext, shorttext, dmp, + text1A, text2B, text2A, text1B, midCommon, + hm1, hm2, hm; + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) { + return null; // Pointless. + } + dmp = this; // 'this' becomes 'window' in a closure. + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diffHalfMatchI( longtext, shorttext, i ) { + var seed, j, bestCommon, prefixLength, suffixLength, + bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; + + // Start with a 1/4 length substring at position i as a seed. + seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); + j = -1; + bestCommon = ""; + while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) { + prefixLength = dmp.diffCommonPrefix( longtext.substring( i ), + shorttext.substring( j ) ); + suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ), + shorttext.substring( 0, j ) ); + if ( bestCommon.length < suffixLength + prefixLength ) { + bestCommon = shorttext.substring( j - suffixLength, j ) + + shorttext.substring( j, j + prefixLength ); + bestLongtextA = longtext.substring( 0, i - suffixLength ); + bestLongtextB = longtext.substring( i + prefixLength ); + bestShorttextA = shorttext.substring( 0, j - suffixLength ); + bestShorttextB = shorttext.substring( j + prefixLength ); + } + } + if ( bestCommon.length * 2 >= longtext.length ) { + return [ bestLongtextA, bestLongtextB, + bestShorttextA, bestShorttextB, bestCommon + ]; + } else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + hm1 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 4 ) ); + + // Check again based on the third quarter. + hm2 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 2 ) ); + if ( !hm1 && !hm2 ) { + return null; + } else if ( !hm2 ) { + hm = hm1; + } else if ( !hm1 ) { + hm = hm2; + } else { + + // Both matched. Select the longest. + hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + text1A, text1B, text2A, text2B; + if ( text1.length > text2.length ) { + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + } else { + text2A = hm[ 0 ]; + text2B = hm[ 1 ]; + text1A = hm[ 2 ]; + text1B = hm[ 3 ]; + } + midCommon = hm[ 4 ]; + return [ text1A, text1B, text2A, text2B, midCommon ]; + }; + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { + var a, diffs, linearray, pointer, countInsert, + countDelete, textInsert, textDelete, j; + + // Scan the text on a line-by-line basis first. + a = this.diffLinesToChars( text1, text2 ); + text1 = a.chars1; + text2 = a.chars2; + linearray = a.lineArray; + + diffs = this.DiffMain( text1, text2, false, deadline ); + + // Convert the diff back to original text. + this.diffCharsToLines( diffs, linearray ); + + // Eliminate freak matches (e.g. blank lines) + this.diffCleanupSemantic( diffs ); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push( [ DIFF_EQUAL, "" ] ); + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + break; + case DIFF_EQUAL: + + // Upon reaching an equality, check for prior redundancies. + if ( countDelete >= 1 && countInsert >= 1 ) { + + // Delete the offending records and add the merged ones. + diffs.splice( pointer - countDelete - countInsert, + countDelete + countInsert ); + pointer = pointer - countDelete - countInsert; + a = this.DiffMain( textDelete, textInsert, false, deadline ); + for ( j = a.length - 1; j >= 0; j-- ) { + diffs.splice( pointer, 0, a[ j ] ); + } + pointer = pointer + a.length; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + pointer++; + } + diffs.pop(); // Remove the dummy entry at the end. + + return diffs; + }; + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) { + var text1Length, text2Length, maxD, vOffset, vLength, + v1, v2, x, delta, front, k1start, k1end, k2start, + k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; + + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + maxD = Math.ceil( ( text1Length + text2Length ) / 2 ); + vOffset = maxD; + vLength = 2 * maxD; + v1 = new Array( vLength ); + v2 = new Array( vLength ); + + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for ( x = 0; x < vLength; x++ ) { + v1[ x ] = -1; + v2[ x ] = -1; + } + v1[ vOffset + 1 ] = 0; + v2[ vOffset + 1 ] = 0; + delta = text1Length - text2Length; + + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = ( delta % 2 !== 0 ); + + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + k1start = 0; + k1end = 0; + k2start = 0; + k2end = 0; + for ( d = 0; d < maxD; d++ ) { + + // Bail out if deadline is reached. + if ( ( new Date() ).getTime() > deadline ) { + break; + } + + // Walk the front path one step. + for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) { + k1Offset = vOffset + k1; + if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { + x1 = v1[ k1Offset + 1 ]; + } else { + x1 = v1[ k1Offset - 1 ] + 1; + } + y1 = x1 - k1; + while ( x1 < text1Length && y1 < text2Length && + text1.charAt( x1 ) === text2.charAt( y1 ) ) { + x1++; + y1++; + } + v1[ k1Offset ] = x1; + if ( x1 > text1Length ) { + + // Ran off the right of the graph. + k1end += 2; + } else if ( y1 > text2Length ) { + + // Ran off the bottom of the graph. + k1start += 2; + } else if ( front ) { + k2Offset = vOffset + delta - k1; + if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { + + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - v2[ k2Offset ]; + if ( x1 >= x2 ) { + + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } + } + } + + // Walk the reverse path one step. + for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) { + k2Offset = vOffset + k2; + if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { + x2 = v2[ k2Offset + 1 ]; + } else { + x2 = v2[ k2Offset - 1 ] + 1; + } + y2 = x2 - k2; + while ( x2 < text1Length && y2 < text2Length && + text1.charAt( text1Length - x2 - 1 ) === + text2.charAt( text2Length - y2 - 1 ) ) { + x2++; + y2++; + } + v2[ k2Offset ] = x2; + if ( x2 > text1Length ) { + + // Ran off the left of the graph. + k2end += 2; + } else if ( y2 > text2Length ) { + + // Ran off the top of the graph. + k2start += 2; + } else if ( !front ) { + k1Offset = vOffset + delta - k2; + if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { + x1 = v1[ k1Offset ]; + y1 = vOffset + x1 - k1Offset; + + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - x2; + if ( x1 >= x2 ) { + + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } + } + } + } + + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + }; + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { + var text1a, text1b, text2a, text2b, diffs, diffsb; + text1a = text1.substring( 0, x ); + text2a = text2.substring( 0, y ); + text1b = text1.substring( x ); + text2b = text2.substring( y ); + + // Compute both diffs serially. + diffs = this.DiffMain( text1a, text2a, false, deadline ); + diffsb = this.DiffMain( text1b, text2b, false, deadline ); + + return diffs.concat( diffsb ); + }; + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, + lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + + // Number of characters that changed prior to the equality. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + + // Number of characters that changed after the equality. + lengthInsertions2 = 0; + lengthDeletions2 = 0; + while ( pointer < diffs.length ) { + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. + equalities[ equalitiesLength++ ] = pointer; + lengthInsertions1 = lengthInsertions2; + lengthDeletions1 = lengthDeletions2; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = diffs[ pointer ][ 1 ]; + } else { // An insertion or deletion. + if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + lengthInsertions2 += diffs[ pointer ][ 1 ].length; + } else { + lengthDeletions2 += diffs[ pointer ][ 1 ].length; + } + + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if ( lastequality && ( lastequality.length <= + Math.max( lengthInsertions1, lengthDeletions1 ) ) && + ( lastequality.length <= Math.max( lengthInsertions2, + lengthDeletions2 ) ) ) { + + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); + + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + + // Throw away the equality we just deleted. + equalitiesLength--; + + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + + // Reset the counters. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = null; + changes = true; + } + } + pointer++; + } + + // Normalize the diff. + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while ( pointer < diffs.length ) { + if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE && + diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + deletion = diffs[ pointer - 1 ][ 1 ]; + insertion = diffs[ pointer ][ 1 ]; + overlapLength1 = this.diffCommonOverlap( deletion, insertion ); + overlapLength2 = this.diffCommonOverlap( insertion, deletion ); + if ( overlapLength1 >= overlapLength2 ) { + if ( overlapLength1 >= deletion.length / 2 || + overlapLength1 >= insertion.length / 2 ) { + + // Overlap found. Insert an equality and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] + ); + diffs[ pointer - 1 ][ 1 ] = + deletion.substring( 0, deletion.length - overlapLength1 ); + diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 ); + pointer++; + } + } else { + if ( overlapLength2 >= deletion.length / 2 || + overlapLength2 >= insertion.length / 2 ) { + + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] + ); + + diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT; + diffs[ pointer - 1 ][ 1 ] = + insertion.substring( 0, insertion.length - overlapLength2 ); + diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE; + diffs[ pointer + 1 ][ 1 ] = + deletion.substring( overlapLength2 ); + pointer++; + } + } + pointer++; + } + pointer++; + } + }; + + /** + * Determine if the suffix of one string is the prefix of another. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of the first + * string and the start of the second string. + * @private + */ + DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { + var text1Length, text2Length, textLength, + best, length, pattern, found; + + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + + // Eliminate the null case. + if ( text1Length === 0 || text2Length === 0 ) { + return 0; + } + + // Truncate the longer string. + if ( text1Length > text2Length ) { + text1 = text1.substring( text1Length - text2Length ); + } else if ( text1Length < text2Length ) { + text2 = text2.substring( 0, text1Length ); + } + textLength = Math.min( text1Length, text2Length ); + + // Quick check for the worst case. + if ( text1 === text2 ) { + return textLength; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + best = 0; + length = 1; + while ( true ) { + pattern = text1.substring( textLength - length ); + found = text2.indexOf( pattern ); + if ( found === -1 ) { + return best; + } + length += found; + if ( found === 0 || text1.substring( textLength - length ) === + text2.substring( 0, length ) ) { + best = length; + length++; + } + } + }; + + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {{chars1: string, chars2: string, lineArray: !Array.}} + * An object containing the encoded text1, the encoded text2 and + * the array of unique strings. + * The zeroth element of the array of unique strings is intentionally blank. + * @private + */ + DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { + var lineArray, lineHash, chars1, chars2; + lineArray = []; // E.g. lineArray[4] === 'Hello\n' + lineHash = {}; // E.g. lineHash['Hello\n'] === 4 + + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray[ 0 ] = ""; + + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode. + * @return {string} Encoded string. + * @private + */ + function diffLinesToCharsMunge( text ) { + var chars, lineStart, lineEnd, lineArrayLength, line; + chars = ""; + + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + lineStart = 0; + lineEnd = -1; + + // Keeping our own length variable is faster than looking it up. + lineArrayLength = lineArray.length; + while ( lineEnd < text.length - 1 ) { + lineEnd = text.indexOf( "\n", lineStart ); + if ( lineEnd === -1 ) { + lineEnd = text.length - 1; + } + line = text.substring( lineStart, lineEnd + 1 ); + lineStart = lineEnd + 1; + + if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) : + ( lineHash[ line ] !== undefined ) ) { + chars += String.fromCharCode( lineHash[ line ] ); + } else { + chars += String.fromCharCode( lineArrayLength ); + lineHash[ line ] = lineArrayLength; + lineArray[ lineArrayLength++ ] = line; + } + } + return chars; + } + + chars1 = diffLinesToCharsMunge( text1 ); + chars2 = diffLinesToCharsMunge( text2 ); + return { + chars1: chars1, + chars2: chars2, + lineArray: lineArray + }; + }; + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {!Array.} diffs Array of diff tuples. + * @param {!Array.} lineArray Array of unique strings. + * @private + */ + DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { + var x, chars, text, y; + for ( x = 0; x < diffs.length; x++ ) { + chars = diffs[ x ][ 1 ]; + text = []; + for ( y = 0; y < chars.length; y++ ) { + text[ y ] = lineArray[ chars.charCodeAt( y ) ]; + } + diffs[ x ][ 1 ] = text.join( "" ); + } + }; + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) { + var pointer, countDelete, countInsert, textInsert, textDelete, + commonlength, changes, diffPointer, position; + diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + commonlength; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_EQUAL: + + // Upon reaching an equality, check for prior redundancies. + if ( countDelete + countInsert > 1 ) { + if ( countDelete !== 0 && countInsert !== 0 ) { + + // Factor out any common prefixes. + commonlength = this.diffCommonPrefix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + if ( ( pointer - countDelete - countInsert ) > 0 && + diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] === + DIFF_EQUAL ) { + diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] += + textInsert.substring( 0, commonlength ); + } else { + diffs.splice( 0, 0, [ DIFF_EQUAL, + textInsert.substring( 0, commonlength ) + ] ); + pointer++; + } + textInsert = textInsert.substring( commonlength ); + textDelete = textDelete.substring( commonlength ); + } + + // Factor out any common suffixies. + commonlength = this.diffCommonSuffix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length - + commonlength ) + diffs[ pointer ][ 1 ]; + textInsert = textInsert.substring( 0, textInsert.length - + commonlength ); + textDelete = textDelete.substring( 0, textDelete.length - + commonlength ); + } + } + + // Delete the offending records and add the merged ones. + if ( countDelete === 0 ) { + diffs.splice( pointer - countInsert, + countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); + } else if ( countInsert === 0 ) { + diffs.splice( pointer - countDelete, + countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); + } else { + diffs.splice( + pointer - countDelete - countInsert, + countDelete + countInsert, + [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] + ); + } + pointer = pointer - countDelete - countInsert + + ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1; + } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) { + + // Merge this equality with the previous one. + diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ]; + diffs.splice( pointer, 1 ); + } else { + pointer++; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + } + if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + changes = false; + pointer = 1; + + // Intentionally ignore the first and last element (don't need checking). + while ( pointer < diffs.length - 1 ) { + if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL && + diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) { + + diffPointer = diffs[ pointer ][ 1 ]; + position = diffPointer.substring( + diffPointer.length - diffs[ pointer - 1 ][ 1 ].length + ); + + // This is a single edit surrounded by equalities. + if ( position === diffs[ pointer - 1 ][ 1 ] ) { + + // Shift the edit over the previous equality. + diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] + + diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length - + diffs[ pointer - 1 ][ 1 ].length ); + diffs[ pointer + 1 ][ 1 ] = + diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ]; + diffs.splice( pointer - 1, 1 ); + changes = true; + } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === + diffs[ pointer + 1 ][ 1 ] ) { + + // Shift the edit over the next equality. + diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ]; + diffs[ pointer ][ 1 ] = + diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) + + diffs[ pointer + 1 ][ 1 ]; + diffs.splice( pointer + 1, 1 ); + changes = true; + } + } + pointer++; + } + + // If shifts were made, the diff needs reordering and another shift sweep. + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; + + return function( o, n ) { + var diff, output, text; + diff = new DiffMatchPatch(); + output = diff.DiffMain( o, n ); + diff.diffCleanupEfficiency( output ); + text = diff.diffPrettyHtml( output ); + + return text; + }; +}() ); + +}() ); diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.xml b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.xml new file mode 100644 index 00000000000..f9cd2e0ae3e --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/qunit-2.0.1_js.xml @@ -0,0 +1,119 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/javascript + + + default_reference + qunit-2.0.1.js + + + description + + + + + + id + qunit-2.0.1_js + + + language + + + + + + portal_type + Web Script + + + short_title + + + + + + title + Qunit-2.0.1 (js) + + + version + + + + + + + + diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.html b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.html new file mode 100644 index 00000000000..b2d880cce2f --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + +
      + +
      +
      +
      + +
      + +
      + + \ No newline at end of file diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.xml b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.xml new file mode 100644 index 00000000000..1c96a8908dc --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_html.xml @@ -0,0 +1,119 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/html + + + default_reference + webrtc_unit_test.html + + + description + + + + + + id + webrtc_unit_test_html + + + language + + + + + + portal_type + Web Page + + + short_title + + + + + + title + Unit test for webrtc gadget + + + version + + + + + + + + diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.js b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.js new file mode 100644 index 00000000000..1a5a4a25d09 --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.js @@ -0,0 +1,231 @@ +/*global window, document, rJS, JSON, QUnit, jQuery, RSVP, console, setTimeout*/ + +(function(rJS, QUnit, RSVP, $) { + "use strict"; + var error_handler = function(e) { + window.console.error(e); + ok(false, e); + }, + connection_options = [ + { + // No need for stun server on the same lan / ipv6 + iceServers: [ + {'url': 'stun:23.21.150.121'}, + {url: 'stun:stun.1.google.com:19302'} + ] + }, + { + 'optional': [{DtlsSrtpKeyAgreement: true}] + } + ], + data_channel_options = {reliable: true}, + offer_contraints = { + mandatory: { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + } + }, + offer = {"type":"offer", "sdp":"v=0\r\n\ +o=- 2708361213080913936 2 IN IP4 127.0.0.1\r\n\ +s=-\r\n\ +t=0 0\r\n\ +a=msid-semantic: WMS\r\n\ +m=application 60581 DTLS/SCTP 5000\r\n\ +c=IN IP4 82.226.112.70\r\n\ +a=candidate:3097449529 1 udp 2113937151 192.168.242.146 60581 typ host generation 0 network-cost 50\r\n\ +a=candidate:421107265 1 udp 2113939711 2a01:e35:2e27:460:3430:1418:c2a6:f8e5 43111 typ host generation 0 network-cost 50\r\n\ +a=candidate:842163049 1 udp 1677729535 82.226.112.70 60581 typ srflx raddr 192.168.242.146 rport 60581 generation 0 network-cost 50\r\n\ +a=ice-ufrag:N0ey\r\n\ +a=ice-pwd:hgfceTKEWpBojQfb+iA3DG0x\r\n\ +a=fingerprint:sha-256 56:82:C4:19:56:BA:BE:B2:37:E0:9B:87:44:AB:0C:D1:7A:4C:61:E1:AA:1D:56:70:91:EE:7D:1E:57:BC:28:3A\r\n\ +a=setup:actpass\r\n\ +a=mid:data\r\n\ +a=sctpmap:5000 webrtc-datachannel 1024\r\n" +} + + QUnit.config.testTimeout = 60000; + rJS(window) + .ready(function(g) { + g.props = {}; + g.props.Alice = {}, + g.props.Bob = {}, + g.props.config = { + type: "query", + sub_storage : { + type: "uuid", + sub_storage: { + "type": "indexeddb", + "database": "serverless" + } + } + }; + + g.props.description_defer = new RSVP.defer(); + return g.getElement() + .push(function (element) { + g.props.element = element; + }); + }) + .allowPublicAcquisition("notifyDescriptionCalculated", function (args) { + this.props.description_defer.resolve(args[0]); + this.props.description_defer = RSVP.defer(); + }) + .declareService(function() { + var g = this; + QUnit.test("Initiate Clients", function( assert ) { + assert.expect(2); + var done = assert.async(); + return g.getDeclaredGadget("alice_handshake_gadget") + .then(function(new_gadget) { + g.props.Alice.alice_handshake_gadget = new_gadget; + return g.props.Alice.alice_handshake_gadget.register("meetup", "alice", g.props.config); + }) + .then(function(res) { + assert.equal(res[0], "meetup_alice", "Alice initiate the handshake gadget and register"); + return g.getDeclaredGadget("bob_handshake_gadget") + }) + .then(function(new_gadget) { + g.props.Bob.bob_handshake_gadget = new_gadget; + return g.props.Bob.bob_handshake_gadget.register("meetup", "bob", g.props.config); + }) + .then(function(res) { + assert.equal(res[0], "meetup_bob", "Bob initiate the handshake gadget and register"); + }) + .then(function(new_gadget) { + var new_element = document.createElement("div"); + g.props.element.querySelector(".gadget_webrtc").appendChild(new_element); + var scope = "webrtc_bob"; + return g.declareGadget("gadget_webrtc.html", { + scope: scope, + element: new_element + }) + }) + .then(function(rtc_gadget) { + g.props.Bob.webrtc_gadget = rtc_gadget; + }) + .then(function(new_gadget) { + var new_element = document.createElement("div"); + g.props.element.querySelector(".gadget_webrtc").appendChild(new_element); + var scope = "webrtc_alice"; + return g.declareGadget("gadget_webrtc.html", { + scope: scope, + element: new_element + }) + }) + .then(function(rtc_gadget) { + g.props.Alice.webrtc_gadget = rtc_gadget; + }) + .fail(error_handler).always(done); + }); + + QUnit.test("Alice waits and picks the Offer as available", function( assert ) { + var done = assert.async(); + assert.expect(1); + var offer_deffer = RSVP.defer(); + return g.props.Alice.alice_handshake_gadget.wait_until_available("meetup", "alice_", + function(response){ + assert.equal(JSON.parse(response[0]).from, "bob", "Alice waits for the offer while other tests are running and picks the offer from bob as offer gets available."); + offer_deffer.resolve(); + }) + .fail(error_handler).always(done); + }); + + QUnit.test("Bob can create offer", function( assert ) { + var local_description; + var done = assert.async(); + assert.expect(4); + return new RSVP.Queue() + .then(function() { + return g.props.Bob.webrtc_gadget.createConnection.apply(g.props.Bob.webrtc_gadget, connection_options) + .push(function () { + return g.props.Bob.webrtc_gadget.createDataChannel("bob", data_channel_options); + }) + .push(function () { + return g.props.Bob.webrtc_gadget.createOffer(offer_contraints); + }) + .push(function (ld) { + local_description = ld; + assert.equal(local_description.type, "offer", "Check if local driscription genrated is of type offer"); + return g.props.Bob.webrtc_gadget.setLocalDescription(local_description); + }) + .push(function () { + return g.props.description_defer.promise; + }); + }) + .then(function(description) { + // diff to check ice candidates + var parsed_desc = JSON.parse(description), + count = 0, + i; + + assert.notDeepEqual(parsed_desc.sdp, local_description, "Diff the local driscription generated above with the ice candidates "); + + var line_list = parsed_desc.sdp.split('\n'); + for (i = 0; i < line_list.length; i += 1) { + if (line_list[i].indexOf('a=candidate:') === 0) { + count += 1; + } + } + assert.equal(count, 3, "Check if all the 3 ice candidates are generated sucessfully"); + assert.equal(parsed_desc.type, "offer", "Check if final type is offer"); + }) + .fail(error_handler).always(done); + }); + + QUnit.test("Bob can send Offer", function( assert ) { + var done = assert.async(); + assert.expect(2); + + var params = {'name': 'alice_bob', + 'data' : new Blob([JSON.stringify({from: 'bob', + action: "offer", + data: offer})], + {type : "application/json"})}; + + return g.props.Bob.bob_handshake_gadget.handle_offer_answer("meetup", params) + .then(function(res) { + assert.equal(res[0], "meetup_alice_bob", "Check if Bob sucessfully saves the offer into the JIO "); + return g.props.Bob.bob_handshake_gadget.get_answer('meetup', "alice_bob"); + }) + .then(function(res) { + assert.deepEqual(res, params.data, "Check if the data retrived is same as data entered"); + }) + .fail(error_handler).always(done); + }); + + QUnit.test("Alice generates an answer", function( assert ) { + var done = assert.async(); + + assert.expect(2); + + var webrtc = g.props.Alice.webrtc_gadget; + return webrtc.createConnection.apply(webrtc, connection_options) + .push(function () { + return webrtc.setRemoteDescription(JSON.stringify(offer)); + }) + .push(function () { + return webrtc.createAnswer(offer_contraints); + }) + .push(function (local_description) { + return webrtc.setLocalDescription(local_description); + }) + .push(function () { + g.props.description_defer = new RSVP.defer(); + return g.props.description_defer.promise; + }) + .then(function(res) { + var parsed_desc = JSON.parse(res), + count = 0; + var line_list = parsed_desc.sdp.split('\n'); + for (var i = 0; i < line_list.length; i += 1) { + if (line_list[i].indexOf('a=candidate:') === 0) { + count += 1; + } + } + assert.equal(count, 3, "Check if all the 3 ice candidates are generated sucessfully"); + assert.equal(parsed_desc.type, "answer", "Check if genrated description is of type answer"); + }) + .fail(error_handler).always(done); + }); + }); +})(rJS, QUnit, RSVP, jQuery); \ No newline at end of file diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.xml b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.xml new file mode 100644 index 00000000000..2ff9131d84f --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_page_module/webrtc_unit_test_js.xml @@ -0,0 +1,119 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + _Add_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _Change_local_roles_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + Owner + + + + + content_md5 + + + + + + content_type + text/javascript + + + default_reference + webrtc_unit_test.js + + + description + + + + + + id + webrtc_unit_test_js + + + language + + + + + + portal_type + Web Script + + + short_title + + + + + + title + Unit test for webrtc gadget (JS) + + + version + + + + + + + + diff --git a/bt5/erp5_ui_test/PathTemplateItem/web_site_module/webrtc_unit_test.xml b/bt5/erp5_ui_test/PathTemplateItem/web_site_module/webrtc_unit_test.xml new file mode 100644 index 00000000000..1dda92ff3fe --- /dev/null +++ b/bt5/erp5_ui_test/PathTemplateItem/web_site_module/webrtc_unit_test.xml @@ -0,0 +1,473 @@ + + + + + + + + + + _Access_contents_information_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Authenticated + Author + Manager + Member + Owner + Reviewer + + + + + _Add_portal_content_Permission + + + Assignor + Manager + + + + + _Add_portal_folders_Permission + + + Assignor + Manager + + + + + _Copy_or_Move_Permission + + + Assignor + Manager + + + + + _Delete_objects_Permission + + + Assignor + Manager + + + + + _Modify_portal_content_Permission + + + Assignee + Assignor + Manager + + + + + _View_Permission + + + Anonymous + Assignee + Assignor + Associate + Auditor + Authenticated + Author + Manager + Member + Owner + Reviewer + + + + + __before_publishing_traverse__ + + + + + + + + + + _defined_in_class + 1 + + + _hookname + __before_publishing_traverse__ + + + _list + + + AAAAAAAAAAI= + + + + + _prior + + + + + + + + + + + __before_traverse__ + + + + + + 99 + ERP5 Web Site/webrtc_unit_test + + + + AAAAAAAAAAI= + + + + + + + _count + + AAAAAAAAAAM= + + + + _identity_criterion + + AAAAAAAAAAQ= + + + + _local_properties + + + + + id + configuration_hateoas_url + + + type + string + + + + + id + configuration_application_title + + + type + string + + + + + id + configuration_view_action_category + + + type + string + + + + + id + configuration_default_view_action_reference + + + type + string + + + + + id + configuration_translation_gadget_url + + + type + string + + + + + id + configuration_jio_gadget_url + + + type + string + + + + + id + configuration_panel_gadget_url + + + type + string + + + + + id + configuration_header_gadget_url + + + type + string + + + + + id + configuration_content_security_policy + + + type + string + + + + + id + configuration_frontpage_gadget_url + + + type + string + + + + + id + configuration_x_frame_options + + + type + string + + + + + + + _mt_index + + AAAAAAAAAAU= + + + + _range_criterion + + AAAAAAAAAAY= + + + + _tree + + AAAAAAAAAAc= + + + + authorization_forced + 0 + + + available_language + + + en + + + + + categories + + + caching_policy/must-revalidate + aggregate/web_page_module/webrtc_unit_test_html + + + + + configuration_application_title + ERP5 + + + configuration_content_security_policy + default-src \'none\'; img-src \'self\' data:; media-src \'self\' blob:; connect-src \'self\' ws: https://localhost:5000 https://softinst67513.host.vifib.net mail.tiolive.com data:; script-src \'self\' \'unsafe-eval\'; font-src netdna.bootstrapcdn.com data:; style-src \'self\' netdna.bootstrapcdn.com \'unsafe-inline\' data:; frame-src \'self\' data: + + + configuration_default_view_action_reference + view + + + configuration_frontpage_gadget_url + worklist + + + configuration_hateoas_url + hateoas/ + + + configuration_header_gadget_url + + + + + + configuration_jio_gadget_url + gadget_erp5_jio.html + + + configuration_panel_gadget_url + gadget_erp5_panel.html + + + configuration_translation_gadget_url + gadget_translation.html + + + configuration_view_action_category + object_view + + + configuration_x_frame_options + SAMEORIGIN + + + container_layout + WebSection_renderDefaultPageAsGadget + + + content_layout + WebSection_renderDefaultPageAsGadget + + + custom_render_method_id + WebSection_renderDefaultPageAsGadget + + + description + RenderJS Gadget ERP5 + + + id + webrtc_unit_test + + + layout_configuration_form_id + WebSection_viewRenderJSPreference + + + portal_type + Web Site + + + short_title + + + + + + skin_selection_name + RJS + + + static_language_selection + 0 + + + title + Unit test for webrtc gadget + + + visible + 1 + + + + + + + + + + + + + + + + + 0 + + + + + + + + + data + + + + + + + + + + + + + + + + + + + + + + + data + + + + + + + + + + + + + + + + diff --git a/bt5/erp5_ui_test/bt/dependency_list b/bt5/erp5_ui_test/bt/dependency_list index 1cfe5060d72..07b2943eda5 100644 --- a/bt5/erp5_ui_test/bt/dependency_list +++ b/bt5/erp5_ui_test/bt/dependency_list @@ -1 +1,2 @@ -erp5_ui_test_core \ No newline at end of file +erp5_ui_test_core +erp5_webrtc_gadget \ No newline at end of file diff --git a/bt5/erp5_ui_test/bt/template_path_list b/bt5/erp5_ui_test/bt/template_path_list index 50bb6cbeda9..1a913e3e3fc 100644 --- a/bt5/erp5_ui_test/bt/template_path_list +++ b/bt5/erp5_ui_test/bt/template_path_list @@ -40,4 +40,11 @@ portal_tests/proxy_field_zuite/** portal_tests/relation_field_zuite portal_tests/relation_field_zuite/** portal_tests/search_zuite -portal_tests/search_zuite/** \ No newline at end of file +portal_tests/search_zuite/** +portal_tests/webrtc_ui_zuite +portal_tests/webrtc_ui_zuite/** +web_page_module/qunit-2.0.1_css +web_page_module/qunit-2.0.1_js +web_page_module/webrtc_unit_test_html +web_page_module/webrtc_unit_test_js +web_site_module/webrtc_unit_test \ No newline at end of file -- 2.30.9 From e888d23303cf68cdf424d1e8b85a4ad132ce020e Mon Sep 17 00:00:00 2001 From: Hardik Juneja Date: Wed, 12 Oct 2016 15:34:14 +0000 Subject: [PATCH 3/5] erp5_webrtc_gadget: Add webrtc rooms portal type required for erp5 support --- .../Webrtc%20Room/delete_offer_answer.xml | 85 ++++++ .../Webrtc%20Room/get_all_offer_answer.xml | 85 ++++++ .../Webrtc%20Room/get_offer_answer.xml | 85 ++++++ .../Webrtc%20Room/store_offer_answer.xml | 85 ++++++ .../portal_types/Webrtc%20Room/view.xml | 83 ++++++ .../Webrtc%20Rooms%20Module/new_room.xml | 85 ++++++ .../Webrtc%20Rooms%20Module/view.xml | 83 ++++++ .../webrtc_rooms_module.xml | 7 + .../webrtc_rooms_module.xml | 113 ++++++++ .../allowed_content_types.xml | 5 + .../base_category_list.xml | 5 + .../Webrtc%20Room.xml | 2 + .../Webrtc%20Rooms%20Module.xml | 2 + .../portal_types/Webrtc%20Room.xml | 28 ++ .../portal_types/Webrtc%20Rooms%20Module.xml | 108 +++++++ .../workflow_chain_type.xml | 6 + .../portal_skins/erp5_webrtc_room.xml | 90 ++++++ .../WebrtcRoom_addWebrtcRoom.py | 17 ++ .../WebrtcRoom_addWebrtcRoom.xml | 62 ++++ .../WebrtcRoom_deleteOfferAnswer.py | 10 + .../WebrtcRoom_deleteOfferAnswer.xml | 62 ++++ .../WebrtcRoom_getAllOfferAnswer.py | 11 + .../WebrtcRoom_getAllOfferAnswer.xml | 62 ++++ .../WebrtcRoom_getOfferAnswer.py | 7 + .../WebrtcRoom_getOfferAnswer.xml | 62 ++++ .../WebrtcRoom_storeOfferAnswer.py | 20 ++ .../WebrtcRoom_storeOfferAnswer.xml | 62 ++++ .../erp5_webrtc_room/WebrtcRoom_view.xml | 131 +++++++++ .../WebrtcRoom_view/my_title.xml | 272 ++++++++++++++++++ .../WebrtcRoomsModule_viewAddWebrtcRoom.xml | 131 +++++++++ .../your_title.xml | 272 ++++++++++++++++++ .../WebrtcRoomsModule_viewWebrtcRoomList.xml | 131 +++++++++ .../listbox.xml | 101 +++++++ .../bt/template_action_path_list | 7 + .../bt/template_local_role_list | 1 + .../bt/template_module_id_list | 1 + ...late_portal_type_allowed_content_type_list | 1 + .../template_portal_type_base_category_list | 1 + .../bt/template_portal_type_id_list | 2 + .../bt/template_portal_type_role_list | 2 + .../template_portal_type_workflow_chain_list | 1 + .../bt/template_skin_id_list | 1 + 42 files changed, 2387 insertions(+) create mode 100644 bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/delete_offer_answer.xml create mode 100644 bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_all_offer_answer.xml create mode 100644 bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_offer_answer.xml create mode 100644 bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/store_offer_answer.xml create mode 100644 bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/view.xml create mode 100644 bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/new_room.xml create mode 100644 bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/view.xml create mode 100644 bt5/erp5_webrtc_gadget/LocalRolesTemplateItem/webrtc_rooms_module.xml create mode 100644 bt5/erp5_webrtc_gadget/ModuleTemplateItem/webrtc_rooms_module.xml create mode 100644 bt5/erp5_webrtc_gadget/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml create mode 100644 bt5/erp5_webrtc_gadget/PortalTypeBaseCategoryTemplateItem/base_category_list.xml create mode 100644 bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Room.xml create mode 100644 bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Rooms%20Module.xml create mode 100644 bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Room.xml create mode 100644 bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Rooms%20Module.xml create mode 100644 bt5/erp5_webrtc_gadget/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.py create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.py create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.py create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.py create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.py create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view/my_title.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom/your_title.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList.xml create mode 100644 bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList/listbox.xml create mode 100644 bt5/erp5_webrtc_gadget/bt/template_action_path_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_local_role_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_module_id_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_portal_type_allowed_content_type_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_portal_type_base_category_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_portal_type_id_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_portal_type_role_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_portal_type_workflow_chain_list create mode 100644 bt5/erp5_webrtc_gadget/bt/template_skin_id_list diff --git a/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/delete_offer_answer.xml b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/delete_offer_answer.xml new file mode 100644 index 00000000000..4728915317b --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/delete_offer_answer.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + + + description + + + + + + icon + + + + id + delete_offer_answer + + + permissions + + + Public, everyone can access + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + Delete Offer Answer + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/WebrtcRoom_deleteOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_all_offer_answer.xml b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_all_offer_answer.xml new file mode 100644 index 00000000000..39c0b20b048 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_all_offer_answer.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + + + description + + + + + + icon + + + + id + get_all_offer_answer + + + permissions + + + Public, everyone can access + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + Get All Offer Answer + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/WebrtcRoom_getAllOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_offer_answer.xml b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_offer_answer.xml new file mode 100644 index 00000000000..2dacdb40c9b --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/get_offer_answer.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + + + description + + + + + + icon + + + + id + get_offer_answer + + + permissions + + + Public, everyone can access + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + Get Offer Answer + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/WebrtcRoom_getOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/store_offer_answer.xml b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/store_offer_answer.xml new file mode 100644 index 00000000000..a33d3904f88 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/store_offer_answer.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + + + description + + + + + + icon + + + + id + store_offer_answer + + + permissions + + + Public, everyone can access + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + Store Offer Answer + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/WebrtcRoom_storeOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/view.xml b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/view.xml new file mode 100644 index 00000000000..9f5f2ac47b6 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Room/view.xml @@ -0,0 +1,83 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + icon + + + + id + view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/WebrtcRoom_view + + + + + diff --git a/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/new_room.xml b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/new_room.xml new file mode 100644 index 00000000000..8abb75ce9bb --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/new_room.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + + + description + + + + + + icon + + + + id + new_room + + + permissions + + + Public, everyone can access + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + New Room + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/WebrtcRoom_addWebrtcRoom + + + + + diff --git a/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/view.xml b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/view.xml new file mode 100644 index 00000000000..dfaac5c10a1 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ActionTemplateItem/portal_types/Webrtc%20Rooms%20Module/view.xml @@ -0,0 +1,83 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_list + + + + + category + object_list + + + condition + + + + description + + + + icon + + + + id + view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/WebrtcRoomsModule_viewWebrtcRoomList + + + + + diff --git a/bt5/erp5_webrtc_gadget/LocalRolesTemplateItem/webrtc_rooms_module.xml b/bt5/erp5_webrtc_gadget/LocalRolesTemplateItem/webrtc_rooms_module.xml new file mode 100644 index 00000000000..422c3dfbbed --- /dev/null +++ b/bt5/erp5_webrtc_gadget/LocalRolesTemplateItem/webrtc_rooms_module.xml @@ -0,0 +1,7 @@ + + + + Owner + + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/ModuleTemplateItem/webrtc_rooms_module.xml b/bt5/erp5_webrtc_gadget/ModuleTemplateItem/webrtc_rooms_module.xml new file mode 100644 index 00000000000..0c2d04b002f --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ModuleTemplateItem/webrtc_rooms_module.xml @@ -0,0 +1,113 @@ + + webrtc_rooms_module + + + Access Transient Objects + Anonymous + Assignee + Assignor + Associate + Auditor + Author + Manager + + + Access contents information + Anonymous + Assignee + Assignor + Associate + Auditor + Author + Manager + + + Access future portal content + Anonymous + Manager + + + Access session data + Anonymous + Assignee + Assignor + Associate + Auditor + Author + Manager + + + Add portal content + Anonymous + Assignor + Author + Manager + + + Add portal folders + Anonymous + Assignor + Author + Manager + + + Change local roles + Assignor + Manager + + + Copy or Move + Assignee + Assignor + Associate + Auditor + Author + Manager + + + Delete objects + Anonymous + Assignor + Manager + + + List folder contents + Assignee + Assignor + Associate + Auditor + Author + Manager + + + Modify portal content + Anonymous + Assignor + Manager + + + Public, everyone can access + Anonymous + + + View + Anonymous + Assignee + Assignor + Associate + Auditor + Manager + + + View History + Assignee + Assignor + Associate + Auditor + Author + Manager + + + Webrtc Rooms Module + Webrtc Rooms + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml b/bt5/erp5_webrtc_gadget/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml new file mode 100644 index 00000000000..5fc09ae8fcb --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml @@ -0,0 +1,5 @@ + + + Webrtc Room + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PortalTypeBaseCategoryTemplateItem/base_category_list.xml b/bt5/erp5_webrtc_gadget/PortalTypeBaseCategoryTemplateItem/base_category_list.xml new file mode 100644 index 00000000000..95e120f65fa --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PortalTypeBaseCategoryTemplateItem/base_category_list.xml @@ -0,0 +1,5 @@ + + + business_application + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Room.xml b/bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Room.xml new file mode 100644 index 00000000000..600ebe860dd --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Room.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Rooms%20Module.xml b/bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Rooms%20Module.xml new file mode 100644 index 00000000000..600ebe860dd --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PortalTypeRolesTemplateItem/Webrtc%20Rooms%20Module.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Room.xml b/bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Room.xml new file mode 100644 index 00000000000..6f32b8e3e56 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Room.xml @@ -0,0 +1,28 @@ + + + + + + + + + + factory + addXMLObject + + + id + Webrtc Room + + + portal_type + Base Type + + + type_class + XMLObject + + + + + diff --git a/bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Rooms%20Module.xml b/bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Rooms%20Module.xml new file mode 100644 index 00000000000..2cec80ab97d --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PortalTypeTemplateItem/portal_types/Webrtc%20Rooms%20Module.xml @@ -0,0 +1,108 @@ + + + + + + + + + + _property_domain_dict + + AAAAAAAAAAI= + + + + content_icon + folder_icon.gif + + + factory + addFolder + + + group_list + + + module + + + + + id + Webrtc Rooms Module + + + portal_type + Base Type + + + type_class + Folder + + + + + + + + + + + + data + + + + short_title + + AAAAAAAAAAM= + + + + title + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + domain_name + erp5_ui + + + property_name + short_title + + + + + + + + + + + + domain_name + erp5_ui + + + property_name + title + + + + + diff --git a/bt5/erp5_webrtc_gadget/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml b/bt5/erp5_webrtc_gadget/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml new file mode 100644 index 00000000000..022d5387f40 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml @@ -0,0 +1,6 @@ + + + Webrtc Room + edit_workflow + + \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room.xml new file mode 100644 index 00000000000..cb4b443823b --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room.xml @@ -0,0 +1,90 @@ + + + + + + + + + + _Access_Transient_Objects_Permission + + + Anonymous + + + + + _Access_arbitrary_user_session_data_Permission + + + Anonymous + + + + + _Access_contents_information_Permission + + + Anonymous + + + + + _Add_portal_content_Permission + + + Anonymous + + + + + _Add_portal_events_Permission + + + Anonymous + + + + + _Add_portal_folders_Permission + + + Anonymous + + + + + _Add_portal_member_Permission + + + Anonymous + + + + + _Add_portal_topics_Permission + + + Anonymous + + + + + _objects + + + + + + id + erp5_webrtc_room + + + title + + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.py b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.py new file mode 100644 index 00000000000..ad55fb2f5bf --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.py @@ -0,0 +1,17 @@ +translateString = context.Base_translateString + +room = context.newContent( + title = title, + portal_type = "Webrtc Room" +) + +if batch_mode: + return room + +room.publish() +portal_status_message = translateString( + "New room created" +) + +return room.Base_redirect('view', + keep_items = dict(portal_status_message=portal_status_message), **kw) diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.xml new file mode 100644 index 00000000000..8c271d01c44 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_addWebrtcRoom.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + title, form_id, batch_mode=False ,**kw + + + id + WebrtcRoom_addWebrtcRoom + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.py b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.py new file mode 100644 index 00000000000..460a2b20d24 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.py @@ -0,0 +1,10 @@ +request = context.REQUEST + +memcached_tool = context.getPortalObject().portal_memcached.getMemcachedDict(key_prefix=roomid, + plugin_path='portal_memcached/default_memcached_plugin') +del memcached_tool[name]; +peer_names = memcached_tool["peer_names"] +peer_names.remove(name) +memcached_tool["peer_names"] = peer_names + +return "done" diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.xml new file mode 100644 index 00000000000..47abee5cf10 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_deleteOfferAnswer.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + roomid, name + + + id + WebrtcRoom_deleteOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.py b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.py new file mode 100644 index 00000000000..50b2ae88f63 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.py @@ -0,0 +1,11 @@ +request = context.REQUEST + +memcached_tool = context.getPortalObject().portal_memcached.getMemcachedDict(key_prefix=roomid, + plugin_path='portal_memcached/default_memcached_plugin') + +try: + peer_array = memcached_tool['peer_names'] +except KeyError: + peer_array = [] + +return peer_array diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.xml new file mode 100644 index 00000000000..b51cc4c4bf6 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getAllOfferAnswer.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + roomid + + + id + WebrtcRoom_getAllOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.py b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.py new file mode 100644 index 00000000000..3c9c58fa580 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.py @@ -0,0 +1,7 @@ +request = context.REQUEST + +memcached_tool = context.getPortalObject().portal_memcached.getMemcachedDict(key_prefix=roomid, + plugin_path='portal_memcached/default_memcached_plugin') +data = memcached_tool[name] + +return data diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.xml new file mode 100644 index 00000000000..8ef39ff4a76 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_getOfferAnswer.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + roomid, name + + + id + WebrtcRoom_getOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.py b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.py new file mode 100644 index 00000000000..10029c8b96e --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.py @@ -0,0 +1,20 @@ +request = context.REQUEST + +memcached_tool = context.getPortalObject().portal_memcached.getMemcachedDict(key_prefix=roomid, + plugin_path='portal_memcached/default_memcached_plugin') + +peer_array = [] +try: + peer_array = memcached_tool['peer_names'] +except KeyError: + memcached_tool['peer_names'] = [] + +if peer_array is None: + memcached_tool['peer_names'] = [] + +peer_array.append(peerid) +memcached_tool[peerid] = data; +memcached_tool['peer_names'] = peer_array + +request.response.setBody("ok") +return request.response diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.xml new file mode 100644 index 00000000000..3ca2778b1d8 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_storeOfferAnswer.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + roomid, peerid, data + + + id + WebrtcRoom_storeOfferAnswer + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view.xml new file mode 100644 index 00000000000..a382938fd53 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view.xml @@ -0,0 +1,131 @@ + + + + + + + + + + _objects + + + + + + action + Base_edit + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + + + + center + + + + + + hidden + + + + + + left + + + my_title + + + + + right + + + + + + + + + id + WebrtcRoom_view + + + method + POST + + + name + WebrtcRoom_view + + + pt + form_view + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Webrtc Room + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view/my_title.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view/my_title.xml new file mode 100644 index 00000000000..4cc7d2e889b --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoom_view/my_title.xml @@ -0,0 +1,272 @@ + + + + + + + + + + id + my_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + required_not_found + Input is required but no input given. + + + too_long + Too much input was given. + + + + + + overrides + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + tales + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + values + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + 20 + + + editable + 1 + + + enabled + 1 + + + external_validator + + + + extra + + + + hidden + 0 + + + input_type + text + + + max_length + + + + required + 0 + + + title + Title + + + truncate + 0 + + + unicode + 0 + + + whitespace_preserve + 0 + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom.xml new file mode 100644 index 00000000000..6005e154ce4 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom.xml @@ -0,0 +1,131 @@ + + + + + + + + + + _objects + + + + + + action + WebrtcRoomsModule_addWebrtcRoom + + + description + use this dialog to create new rooms + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + + + + center + + + + + + hidden + + + + + + left + + + your_title + + + + + right + + + + + + + + + id + WebrtcRoomsModule_viewAddWebrtcRoom + + + method + POST + + + name + WebrtcRoomsModule_viewAddWebrtcRoom + + + pt + form_dialog + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + New Rooms + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom/your_title.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom/your_title.xml new file mode 100644 index 00000000000..a0ebfe0b0d9 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewAddWebrtcRoom/your_title.xml @@ -0,0 +1,272 @@ + + + + + + + + + + id + your_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + required_not_found + Input is required but no input given. + + + too_long + Too much input was given. + + + + + + overrides + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + tales + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + values + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + 20 + + + editable + 1 + + + enabled + 1 + + + external_validator + + + + extra + + + + hidden + 0 + + + input_type + text + + + max_length + + + + required + 0 + + + title + Title + + + truncate + 0 + + + unicode + 0 + + + whitespace_preserve + 0 + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList.xml new file mode 100644 index 00000000000..4f67eaa7a55 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList.xml @@ -0,0 +1,131 @@ + + + + + + + + + + _objects + + + + + + action + Base_doSelect + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + listbox + + + + + center + + + + + + hidden + + + + + + left + + + + + + right + + + + + + + + + id + WebrtcRoomsModule_viewWebrtcRoomList + + + method + POST + + + name + WebrtcRoomsModule_viewWebrtcRoomList + + + pt + form_list + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Webrtc Rooms + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList/listbox.xml b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList/listbox.xml new file mode 100644 index 00000000000..9a77dcd72c3 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/SkinTemplateItem/portal_skins/erp5_webrtc_room/WebrtcRoomsModule_viewWebrtcRoomList/listbox.xml @@ -0,0 +1,101 @@ + + + + + + + + + + delegated_list + + + portal_type + selection_name + title + + + + + id + listbox + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + field_id + my_list_mode_listbox + + + form_id + Base_viewFieldLibrary + + + portal_type + + + + Webrtc Room + Webrtc Room + + + + + + selection_name + webrtc_rooms_module_selection + + + title + Webrtc Rooms + + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/bt/template_action_path_list b/bt5/erp5_webrtc_gadget/bt/template_action_path_list new file mode 100644 index 00000000000..ba43dd918b4 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_action_path_list @@ -0,0 +1,7 @@ +Webrtc Room | delete_offer_answer +Webrtc Room | get_all_offer_answer +Webrtc Room | get_offer_answer +Webrtc Room | store_offer_answer +Webrtc Room | view +Webrtc Rooms Module | new_room +Webrtc Rooms Module | view \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_local_role_list b/bt5/erp5_webrtc_gadget/bt/template_local_role_list new file mode 100644 index 00000000000..18b0bbbbdd9 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_local_role_list @@ -0,0 +1 @@ +webrtc_rooms_module \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_module_id_list b/bt5/erp5_webrtc_gadget/bt/template_module_id_list new file mode 100644 index 00000000000..18b0bbbbdd9 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_module_id_list @@ -0,0 +1 @@ +webrtc_rooms_module \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_portal_type_allowed_content_type_list b/bt5/erp5_webrtc_gadget/bt/template_portal_type_allowed_content_type_list new file mode 100644 index 00000000000..722d1effd3e --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_portal_type_allowed_content_type_list @@ -0,0 +1 @@ +Webrtc Rooms Module | Webrtc Room \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_portal_type_base_category_list b/bt5/erp5_webrtc_gadget/bt/template_portal_type_base_category_list new file mode 100644 index 00000000000..f8a470c958f --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_portal_type_base_category_list @@ -0,0 +1 @@ +Webrtc Rooms Module | business_application \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_portal_type_id_list b/bt5/erp5_webrtc_gadget/bt/template_portal_type_id_list new file mode 100644 index 00000000000..7db6aaad3fc --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_portal_type_id_list @@ -0,0 +1,2 @@ +Webrtc Room +Webrtc Rooms Module \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_portal_type_role_list b/bt5/erp5_webrtc_gadget/bt/template_portal_type_role_list new file mode 100644 index 00000000000..7db6aaad3fc --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_portal_type_role_list @@ -0,0 +1,2 @@ +Webrtc Room +Webrtc Rooms Module \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_portal_type_workflow_chain_list b/bt5/erp5_webrtc_gadget/bt/template_portal_type_workflow_chain_list new file mode 100644 index 00000000000..40fa6081d28 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_portal_type_workflow_chain_list @@ -0,0 +1 @@ +Webrtc Room | edit_workflow \ No newline at end of file diff --git a/bt5/erp5_webrtc_gadget/bt/template_skin_id_list b/bt5/erp5_webrtc_gadget/bt/template_skin_id_list new file mode 100644 index 00000000000..bb7995ebc6e --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_skin_id_list @@ -0,0 +1 @@ +erp5_webrtc_room \ No newline at end of file -- 2.30.9 From 5f6e57a83142829d9846543b4c051e062fdeb1e5 Mon Sep 17 00:00:00 2001 From: Hardik Juneja Date: Wed, 12 Oct 2016 15:37:39 +0000 Subject: [PATCH 4/5] erp5_webrtc_gadget: update webrtc module to support erp5 as a handshake option --- .../gadget_webrtc_handshake_js.js | 80 +++++++++++++++++-- .../gadget_webrtc_handshake_js.xml | 4 +- .../gadget_webrtc_jio_bridge_js.js | 2 +- .../gadget_webrtc_jio_bridge_js.xml | 4 +- 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js index 8725a7a66d1..65cc8603897 100644 --- a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.js @@ -45,14 +45,22 @@ g.props = {}; }) - .declareMethod('create_room', function(roomid) { + .declareMethod('create_room', function(roomid, type) { var gadget = this; return new RSVP.Queue() .push(function () { return gadget.getDeclaredGadget("jio_gadget"); }) .push(function (jio_gadget) { - return jio_gadget.put(roomid, {}); + if (type === "erp5") { + return jio_gadget.post({ + title: roomid, + portal_type: "Webrtc Room", + parent_relative_url: "webrtc_rooms_module" + }); + } else { + return jio_gadget.put(roomid, {}); + } }) .push(null, function(e){ console.log(e); @@ -60,19 +68,29 @@ }) .declareMethod('register', function(roomid, peerid, config) { - var gadget = this; - + var gadget = this, + id; + gadget.props.config = config; return new RSVP.Queue() .push(function() { return createJio(gadget, config); }) .push(function() { - return gadget.create_room(roomid); + return gadget.create_room(roomid, config.type); }) - .push(function () { + .push(function (id) { + gadget.erp5_roomid = id; return gadget.getDeclaredGadget("jio_gadget"); }) .push(function (jio_gadget) { + if (config.type === "erp5") { + var url = config.url + "/" + gadget.erp5_roomid + "/WebrtcRoom_storeOfferAnswer"; + return jio_gadget.putAttachment(gadget.erp5_roomid, + url, + new Blob([JSON.stringify({roomid:roomid, + peerid: peerid, + data:'1'})])); + } return jio_gadget.putAttachment(roomid, peerid, ''); }); // TODO: create new function, show registered peers? @@ -86,8 +104,27 @@ return gadget.getDeclaredGadget("jio_gadget"); }) .push(function (jio_gadget) { + if (gadget.props.config.type === "erp5") { + var url = gadget.props.config.url + "/" + gadget.erp5_roomid + + "/WebrtcRoom_storeOfferAnswer"; + return new RSVP.Queue() + .push(function () { + if (options.data instanceof Blob) { + return jIO.util.readBlobAsText(options.data); + } else { + return {target:{result:options.data}}; + } + }) + .push(function (evt) { + return jio_gadget.putAttachment(gadget.erp5_roomid, + url, + new Blob([JSON.stringify({roomid:roomid, + peerid: options.name, + data: evt.target.result})])); + }); + } return jio_gadget.putAttachment(roomid, options.name, options.data); - }) + }); }) .declareMethod('get_answer', function (roomid, attachment) { @@ -98,6 +135,11 @@ return gadget.getDeclaredGadget("jio_gadget"); }) .push(function (jio_gadget) { + if (gadget.props.config.type === "erp5") { + var url = gadget.props.config.url + "/" + gadget.erp5_roomid + + "/WebrtcRoom_getOfferAnswer?roomid=" + roomid+"&name="+attachment; + return jio_gadget.getAttachment(gadget.erp5_roomid, url); + } return jio_gadget.getAttachment(roomid, attachment); }); }) @@ -110,6 +152,23 @@ return gadget.getDeclaredGadget("jio_gadget"); }) .push(function (jio_gadget) { + if (gadget.props.config.type === "erp5") { + var url = gadget.props.config.url + "/" + gadget.erp5_roomid + + "/WebrtcRoom_getAllOfferAnswer?roomid="+roomid; + return jio_gadget.getAttachment(gadget.erp5_roomid, url) + .push(function(res) { + return jIO.util.readBlobAsText(res); + }) + .push(function(evt) { + var res = evt.target.result.replace(/'/g, '"'); + res = JSON.parse(res); + var offers = {} + for (var i in res) { + offers[res[i]] = ''; + } + return offers; + }); + } return jio_gadget.allAttachments(roomid); }); }) @@ -168,7 +227,7 @@ } else { offers = a; return false; - } + } }); }); }; @@ -184,6 +243,11 @@ return gadget.getDeclaredGadget("jio_gadget"); }) .push(function (jio_gadget) { + if (gadget.props.config.type === "erp5") { + var url = gadget.props.config.url + "/" + gadget.erp5_roomid + + "/WebrtcRoom_deleteOfferAnswer?roomid="+roomid+"&name="+peerid+"_"+to; + return jio_gadget.getAttachment(gadget.erp5_roomid, url); + } return jio_gadget.removeAttachment(roomid, peerid+"_"+to); }); }); diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml index 12f2ac29ef8..8327e60bda2 100644 --- a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_handshake_js.xml @@ -222,7 +222,7 @@ serial - 954.14301.62370.56661 + 954.35891.65212.64409 state @@ -240,7 +240,7 @@ - 1475066759.35 + 1476275774.45 UTC diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js index 0243e08b2d9..598bef535c3 100644 --- a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.js @@ -258,7 +258,7 @@ // TODO: have a array for channel_defer for concurency? gadget.props.channel_defer = RSVP.defer(); for (var offer in offers) { - jsonOffer = JSON.parse(offers[offer]); + jsonOffer = JSON.parse(offers[offer]); if (jsonOffer.action === "offer") { gadget.props.counter += 1; connections.push(connectCallback(jsonOffer, handler_gadget)); diff --git a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml index 51ffd1bd29d..261cd9057fe 100644 --- a/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml +++ b/bt5/erp5_webrtc_gadget/PathTemplateItem/web_page_module/gadget_webrtc_jio_bridge_js.xml @@ -222,7 +222,7 @@ serial - 954.14300.3742.31044 + 954.35841.9537.7133 state @@ -240,7 +240,7 @@ - 1475066648.69 + 1476272709.15 UTC -- 2.30.9 From 41044d9b830ae4b90a6606649dc3b1914a67aa9a Mon Sep 17 00:00:00 2001 From: Hardik Juneja Date: Wed, 12 Oct 2016 15:58:42 +0000 Subject: [PATCH 5/5] erp5_webrtc_gadget: Add tests for scripts created for Memcached with ERP5 --- .../test.erp5.testWebrtcRooms.py | 160 ++++++++++++++++++ .../test.erp5.testWebrtcRooms.xml | 121 +++++++++++++ .../bt/template_extension_id_list | 1 + 3 files changed, 282 insertions(+) create mode 100644 bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.py create mode 100644 bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.xml create mode 100644 bt5/erp5_webrtc_gadget/bt/template_extension_id_list diff --git a/bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.py b/bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.py new file mode 100644 index 00000000000..afb8ae9fba2 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.py @@ -0,0 +1,160 @@ +############################################################################## +# +# Copyright (c) 2002-2016 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +############################################################################## + +from Products.ERP5Type.tests.SecurityTestCase import SecurityTestCase +#from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase + +class TestWebrtcRooms(SecurityTestCase): + """ + Test Class to test webrtc rooms + """ + + def getTitle(self): + return "SampleTest" + + def afterSetUp(self): + """ + This is ran before anything, used to set the environment + """ + user_folder = self.getPortal().acl_users + + user_folder._doAddUser('anon_user', '', ['Anonymous',], []) + + #spy = dict(title='Anon User', reference='anon_user', function=None) + #self.createSimpleUser(**spy) + + self.commit() + self.tic() + self.login('anon_user') + self.webrtc_room_module = self.portal.getDefaultModule(portal_type='Webrtc Rooms Module') + self.assertTrue(self.webrtc_room_module is not None) + + def _newRoom(self, title='test room'): + """ + Function to create new room + """ + return self.webrtc_room_module.WebrtcRoom_addWebrtcRoom( + title=title, + form_id='WebrtcRoomsModule_viewWebrtcRoomList', + batch_mode=True + ) + + def _setOffer(self, roomid, peerid, data): + """ + Function to create new offer + """ + return self.webrtc_room_module.WebrtcRoom_storeOfferAnswer( + roomid=roomid, + peerid=peerid, + data=data, + ) + + def _getOffer(self, roomid, name): + """ + Function to get offer + """ + return self.webrtc_room_module.WebrtcRoom_getOfferAnswer( + roomid=roomid, + name=name + ) + + def _getAllOffer(self, roomid): + """ + Function to get all offers in room + """ + return self.webrtc_room_module.WebrtcRoom_getAllOfferAnswer( + roomid=roomid, + ) + + def _deleteOffer(self, roomid, name): + """ + Function to delete offer + """ + return self.webrtc_room_module.WebrtcRoom_deleteOfferAnswer( + roomid=roomid, + name=name + ) + + def test_00_AnonymousUserCanCreateRoom(self): + self.assertUserCanAccessDocument('anon_user', self.webrtc_room_module) + self.assertUserCanViewDocument('anon_user', self.webrtc_room_module) + self.assertUserCanAddDocument('anon_user', self.webrtc_room_module) + + self.login('anon_user') + + room = self._newRoom(title='test_room0') + self.tic() + + self.assertUserCanAccessDocument('anon_user', room) + self.assertUserCanViewDocument('anon_user', room) + self.assertUserCanAddDocument('anon_user', room) + + self.tic() + + def test_01_AnonymousUserCanAddAndGetOffer(self): + self.login('anon_user') + data = "{'type':'offer', 'data':'sdp:'v=0\r\n o=- 2708361213080913936 2 IN IP4 127.0.0.1\r\n\'}" + roomid = 'test_room1' + self._newRoom(title=roomid) + setResponse = self._setOffer(roomid, 'master', data) + self.assertEqual('ok', setResponse.body) + self.tic() + + getResponse = self._getOffer(roomid, 'master') + self.assertEqual(data, getResponse) + self._deleteOffer(roomid, 'master') + self.tic() + + def test_02_AnonymousUserCanGetAllOffer(self): + self.login('anon_user') + data = "{'type':'offer', 'data':'sdp:'v=0\r\n o=- 2708361213080913936 2 IN IP4 127.0.0.1\r\n\'}" + roomid = 'test_room2' + self._newRoom(title=roomid) + self._setOffer(roomid, 'slave1', data) + self._setOffer(roomid, 'slave2', data) + + getResponse = self._getAllOffer(roomid) + self.assertEqual(['slave1', 'slave2'], getResponse) + self._deleteOffer(roomid, 'slave1') + self._deleteOffer(roomid, 'slave2') + self.tic() + + def test_04_AnonymousUserCanDeleteOffer(self): + self.login('anon_user') + data = "{'type':'offer', 'data':'sdp:'v=0\r\n o=- 2708361213080913936 2 IN IP4 127.0.0.1\r\n\'}" + roomid = 'test_room3' + self._newRoom(title=roomid) + self._setOffer(roomid, 'slave1', data) + self._deleteOffer(roomid, 'slave1') + try: + self._getOffer(roomid, 'slave1') + except KeyError: + pass + else: + self.fail('No error was raised when getting a deleted key.') + + diff --git a/bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.xml b/bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.xml new file mode 100644 index 00000000000..b8248f90510 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/ExtensionTemplateItem/portal_components/test.erp5.testWebrtcRooms.xml @@ -0,0 +1,121 @@ + + + + + + + + + + _recorded_property_dict + + AAAAAAAAAAI= + + + + default_reference + testWebrtcRooms + + + description + Unit test for Webrtc Rooms Module + + + id + test.erp5.testWebrtcRooms + + + portal_type + Test Component + + + sid + + + + + + text_content_error_message + + + + + + text_content_warning_message + + + + + + version + erp5 + + + workflow_history + + AAAAAAAAAAM= + + + + + + + + + + + + + data + + + + + + + + + + + + + + + data + + + + component_validation_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + validate + + + validation_state + validated + + + + + + + diff --git a/bt5/erp5_webrtc_gadget/bt/template_extension_id_list b/bt5/erp5_webrtc_gadget/bt/template_extension_id_list new file mode 100644 index 00000000000..2aab8144949 --- /dev/null +++ b/bt5/erp5_webrtc_gadget/bt/template_extension_id_list @@ -0,0 +1 @@ +portal_components/test.erp5.testWebrtcRooms \ No newline at end of file -- 2.30.9