Bug 1232707 - Import the latest version of the Loop system add-on. r=me for import of already reviewed code.
This commit is contained in:
57
browser/extensions/loop/bootstrap.js
vendored
57
browser/extensions/loop/bootstrap.js
vendored
@@ -3,6 +3,8 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported startup, shutdown, install, uninstall */
|
||||||
|
|
||||||
const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
|
const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
|
||||||
|
|
||||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
@@ -426,7 +428,7 @@ var WindowListener = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let notification = new window.Notification(options.title, notificationOptions);
|
let notification = new window.Notification(options.title, notificationOptions);
|
||||||
notification.addEventListener("click", e => {
|
notification.addEventListener("click", () => {
|
||||||
if (window.closed) {
|
if (window.closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -483,6 +485,7 @@ var WindowListener = {
|
|||||||
// Watch for title changes as opposed to location changes as more
|
// Watch for title changes as opposed to location changes as more
|
||||||
// metadata about the page is available when this event fires.
|
// metadata about the page is available when this event fires.
|
||||||
gBrowser.addEventListener("DOMTitleChanged", this);
|
gBrowser.addEventListener("DOMTitleChanged", this);
|
||||||
|
this._browserSharePaused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._maybeShowBrowserSharingInfoBar();
|
this._maybeShowBrowserSharingInfoBar();
|
||||||
@@ -504,6 +507,7 @@ var WindowListener = {
|
|||||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||||
gBrowser.removeEventListener("DOMTitleChanged", this);
|
gBrowser.removeEventListener("DOMTitleChanged", this);
|
||||||
this._listeningToTabSelect = false;
|
this._listeningToTabSelect = false;
|
||||||
|
this._browserSharePaused = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -535,26 +539,37 @@ var WindowListener = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let box = gBrowser.getNotificationBox();
|
let box = gBrowser.getNotificationBox();
|
||||||
let paused = false;
|
let pauseButtonLabel = this._getString(this._browserSharePaused ?
|
||||||
|
"infobar_button_resume_label" :
|
||||||
|
"infobar_button_pause_label");
|
||||||
|
let pauseButtonAccessKey = this._getString(this._browserSharePaused ?
|
||||||
|
"infobar_button_resume_accesskey" :
|
||||||
|
"infobar_button_pause_accesskey");
|
||||||
|
let barLabel = this._getString(this._browserSharePaused ?
|
||||||
|
"infobar_screenshare_paused_browser_message" :
|
||||||
|
"infobar_screenshare_browser_message2");
|
||||||
let bar = box.appendNotification(
|
let bar = box.appendNotification(
|
||||||
this._getString("infobar_screenshare_browser_message2"),
|
barLabel,
|
||||||
kBrowserSharingNotificationId,
|
kBrowserSharingNotificationId,
|
||||||
// Icon is defined in browser theme CSS.
|
// Icon is defined in browser theme CSS.
|
||||||
null,
|
null,
|
||||||
box.PRIORITY_WARNING_LOW,
|
box.PRIORITY_WARNING_LOW,
|
||||||
[{
|
[{
|
||||||
label: this._getString("infobar_button_pause_label"),
|
label: pauseButtonLabel,
|
||||||
accessKey: this._getString("infobar_button_pause_accesskey"),
|
accessKey: pauseButtonAccessKey,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
callback: (event, buttonInfo, buttonNode) => {
|
callback: (event, buttonInfo, buttonNode) => {
|
||||||
paused = !paused;
|
this._browserSharePaused = !this._browserSharePaused;
|
||||||
bar.label = paused ? this._getString("infobar_screenshare_paused_browser_message") :
|
bar.label = this._getString(this._browserSharePaused ?
|
||||||
this._getString("infobar_screenshare_browser_message2");
|
"infobar_screenshare_paused_browser_message" :
|
||||||
bar.classList.toggle("paused", paused);
|
"infobar_screenshare_browser_message2");
|
||||||
buttonNode.label = paused ? this._getString("infobar_button_resume_label") :
|
bar.classList.toggle("paused", this._browserSharePaused);
|
||||||
this._getString("infobar_button_pause_label");
|
buttonNode.label = this._getString(this._browserSharePaused ?
|
||||||
buttonNode.accessKey = paused ? this._getString("infobar_button_resume_accesskey") :
|
"infobar_button_resume_label" :
|
||||||
this._getString("infobar_button_pause_accesskey");
|
"infobar_button_pause_label");
|
||||||
|
buttonNode.accessKey = this._getString(this._browserSharePaused ?
|
||||||
|
"infobar_button_resume_accesskey" :
|
||||||
|
"infobar_button_pause_accesskey");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
type: "pause"
|
type: "pause"
|
||||||
@@ -571,6 +586,9 @@ var WindowListener = {
|
|||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Sets 'paused' class if needed.
|
||||||
|
bar.classList.toggle("paused", !!this._browserSharePaused);
|
||||||
|
|
||||||
// Keep showing the notification bar until the user explicitly closes it.
|
// Keep showing the notification bar until the user explicitly closes it.
|
||||||
bar.persistence = -1;
|
bar.persistence = -1;
|
||||||
},
|
},
|
||||||
@@ -612,7 +630,7 @@ var WindowListener = {
|
|||||||
* Handles events from gBrowser.
|
* Handles events from gBrowser.
|
||||||
*/
|
*/
|
||||||
handleEvent: function(event) {
|
handleEvent: function(event) {
|
||||||
switch(event.type) {
|
switch (event.type) {
|
||||||
case "DOMTitleChanged":
|
case "DOMTitleChanged":
|
||||||
// Get the new title of the shared tab
|
// Get the new title of the shared tab
|
||||||
this._notifyBrowserSwitch();
|
this._notifyBrowserSwitch();
|
||||||
@@ -689,15 +707,10 @@ var WindowListener = {
|
|||||||
window.LoopUI = LoopUI;
|
window.LoopUI = LoopUI;
|
||||||
},
|
},
|
||||||
|
|
||||||
tearDownBrowserUI: function(window) {
|
tearDownBrowserUI: function() {
|
||||||
let document = window.document;
|
|
||||||
|
|
||||||
// Take any steps to remove UI or anything from the browser window
|
// Take any steps to remove UI or anything from the browser window
|
||||||
// document.getElementById() etc. will work here
|
// document.getElementById() etc. will work here
|
||||||
if (window.LoopUI) {
|
|
||||||
window.LoopUI.removeMenuItem();
|
|
||||||
// XXX Add in tear-down of the panel.
|
// XXX Add in tear-down of the panel.
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// nsIWindowMediatorListener functions.
|
// nsIWindowMediatorListener functions.
|
||||||
@@ -717,10 +730,10 @@ var WindowListener = {
|
|||||||
}, false);
|
}, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
onCloseWindow: function(xulWindow) {
|
onCloseWindow: function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
onWindowTitleChange: function(xulWindow, newTitle) {
|
onWindowTitleChange: function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
const { utils: Cu } = Components;
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
@@ -35,6 +35,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto",
|
|||||||
XPCOMUtils.defineLazyModuleGetter(this, "ObjectUtils",
|
XPCOMUtils.defineLazyModuleGetter(this, "ObjectUtils",
|
||||||
"resource://gre/modules/ObjectUtils.jsm");
|
"resource://gre/modules/ObjectUtils.jsm");
|
||||||
|
|
||||||
|
/* exported LoopRooms, roomsPushNotification */
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"];
|
this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"];
|
||||||
|
|
||||||
@@ -737,7 +738,7 @@ var LoopRoomsInternal = {
|
|||||||
let room = this.rooms.get(roomToken);
|
let room = this.rooms.get(roomToken);
|
||||||
let url = "/rooms/" + encodeURIComponent(roomToken);
|
let url = "/rooms/" + encodeURIComponent(roomToken);
|
||||||
MozLoopService.hawkRequest(this.sessionType, url, "DELETE")
|
MozLoopService.hawkRequest(this.sessionType, url, "DELETE")
|
||||||
.then(response => {
|
.then(() => {
|
||||||
this.rooms.delete(roomToken);
|
this.rooms.delete(roomToken);
|
||||||
eventEmitter.emit("delete", room);
|
eventEmitter.emit("delete", room);
|
||||||
eventEmitter.emit("delete:" + room.roomToken, room);
|
eventEmitter.emit("delete:" + room.roomToken, room);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
const { utils: Cu } = Components;
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
|||||||
@@ -394,7 +394,9 @@ const kMessageHandlers = {
|
|||||||
version: appInfo.version,
|
version: appInfo.version,
|
||||||
OS: appInfo.OS
|
OS: appInfo.OS
|
||||||
};
|
};
|
||||||
} catch (ex) {}
|
} catch (ex) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reply(gAppVersionInfo);
|
reply(gAppVersionInfo);
|
||||||
},
|
},
|
||||||
@@ -415,7 +417,7 @@ const kMessageHandlers = {
|
|||||||
let name = message.data[0];
|
let name = message.data[0];
|
||||||
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||||
.createInstance(Ci.nsIXMLHttpRequest);
|
.createInstance(Ci.nsIXMLHttpRequest);
|
||||||
let url = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
|
let url = `chrome://loop/content/shared/sounds/${name}.ogg`;
|
||||||
|
|
||||||
request.open("GET", url, true);
|
request.open("GET", url, true);
|
||||||
request.responseType = "arraybuffer";
|
request.responseType = "arraybuffer";
|
||||||
@@ -425,7 +427,7 @@ const kMessageHandlers = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let blob = new Blob([request.response], {type: "audio/ogg"});
|
let blob = new Blob([request.response], { type: "audio/ogg" });
|
||||||
reply(blob);
|
reply(blob);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -592,7 +594,7 @@ const kMessageHandlers = {
|
|||||||
win.messageManager.removeMessageListener("PageMetadata:PageDataResult", onPageDataResult);
|
win.messageManager.removeMessageListener("PageMetadata:PageDataResult", onPageDataResult);
|
||||||
let pageData = msg.json;
|
let pageData = msg.json;
|
||||||
win.LoopUI.getFavicon(function(err, favicon) {
|
win.LoopUI.getFavicon(function(err, favicon) {
|
||||||
if (err) {
|
if (err && err !== "favicon not found for uri") {
|
||||||
MozLoopService.log.error("Error occurred whilst fetching favicon", err);
|
MozLoopService.log.error("Error occurred whilst fetching favicon", err);
|
||||||
// We don't return here intentionally to make sure the callback is
|
// We don't return here intentionally to make sure the callback is
|
||||||
// invoked at all times. We just report the error here.
|
// invoked at all times. We just report the error here.
|
||||||
@@ -624,49 +626,6 @@ const kMessageHandlers = {
|
|||||||
reply(gSocialProviders);
|
reply(gSocialProviders);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Compose a URL pointing to the location of an avatar by email address.
|
|
||||||
* At the moment we use the Gravatar service to match email addresses with
|
|
||||||
* avatars. If no email address is found we return null.
|
|
||||||
*
|
|
||||||
* @param {Object} message Message meant for the handler function, containing
|
|
||||||
* the following parameters in its `data` property:
|
|
||||||
* [
|
|
||||||
* {String} emailAddress Users' email address
|
|
||||||
* {Number} size Size of the avatar image
|
|
||||||
* to return in pixels. Optional.
|
|
||||||
* Default value: 40.
|
|
||||||
* ]
|
|
||||||
* @param {Function} reply Callback function, invoked with the result of this
|
|
||||||
* message handler. The result will be sent back to
|
|
||||||
* the senders' channel.
|
|
||||||
* @return the URL pointing to an avatar matching the provided email address
|
|
||||||
* or null if this is not available.
|
|
||||||
*/
|
|
||||||
GetUserAvatar: function(message, reply) {
|
|
||||||
let [emailAddress, size] = message.data;
|
|
||||||
if (!emailAddress || !MozLoopService.getLoopPref("contacts.gravatars.show")) {
|
|
||||||
reply(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the MD5 dance.
|
|
||||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
|
||||||
.createInstance(Ci.nsICryptoHash);
|
|
||||||
hasher.init(Ci.nsICryptoHash.MD5);
|
|
||||||
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
|
|
||||||
.createInstance(Ci.nsIStringInputStream);
|
|
||||||
stringStream.data = emailAddress.trim().toLowerCase();
|
|
||||||
hasher.updateFromStream(stringStream, -1);
|
|
||||||
let hash = hasher.finish(false);
|
|
||||||
// Convert the binary hash data to a hex string.
|
|
||||||
let md5Email = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
|
|
||||||
|
|
||||||
// Compose the Gravatar URL.
|
|
||||||
reply("https://www.gravatar.com/avatar/" + md5Email +
|
|
||||||
".jpg?default=blank&s=" + (size || 40));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an object with data that represents the currently
|
* Gets an object with data that represents the currently
|
||||||
* authenticated user's identity.
|
* authenticated user's identity.
|
||||||
@@ -1043,58 +1002,6 @@ const kMessageHandlers = {
|
|||||||
reply();
|
reply();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts alerting the user about an incoming call
|
|
||||||
*
|
|
||||||
* @param {Object} message Message meant for the handler function, containing
|
|
||||||
* the following parameters in its `data` property:
|
|
||||||
* [ ]
|
|
||||||
* @param {Function} reply Callback function, invoked with the result of this
|
|
||||||
* message handler. The result will be sent back to
|
|
||||||
* the senders' channel.
|
|
||||||
*/
|
|
||||||
StartAlerting: function(message, reply) {
|
|
||||||
let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
|
||||||
chromeWindow.getAttention();
|
|
||||||
ringer = new chromeWindow.Audio();
|
|
||||||
ringer.src = Services.prefs.getCharPref("loop.ringtone");
|
|
||||||
ringer.loop = true;
|
|
||||||
ringer.load();
|
|
||||||
ringer.play();
|
|
||||||
targetWindow.document.addEventListener("visibilitychange",
|
|
||||||
ringerStopper = function(event) {
|
|
||||||
if (event.currentTarget.hidden) {
|
|
||||||
kMessageHandlers.StopAlerting();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
reply();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops alerting the user about an incoming call
|
|
||||||
*
|
|
||||||
* @param {Object} message Message meant for the handler function, containing
|
|
||||||
* the following parameters in its `data` property:
|
|
||||||
* [ ]
|
|
||||||
* @param {Function} reply Callback function, invoked with the result of this
|
|
||||||
* message handler. The result will be sent back to
|
|
||||||
* the senders' channel.
|
|
||||||
*/
|
|
||||||
StopAlerting: function(message, reply) {
|
|
||||||
if (!ringer) {
|
|
||||||
reply();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ringerStopper) {
|
|
||||||
ringer.ownerDocument.removeEventListener("visibilitychange",
|
|
||||||
ringerStopper);
|
|
||||||
ringerStopper = null;
|
|
||||||
}
|
|
||||||
ringer.pause();
|
|
||||||
ringer = null;
|
|
||||||
reply();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a value to a telemetry histogram.
|
* Adds a value to a telemetry histogram.
|
||||||
*
|
*
|
||||||
@@ -1167,7 +1074,7 @@ const LoopAPIInternal = {
|
|||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
MozLoopService.log.error("Failed to send reply back to content:", ex);
|
MozLoopService.log.error("Failed to send reply back to content:", ex);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, check if this is a batch call.
|
// First, check if this is a batch call.
|
||||||
@@ -1289,7 +1196,7 @@ const LoopAPIInternal = {
|
|||||||
try {
|
try {
|
||||||
message.target.sendAsyncMessage(kPushMessageName, [pushMessagePrefix +
|
message.target.sendAsyncMessage(kPushMessageName, [pushMessagePrefix +
|
||||||
prettyEventName, data]);
|
prettyEventName, data]);
|
||||||
} catch(ex) {
|
} catch (ex) {
|
||||||
MozLoopService.log.debug("Unable to send event through to target: " +
|
MozLoopService.log.debug("Unable to send event through to target: " +
|
||||||
ex.message);
|
ex.message);
|
||||||
// Unregister event handlers when the message port is unreachable.
|
// Unregister event handlers when the message port is unreachable.
|
||||||
@@ -1335,9 +1242,12 @@ const LoopAPIInternal = {
|
|||||||
for (let page of gPageListeners) {
|
for (let page of gPageListeners) {
|
||||||
try {
|
try {
|
||||||
page.sendAsyncMessage(kPushMessageName, [name, data]);
|
page.sendAsyncMessage(kPushMessageName, [name, data]);
|
||||||
} catch (ex if ex.result == Components.results.NS_ERROR_NOT_INITIALIZED) {
|
} catch (ex) {
|
||||||
// Don't make noise when the Remote Page Manager needs more time to
|
// Only make noise when the Remote Page Manager needs more time to
|
||||||
// initialize.
|
// initialize.
|
||||||
|
if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1349,7 +1259,9 @@ const LoopAPIInternal = {
|
|||||||
if (!gPageListeners) {
|
if (!gPageListeners) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[for (listener of gPageListeners) listener.destroy()];
|
for (let listener of gPageListeners) {
|
||||||
|
listener.destroy();
|
||||||
|
}
|
||||||
gPageListeners = null;
|
gPageListeners = null;
|
||||||
|
|
||||||
// Unsubscribe from global events.
|
// Unsubscribe from global events.
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/Timer.jsm");
|
|||||||
const { MozLoopService } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
|
const { MozLoopService } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
|
||||||
const consoleLog = MozLoopService.log;
|
const consoleLog = MozLoopService.log;
|
||||||
|
|
||||||
|
/* exported MozLoopPushHandler */
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["MozLoopPushHandler"];
|
this.EXPORTED_SYMBOLS = ["MozLoopPushHandler"];
|
||||||
|
|
||||||
const CONNECTION_STATE_CLOSED = 0;
|
const CONNECTION_STATE_CLOSED = 0;
|
||||||
@@ -556,7 +558,7 @@ var MozLoopPushHandler = {
|
|||||||
* This method will continually try to re-establish a connection
|
* This method will continually try to re-establish a connection
|
||||||
* to the PushServer unless shutdown has been called.
|
* to the PushServer unless shutdown has been called.
|
||||||
*/
|
*/
|
||||||
_onClose: function(aCode, aReason) {
|
_onClose: function(aCode) {
|
||||||
this._pingMonitor.stop();
|
this._pingMonitor.stop();
|
||||||
|
|
||||||
switch (this.connectionState) {
|
switch (this.connectionState) {
|
||||||
|
|||||||
@@ -4,11 +4,7 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
const { interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||||
|
|
||||||
// Invalid auth token as per
|
|
||||||
// https://github.com/mozilla-services/loop-server/blob/45787d34108e2f0d87d74d4ddf4ff0dbab23501c/loop/errno.json#L6
|
|
||||||
const INVALID_AUTH_TOKEN = 110;
|
|
||||||
|
|
||||||
const LOOP_SESSION_TYPE = {
|
const LOOP_SESSION_TYPE = {
|
||||||
GUEST: 1,
|
GUEST: 1,
|
||||||
@@ -201,7 +197,6 @@ var gFxAEnabled = true;
|
|||||||
var gFxAOAuthClientPromise = null;
|
var gFxAOAuthClientPromise = null;
|
||||||
var gFxAOAuthClient = null;
|
var gFxAOAuthClient = null;
|
||||||
var gErrors = new Map();
|
var gErrors = new Map();
|
||||||
var gLastWindowId = 0;
|
|
||||||
var gConversationWindowData = new Map();
|
var gConversationWindowData = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1676,8 +1671,6 @@ this.MozLoopService = {
|
|||||||
return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
|
return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
|
||||||
}).then(tokenData => {
|
}).then(tokenData => {
|
||||||
MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
|
MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
|
||||||
return tokenData;
|
|
||||||
}).then(tokenData => {
|
|
||||||
return MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
|
return MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
|
||||||
MozLoopServiceInternal.clearError("login");
|
MozLoopServiceInternal.clearError("login");
|
||||||
MozLoopServiceInternal.clearError("profile");
|
MozLoopServiceInternal.clearError("profile");
|
||||||
@@ -1768,6 +1761,13 @@ this.MozLoopService = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let url = new URL("/settings", fxAOAuthClient.parameters.content_uri);
|
let url = new URL("/settings", fxAOAuthClient.parameters.content_uri);
|
||||||
|
|
||||||
|
if (this.userProfile) {
|
||||||
|
// fxA User profile is present, open settings for the correct profile. Bug: 1070208
|
||||||
|
let fxAProfileUid = MozLoopService.userProfile.uid;
|
||||||
|
url = new URL("/settings?uid=" + fxAProfileUid, fxAOAuthClient.parameters.content_uri);
|
||||||
|
}
|
||||||
|
|
||||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
win.switchToTabHavingURI(url.toString(), true);
|
win.switchToTabHavingURI(url.toString(), true);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -1865,7 +1865,7 @@ this.MozLoopService = {
|
|||||||
*/
|
*/
|
||||||
openURL: function(url) {
|
openURL: function(url) {
|
||||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
win.openUILinkIn(url, "tab");
|
win.openUILinkIn(Services.urlFormatter.formatURL(url), "tab");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
importScripts("resource://gre/modules/osfile.jsm");
|
importScripts("resource://gre/modules/osfile.jsm");
|
||||||
|
|
||||||
var File = OS.File;
|
|
||||||
var Encoder = new TextEncoder();
|
var Encoder = new TextEncoder();
|
||||||
var Counter = 0;
|
var Counter = 0;
|
||||||
|
|
||||||
@@ -49,13 +48,20 @@ onmessage = function(e) {
|
|||||||
// Save to disk
|
// Save to disk
|
||||||
let array = Encoder.encode(pingStr);
|
let array = Encoder.encode(pingStr);
|
||||||
try {
|
try {
|
||||||
File.makeDir(directory,
|
OS.File.makeDir(directory, {
|
||||||
{ unixMode: OS.Constants.S_IRWXU, ignoreExisting: true });
|
unixMode: OS.Constants.S_IRWXU,
|
||||||
File.writeAtomic(OS.Path.join(directory, filename), array);
|
ignoreExisting: true
|
||||||
|
});
|
||||||
|
OS.File.writeAtomic(OS.Path.join(directory, filename), array);
|
||||||
postMessage({ ok: true });
|
postMessage({ ok: true });
|
||||||
} catch (ex if ex instanceof File.Error) {
|
} catch (ex) {
|
||||||
// Instances of OS.File.Error know how to serialize themselves
|
// Instances of OS.File.Error know how to serialize themselves
|
||||||
postMessage({fail: File.Error.toMsg(ex)});
|
if (ex instanceof OS.File.Error) {
|
||||||
|
postMessage({ fail: OS.File.Error.toMsg(ex) });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
||||||
<script type="text/javascript" src="panels/js/otconfig.js"></script>
|
<script type="text/javascript" src="panels/js/otconfig.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/sdk.js"></script>
|
<script type="text/javascript" src="shared/vendor/sdk.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/react-0.13.3.js"></script>
|
<script type="text/javascript" src="shared/vendor/react.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/lodash-3.9.3.js"></script>
|
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/backbone-1.2.1.js"></script>
|
<script type="text/javascript" src="shared/vendor/backbone.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/classnames-2.2.0.js"></script>
|
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||||
|
|||||||
@@ -147,35 +147,6 @@ body {
|
|||||||
|
|
||||||
/* Rooms CSS */
|
/* Rooms CSS */
|
||||||
|
|
||||||
.no-conversations-message {
|
|
||||||
/* example of vertical aligning a container in an element see:
|
|
||||||
http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/ */
|
|
||||||
text-align: center;
|
|
||||||
color: #4a4a4a;
|
|
||||||
font-weight: lighter;
|
|
||||||
position: relative;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
padding-top: 11rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
background-image: url("../../shared/img/empty_conversations.svg");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: top center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-text-medium,
|
|
||||||
.panel-text-large {
|
|
||||||
margin: 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-text-medium {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-text-large {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-list-loading {
|
.room-list-loading {
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -203,18 +174,20 @@ body {
|
|||||||
.rooms > h1 {
|
.rooms > h1 {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: .5rem 0;
|
padding: .5rem 15px;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
line-height: 3rem;
|
line-height: 3rem;
|
||||||
margin: 0 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-room-view {
|
.new-room-view {
|
||||||
border-bottom: 1px solid #d8d8d8;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.new-room-view + h1 {
|
||||||
|
border-top: 1px solid #d8d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
.new-room-view > .btn {
|
.new-room-view > .btn {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
@@ -245,15 +218,6 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-list-empty {
|
|
||||||
border-bottom-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
/* the child no-conversations-message is vertical aligned inside this container
|
|
||||||
see: http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/
|
|
||||||
stops blurring from decimal pixels being rendered - pixel rounding */
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-list > .room-entry {
|
.room-list > .room-entry {
|
||||||
padding: .2rem 15px;
|
padding: .2rem 15px;
|
||||||
/* Always show the default pointer, even over the text part of the entry. */
|
/* Always show the default pointer, even over the text part of the entry. */
|
||||||
@@ -569,9 +533,8 @@ html[dir="rtl"] .generate-url-spinner {
|
|||||||
/* Sign in/up link */
|
/* Sign in/up link */
|
||||||
|
|
||||||
.signin-link {
|
.signin-link {
|
||||||
flex: 2 1 auto;
|
flex: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.signin-link > a {
|
.signin-link > a {
|
||||||
@@ -597,15 +560,6 @@ html[dir="rtl"] .generate-url-spinner {
|
|||||||
-moz-margin-start: .5em;
|
-moz-margin-start: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-details .dropdown-menu {
|
|
||||||
bottom: 1.3rem; /* Just above the text. */
|
|
||||||
left: -5px; /* Compensate for button padding. */
|
|
||||||
}
|
|
||||||
|
|
||||||
html[dir="rtl"] .user-details .dropdown-menu {
|
|
||||||
right: -5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-menu .dropdown-menu {
|
.settings-menu .dropdown-menu {
|
||||||
/* The panel can't have dropdown menu overflowing its iframe boudaries;
|
/* The panel can't have dropdown menu overflowing its iframe boudaries;
|
||||||
let's anchor it from the bottom-right, while resetting the top & left values
|
let's anchor it from the bottom-right, while resetting the top & left values
|
||||||
@@ -646,15 +600,18 @@ html[dir="rtl"] .settings-menu .dropdown-menu {
|
|||||||
height: 42px;
|
height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer .signin-details {
|
.footer > .signin-details {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer .user-identity {
|
.footer > .user-identity {
|
||||||
|
flex: 1;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-inline-end: 1rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* First time use */
|
/* First time use */
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.conversation = (function(mozL10n) {
|
loop.conversation = function (mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedMixins = loop.shared.mixins;
|
var sharedMixins = loop.shared.mixins;
|
||||||
@@ -18,41 +18,37 @@ loop.conversation = (function(mozL10n) {
|
|||||||
* Master controller view for handling if incoming or outgoing calls are
|
* Master controller view for handling if incoming or outgoing calls are
|
||||||
* in progress, and hence, which view to display.
|
* in progress, and hence, which view to display.
|
||||||
*/
|
*/
|
||||||
var AppControllerView = React.createClass({displayName: "AppControllerView",
|
var AppControllerView = React.createClass({
|
||||||
mixins: [
|
displayName: "AppControllerView",
|
||||||
Backbone.Events,
|
|
||||||
loop.store.StoreMixin("conversationAppStore"),
|
mixins: [Backbone.Events, loop.store.StoreMixin("conversationAppStore"), sharedMixins.DocumentTitleMixin, sharedMixins.WindowCloseMixin],
|
||||||
sharedMixins.DocumentTitleMixin,
|
|
||||||
sharedMixins.WindowCloseMixin
|
|
||||||
],
|
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore)
|
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore)
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function () {
|
||||||
return this.getStoreState();
|
return this.getStoreState();
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderFeedbackForm: function() {
|
_renderFeedbackForm: function () {
|
||||||
this.setTitle(mozL10n.get("conversation_has_ended"));
|
this.setTitle(mozL10n.get("conversation_has_ended"));
|
||||||
|
|
||||||
return (React.createElement(FeedbackView, {
|
return React.createElement(FeedbackView, {
|
||||||
onAfterFeedbackReceived: this.closeWindow}));
|
onAfterFeedbackReceived: this.closeWindow });
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We only show the feedback for once every 6 months, otherwise close
|
* We only show the feedback for once every 6 months, otherwise close
|
||||||
* the window.
|
* the window.
|
||||||
*/
|
*/
|
||||||
handleCallTerminated: function() {
|
handleCallTerminated: function () {
|
||||||
var delta = new Date() - new Date(this.state.feedbackTimestamp);
|
var delta = new Date() - new Date(this.state.feedbackTimestamp);
|
||||||
|
|
||||||
// Show timestamp if feedback period (6 months) passed.
|
// Show timestamp if feedback period (6 months) passed.
|
||||||
// 0 is default value for pref. Always show feedback form on first use.
|
// 0 is default value for pref. Always show feedback form on first use.
|
||||||
if (this.state.feedbackTimestamp === 0 ||
|
if (this.state.feedbackTimestamp === 0 || delta >= this.state.feedbackPeriod) {
|
||||||
delta >= this.state.feedbackPeriod) {
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.ShowFeedbackForm());
|
this.props.dispatcher.dispatch(new sharedActions.ShowFeedbackForm());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -60,25 +56,28 @@ loop.conversation = (function(mozL10n) {
|
|||||||
this.closeWindow();
|
this.closeWindow();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
if (this.state.showFeedbackForm) {
|
if (this.state.showFeedbackForm) {
|
||||||
return this._renderFeedbackForm();
|
return this._renderFeedbackForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.state.windowType) {
|
switch (this.state.windowType) {
|
||||||
case "room": {
|
case "room":
|
||||||
return (React.createElement(DesktopRoomConversationView, {
|
{
|
||||||
|
return React.createElement(DesktopRoomConversationView, {
|
||||||
chatWindowDetached: this.state.chatWindowDetached,
|
chatWindowDetached: this.state.chatWindowDetached,
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
onCallTerminated: this.handleCallTerminated,
|
onCallTerminated: this.handleCallTerminated,
|
||||||
roomStore: this.props.roomStore}));
|
roomStore: this.props.roomStore });
|
||||||
}
|
}
|
||||||
case "failed": {
|
case "failed":
|
||||||
return (React.createElement(RoomFailureView, {
|
{
|
||||||
|
return React.createElement(RoomFailureView, {
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
failureReason: FAILURE_DETAILS.UNKNOWN}));
|
failureReason: FAILURE_DETAILS.UNKNOWN });
|
||||||
}
|
}
|
||||||
default: {
|
default:
|
||||||
|
{
|
||||||
// If we don't have a windowType, we don't know what we are yet,
|
// If we don't have a windowType, we don't know what we are yet,
|
||||||
// so don't display anything.
|
// so don't display anything.
|
||||||
return null;
|
return null;
|
||||||
@@ -100,20 +99,10 @@ loop.conversation = (function(mozL10n) {
|
|||||||
windowId = hash[1];
|
windowId = hash[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
var requests = [
|
var requests = [["GetAllConstants"], ["GetAllStrings"], ["GetLocale"], ["GetLoopPref", "ot.guid"], ["GetLoopPref", "textChat.enabled"], ["GetLoopPref", "feedback.periodSec"], ["GetLoopPref", "feedback.dateLastSeenSec"]];
|
||||||
["GetAllConstants"],
|
var prefetch = [["GetConversationWindowData", windowId]];
|
||||||
["GetAllStrings"],
|
|
||||||
["GetLocale"],
|
|
||||||
["GetLoopPref", "ot.guid"],
|
|
||||||
["GetLoopPref", "textChat.enabled"],
|
|
||||||
["GetLoopPref", "feedback.periodSec"],
|
|
||||||
["GetLoopPref", "feedback.dateLastSeenSec"]
|
|
||||||
];
|
|
||||||
var prefetch = [
|
|
||||||
["GetConversationWindowData", windowId]
|
|
||||||
];
|
|
||||||
|
|
||||||
return loop.requestMulti.apply(null, requests.concat(prefetch)).then(function(results) {
|
return loop.requestMulti.apply(null, requests.concat(prefetch)).then(function (results) {
|
||||||
// `requestIdx` is keyed off the order of the `requests` and `prefetch`
|
// `requestIdx` is keyed off the order of the `requests` and `prefetch`
|
||||||
// arrays. Be careful to update both when making changes.
|
// arrays. Be careful to update both when making changes.
|
||||||
var requestIdx = 0;
|
var requestIdx = 0;
|
||||||
@@ -124,7 +113,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
var locale = results[++requestIdx];
|
var locale = results[++requestIdx];
|
||||||
mozL10n.initialize({
|
mozL10n.initialize({
|
||||||
locale: locale,
|
locale: locale,
|
||||||
getStrings: function(key) {
|
getStrings: function (key) {
|
||||||
if (!(key in stringBundle)) {
|
if (!(key in stringBundle)) {
|
||||||
console.error("No string found for key: ", key);
|
console.error("No string found for key: ", key);
|
||||||
return "{ textContent: '' }";
|
return "{ textContent: '' }";
|
||||||
@@ -138,10 +127,10 @@ loop.conversation = (function(mozL10n) {
|
|||||||
// don't work in the conversation window
|
// don't work in the conversation window
|
||||||
var currGuid = results[++requestIdx];
|
var currGuid = results[++requestIdx];
|
||||||
window.OT.overrideGuidStorage({
|
window.OT.overrideGuidStorage({
|
||||||
get: function(callback) {
|
get: function (callback) {
|
||||||
callback(null, currGuid);
|
callback(null, currGuid);
|
||||||
},
|
},
|
||||||
set: function(guid, callback) {
|
set: function (guid, callback) {
|
||||||
// See nsIPrefBranch
|
// See nsIPrefBranch
|
||||||
var PREF_STRING = 32;
|
var PREF_STRING = 32;
|
||||||
currGuid = guid;
|
currGuid = guid;
|
||||||
@@ -177,7 +166,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
feedbackTimestamp: results[++requestIdx]
|
feedbackTimestamp: results[++requestIdx]
|
||||||
});
|
});
|
||||||
|
|
||||||
prefetch.forEach(function(req) {
|
prefetch.forEach(function (req) {
|
||||||
req.shift();
|
req.shift();
|
||||||
loop.storeRequest(req, results[++requestIdx]);
|
loop.storeRequest(req, results[++requestIdx]);
|
||||||
});
|
});
|
||||||
@@ -195,13 +184,12 @@ loop.conversation = (function(mozL10n) {
|
|||||||
textChatStore: textChatStore
|
textChatStore: textChatStore
|
||||||
});
|
});
|
||||||
|
|
||||||
React.render(
|
React.render(React.createElement(AppControllerView, {
|
||||||
React.createElement(AppControllerView, {
|
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher,
|
||||||
roomStore: roomStore}), document.querySelector("#main"));
|
roomStore: roomStore }), document.querySelector("#main"));
|
||||||
|
|
||||||
document.documentElement.setAttribute("lang", mozL10n.getLanguage());
|
document.documentElement.setAttribute("lang", mozL10n.language.code);
|
||||||
document.documentElement.setAttribute("dir", mozL10n.getDirection());
|
document.documentElement.setAttribute("dir", mozL10n.language.direction);
|
||||||
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||||
@@ -222,6 +210,6 @@ loop.conversation = (function(mozL10n) {
|
|||||||
*/
|
*/
|
||||||
_sdkDriver: null
|
_sdkDriver: null
|
||||||
};
|
};
|
||||||
})(document.mozL10n);
|
}(document.mozL10n);
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", loop.conversation.init);
|
document.addEventListener("DOMContentLoaded", loop.conversation.init);
|
||||||
|
|||||||
@@ -3,14 +3,17 @@
|
|||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.feedbackViews = (function(_, mozL10n) {
|
loop.feedbackViews = function (_, mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Feedback view is displayed once every 6 months (loop.feedback.periodSec)
|
* Feedback view is displayed once every 6 months (loop.feedback.periodSec)
|
||||||
* after a conversation has ended.
|
* after a conversation has ended.
|
||||||
*/
|
*/
|
||||||
var FeedbackView = React.createClass({displayName: "FeedbackView",
|
|
||||||
|
var FeedbackView = React.createClass({
|
||||||
|
displayName: "FeedbackView",
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onAfterFeedbackReceived: React.PropTypes.func.isRequired
|
onAfterFeedbackReceived: React.PropTypes.func.isRequired
|
||||||
},
|
},
|
||||||
@@ -19,26 +22,32 @@ loop.feedbackViews = (function(_, mozL10n) {
|
|||||||
* Pressing the button to leave feedback will open the form in a new page
|
* Pressing the button to leave feedback will open the form in a new page
|
||||||
* and close the conversation window.
|
* and close the conversation window.
|
||||||
*/
|
*/
|
||||||
onFeedbackButtonClick: function() {
|
onFeedbackButtonClick: function () {
|
||||||
loop.request("GetLoopPref", "feedback.formURL").then(function(url) {
|
loop.request("GetLoopPref", "feedback.formURL").then(function (url) {
|
||||||
loop.request("OpenURL", url).then(this.props.onAfterFeedbackReceived);
|
loop.request("OpenURL", url).then(this.props.onAfterFeedbackReceived);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: "feedback-view-container"},
|
"div",
|
||||||
React.createElement("h2", {className: "feedback-heading"},
|
{ className: "feedback-view-container" },
|
||||||
|
React.createElement(
|
||||||
|
"h2",
|
||||||
|
{ className: "feedback-heading" },
|
||||||
mozL10n.get("feedback_window_heading")
|
mozL10n.get("feedback_window_heading")
|
||||||
),
|
),
|
||||||
React.createElement("div", {className: "feedback-hello-logo"}),
|
React.createElement("div", { className: "feedback-hello-logo" }),
|
||||||
React.createElement("div", {className: "feedback-button-container"},
|
React.createElement(
|
||||||
React.createElement("button", {onClick: this.onFeedbackButtonClick,
|
"div",
|
||||||
ref: "feedbackFormBtn"},
|
{ className: "feedback-button-container" },
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{ onClick: this.onFeedbackButtonClick,
|
||||||
|
ref: "feedbackFormBtn" },
|
||||||
mozL10n.get("feedback_request_button")
|
mozL10n.get("feedback_request_button")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -46,4 +55,4 @@ loop.feedbackViews = (function(_, mozL10n) {
|
|||||||
return {
|
return {
|
||||||
FeedbackView: FeedbackView
|
FeedbackView: FeedbackView
|
||||||
};
|
};
|
||||||
})(_, navigator.mozL10n || document.mozL10n);
|
}(_, navigator.mozL10n || document.mozL10n);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -260,9 +260,11 @@ loop.store = loop.store || {};
|
|||||||
this._notifications.remove("create-room-error");
|
this._notifications.remove("create-room-error");
|
||||||
loop.request("Rooms:Create", roomCreationData).then(function(result) {
|
loop.request("Rooms:Create", roomCreationData).then(function(result) {
|
||||||
var buckets = this._constants.ROOM_CREATE;
|
var buckets = this._constants.ROOM_CREATE;
|
||||||
if (result && result.isError) {
|
if (!result || result.isError) {
|
||||||
loop.request("TelemetryAddValue", "LOOP_ROOM_CREATE", buckets.CREATE_FAIL);
|
loop.request("TelemetryAddValue", "LOOP_ROOM_CREATE", buckets.CREATE_FAIL);
|
||||||
this.dispatchAction(new sharedActions.CreateRoomError({ error: result }));
|
this.dispatchAction(new sharedActions.CreateRoomError({
|
||||||
|
error: result ? result : new Error("no result")
|
||||||
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.roomViews = (function(mozL10n) {
|
loop.roomViews = function (mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||||
@@ -24,20 +24,17 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
|
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function () {
|
||||||
this.listenTo(this.props.roomStore, "change:activeRoom",
|
this.listenTo(this.props.roomStore, "change:activeRoom", this._onActiveRoomStateChanged);
|
||||||
this._onActiveRoomStateChanged);
|
this.listenTo(this.props.roomStore, "change:error", this._onRoomError);
|
||||||
this.listenTo(this.props.roomStore, "change:error",
|
this.listenTo(this.props.roomStore, "change:savingContext", this._onRoomSavingContext);
|
||||||
this._onRoomError);
|
|
||||||
this.listenTo(this.props.roomStore, "change:savingContext",
|
|
||||||
this._onRoomSavingContext);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function () {
|
||||||
this.stopListening(this.props.roomStore);
|
this.stopListening(this.props.roomStore);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onActiveRoomStateChanged: function() {
|
_onActiveRoomStateChanged: function () {
|
||||||
// Only update the state if we're mounted, to avoid the problem where
|
// Only update the state if we're mounted, to avoid the problem where
|
||||||
// stopListening doesn't nuke the active listeners during a event
|
// stopListening doesn't nuke the active listeners during a event
|
||||||
// processing.
|
// processing.
|
||||||
@@ -46,7 +43,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onRoomError: function() {
|
_onRoomError: function () {
|
||||||
// Only update the state if we're mounted, to avoid the problem where
|
// Only update the state if we're mounted, to avoid the problem where
|
||||||
// stopListening doesn't nuke the active listeners during a event
|
// stopListening doesn't nuke the active listeners during a event
|
||||||
// processing.
|
// processing.
|
||||||
@@ -55,7 +52,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onRoomSavingContext: function() {
|
_onRoomSavingContext: function () {
|
||||||
// Only update the state if we're mounted, to avoid the problem where
|
// Only update the state if we're mounted, to avoid the problem where
|
||||||
// stopListening doesn't nuke the active listeners during a event
|
// stopListening doesn't nuke the active listeners during a event
|
||||||
// processing.
|
// processing.
|
||||||
@@ -64,7 +61,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function () {
|
||||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||||
return _.extend({
|
return _.extend({
|
||||||
// Used by the UI showcase.
|
// Used by the UI showcase.
|
||||||
@@ -77,7 +74,9 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
/**
|
/**
|
||||||
* Used to display errors in direct calls and rooms to the user.
|
* Used to display errors in direct calls and rooms to the user.
|
||||||
*/
|
*/
|
||||||
var FailureInfoView = React.createClass({displayName: "FailureInfoView",
|
var FailureInfoView = React.createClass({
|
||||||
|
displayName: "FailureInfoView",
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
failureReason: React.PropTypes.string.isRequired
|
failureReason: React.PropTypes.string.isRequired
|
||||||
},
|
},
|
||||||
@@ -87,14 +86,13 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
*
|
*
|
||||||
* @return {String} The translated message for the failure reason.
|
* @return {String} The translated message for the failure reason.
|
||||||
*/
|
*/
|
||||||
_getMessage: function() {
|
_getMessage: function () {
|
||||||
switch (this.props.failureReason) {
|
switch (this.props.failureReason) {
|
||||||
case FAILURE_DETAILS.NO_MEDIA:
|
case FAILURE_DETAILS.NO_MEDIA:
|
||||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||||
return mozL10n.get("no_media_failure_message");
|
return mozL10n.get("no_media_failure_message");
|
||||||
case FAILURE_DETAILS.TOS_FAILURE:
|
case FAILURE_DETAILS.TOS_FAILURE:
|
||||||
return mozL10n.get("tos_failure_message",
|
return mozL10n.get("tos_failure_message", { clientShortname: mozL10n.get("clientShortname2") });
|
||||||
{ clientShortname: mozL10n.get("clientShortname2") });
|
|
||||||
case FAILURE_DETAILS.ICE_FAILED:
|
case FAILURE_DETAILS.ICE_FAILED:
|
||||||
return mozL10n.get("ice_failure_message");
|
return mozL10n.get("ice_failure_message");
|
||||||
default:
|
default:
|
||||||
@@ -102,11 +100,15 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: "failure-info"},
|
"div",
|
||||||
React.createElement("div", {className: "failure-info-logo"}),
|
{ className: "failure-info" },
|
||||||
React.createElement("h2", {className: "failure-info-message"}, this._getMessage())
|
React.createElement("div", { className: "failure-info-logo" }),
|
||||||
|
React.createElement(
|
||||||
|
"h2",
|
||||||
|
{ className: "failure-info-message" },
|
||||||
|
this._getMessage()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,9 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
/**
|
/**
|
||||||
* Something went wrong view. Displayed when there's a big problem.
|
* Something went wrong view. Displayed when there's a big problem.
|
||||||
*/
|
*/
|
||||||
var RoomFailureView = React.createClass({displayName: "RoomFailureView",
|
var RoomFailureView = React.createClass({
|
||||||
|
displayName: "RoomFailureView",
|
||||||
|
|
||||||
mixins: [sharedMixins.AudioMixin],
|
mixins: [sharedMixins.AudioMixin],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
@@ -123,19 +127,15 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
failureReason: React.PropTypes.string
|
failureReason: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function () {
|
||||||
this.play("failure");
|
this.play("failure");
|
||||||
},
|
},
|
||||||
|
|
||||||
handleRejoinCall: function() {
|
handleRejoinCall: function () {
|
||||||
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
|
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
var settingsMenuItems = [
|
|
||||||
{ id: "help" }
|
|
||||||
];
|
|
||||||
|
|
||||||
var btnTitle;
|
var btnTitle;
|
||||||
if (this.props.failureReason === FAILURE_DETAILS.ICE_FAILED) {
|
if (this.props.failureReason === FAILURE_DETAILS.ICE_FAILED) {
|
||||||
btnTitle = mozL10n.get("retry_call_button");
|
btnTitle = mozL10n.get("retry_call_button");
|
||||||
@@ -143,24 +143,27 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
btnTitle = mozL10n.get("rejoin_button");
|
btnTitle = mozL10n.get("rejoin_button");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: "room-failure"},
|
"div",
|
||||||
React.createElement(FailureInfoView, {failureReason: this.props.failureReason}),
|
{ className: "room-failure" },
|
||||||
React.createElement("div", {className: "btn-group call-action-group"},
|
React.createElement(FailureInfoView, { failureReason: this.props.failureReason }),
|
||||||
React.createElement("button", {className: "btn btn-info btn-rejoin",
|
React.createElement(
|
||||||
onClick: this.handleRejoinCall},
|
"div",
|
||||||
|
{ className: "btn-group call-action-group" },
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{ className: "btn btn-info btn-rejoin",
|
||||||
|
onClick: this.handleRejoinCall },
|
||||||
btnTitle
|
btnTitle
|
||||||
)
|
)
|
||||||
),
|
|
||||||
React.createElement(loop.shared.views.SettingsControlButton, {
|
|
||||||
menuBelow: true,
|
|
||||||
menuItems: settingsMenuItems})
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var SocialShareDropdown = React.createClass({displayName: "SocialShareDropdown",
|
var SocialShareDropdown = React.createClass({
|
||||||
|
displayName: "SocialShareDropdown",
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
roomUrl: React.PropTypes.string,
|
roomUrl: React.PropTypes.string,
|
||||||
@@ -168,18 +171,17 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
socialShareProviders: React.PropTypes.array
|
socialShareProviders: React.PropTypes.array
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAddServiceClick: function(event) {
|
handleAddServiceClick: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.AddSocialShareProvider());
|
this.props.dispatcher.dispatch(new sharedActions.AddSocialShareProvider());
|
||||||
},
|
},
|
||||||
|
|
||||||
handleProviderClick: function(event) {
|
handleProviderClick: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
var origin = event.currentTarget.dataset.provider;
|
var origin = event.currentTarget.dataset.provider;
|
||||||
var provider = this.props.socialShareProviders
|
var provider = this.props.socialShareProviders.filter(function (socialProvider) {
|
||||||
.filter(function(socialProvider) {
|
|
||||||
return socialProvider.origin === origin;
|
return socialProvider.origin === origin;
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
@@ -190,7 +192,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
// Don't render a thing when no data has been fetched yet.
|
// Don't render a thing when no data has been fetched yet.
|
||||||
if (!this.props.socialShareProviders) {
|
if (!this.props.socialShareProviders) {
|
||||||
return null;
|
return null;
|
||||||
@@ -204,27 +206,35 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
"hide": !this.props.show
|
"hide": !this.props.show
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("ul", {className: shareDropdown},
|
"ul",
|
||||||
React.createElement("li", {className: "dropdown-menu-item", onClick: this.handleAddServiceClick},
|
{ className: shareDropdown },
|
||||||
React.createElement("i", {className: "icon icon-add-share-service"}),
|
React.createElement(
|
||||||
React.createElement("span", null, mozL10n.get("share_add_service_button"))
|
"li",
|
||||||
|
{ className: "dropdown-menu-item", onClick: this.handleAddServiceClick },
|
||||||
|
React.createElement("i", { className: "icon icon-add-share-service" }),
|
||||||
|
React.createElement(
|
||||||
|
"span",
|
||||||
|
null,
|
||||||
|
mozL10n.get("share_add_service_button")
|
||||||
|
)
|
||||||
),
|
),
|
||||||
this.props.socialShareProviders.length ? React.createElement("li", {className: "dropdown-menu-separator"}) : null,
|
this.props.socialShareProviders.length ? React.createElement("li", { className: "dropdown-menu-separator" }) : null,
|
||||||
|
this.props.socialShareProviders.map(function (provider, idx) {
|
||||||
this.props.socialShareProviders.map(function(provider, idx) {
|
return React.createElement(
|
||||||
return (
|
"li",
|
||||||
React.createElement("li", {className: "dropdown-menu-item",
|
{ className: "dropdown-menu-item",
|
||||||
"data-provider": provider.origin,
|
"data-provider": provider.origin,
|
||||||
key: "provider-" + idx,
|
key: "provider-" + idx,
|
||||||
onClick: this.handleProviderClick},
|
onClick: this.handleProviderClick },
|
||||||
React.createElement("img", {className: "icon", src: provider.iconURL}),
|
React.createElement("img", { className: "icon", src: provider.iconURL }),
|
||||||
React.createElement("span", null, provider.name)
|
React.createElement(
|
||||||
|
"span",
|
||||||
|
null,
|
||||||
|
provider.name
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -232,7 +242,9 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
/**
|
/**
|
||||||
* Desktop room invitation view (overlay).
|
* Desktop room invitation view (overlay).
|
||||||
*/
|
*/
|
||||||
var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView",
|
var DesktopRoomInvitationView = React.createClass({
|
||||||
|
displayName: "DesktopRoomInvitationView",
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
TRIGGERED_RESET_DELAY: 2000
|
TRIGGERED_RESET_DELAY: 2000
|
||||||
},
|
},
|
||||||
@@ -242,24 +254,20 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
error: React.PropTypes.object,
|
error: React.PropTypes.object,
|
||||||
onAddContextClick: React.PropTypes.func,
|
|
||||||
onEditContextClose: React.PropTypes.func,
|
|
||||||
// This data is supplied by the activeRoomStore.
|
// This data is supplied by the activeRoomStore.
|
||||||
roomData: React.PropTypes.object.isRequired,
|
roomData: React.PropTypes.object.isRequired,
|
||||||
savingContext: React.PropTypes.bool,
|
|
||||||
show: React.PropTypes.bool.isRequired,
|
show: React.PropTypes.bool.isRequired,
|
||||||
showEditContext: React.PropTypes.bool.isRequired,
|
|
||||||
socialShareProviders: React.PropTypes.array
|
socialShareProviders: React.PropTypes.array
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
copiedUrl: false,
|
copiedUrl: false,
|
||||||
newRoomName: ""
|
newRoomName: ""
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
handleEmailButtonClick: function(event) {
|
handleEmailButtonClick: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
var roomData = this.props.roomData;
|
var roomData = this.props.roomData;
|
||||||
@@ -272,15 +280,14 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.dispatcher.dispatch(
|
this.props.dispatcher.dispatch(new sharedActions.EmailRoomUrl({
|
||||||
new sharedActions.EmailRoomUrl({
|
|
||||||
roomUrl: roomData.roomUrl,
|
roomUrl: roomData.roomUrl,
|
||||||
roomDescription: contextURL,
|
roomDescription: contextURL,
|
||||||
from: "conversation"
|
from: "conversation"
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
handleFacebookButtonClick: function(event) {
|
handleFacebookButtonClick: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.FacebookShareRoomUrl({
|
this.props.dispatcher.dispatch(new sharedActions.FacebookShareRoomUrl({
|
||||||
@@ -289,7 +296,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
handleCopyButtonClick: function(event) {
|
handleCopyButtonClick: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.CopyRoomUrl({
|
this.props.dispatcher.dispatch(new sharedActions.CopyRoomUrl({
|
||||||
@@ -304,57 +311,84 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
/**
|
/**
|
||||||
* Reset state of triggered buttons if necessary
|
* Reset state of triggered buttons if necessary
|
||||||
*/
|
*/
|
||||||
resetTriggeredButtons: function() {
|
resetTriggeredButtons: function () {
|
||||||
if (this.state.copiedUrl) {
|
if (this.state.copiedUrl) {
|
||||||
this.setState({ copiedUrl: false });
|
this.setState({ copiedUrl: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleEditContextClose: function() {
|
render: function () {
|
||||||
if (this.props.onEditContextClose) {
|
|
||||||
this.props.onEditContextClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
if (!this.props.show || !this.props.roomData.roomUrl) {
|
if (!this.props.show || !this.props.roomData.roomUrl) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cx = classNames;
|
var cx = classNames;
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: "room-invitation-overlay"},
|
"div",
|
||||||
React.createElement("div", {className: "room-invitation-content"},
|
{ className: "room-invitation-overlay" },
|
||||||
React.createElement("p", {className: cx({ hide: this.props.showEditContext })},
|
React.createElement(
|
||||||
mozL10n.get("invite_header_text2")
|
"div",
|
||||||
|
{ className: "room-invitation-content" },
|
||||||
|
React.createElement(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
React.createElement(
|
||||||
|
"span",
|
||||||
|
{ className: "room-context-header" },
|
||||||
|
mozL10n.get("invite_header_text_bold")
|
||||||
|
),
|
||||||
|
" ",
|
||||||
|
React.createElement(
|
||||||
|
"span",
|
||||||
|
null,
|
||||||
|
mozL10n.get("invite_header_text3")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
React.createElement("div", {className: cx({
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: cx({
|
||||||
"btn-group": true,
|
"btn-group": true,
|
||||||
"call-action-group": true,
|
"call-action-group": true
|
||||||
hide: this.props.showEditContext
|
}) },
|
||||||
})},
|
React.createElement(
|
||||||
React.createElement("div", {className: cx({
|
"div",
|
||||||
|
{ className: cx({
|
||||||
"btn-copy": true,
|
"btn-copy": true,
|
||||||
"invite-button": true,
|
"invite-button": true,
|
||||||
"triggered": this.state.copiedUrl
|
"triggered": this.state.copiedUrl
|
||||||
}),
|
}),
|
||||||
onClick: this.handleCopyButtonClick},
|
onClick: this.handleCopyButtonClick },
|
||||||
React.createElement("img", {src: "shared/img/glyph-link-16x16.svg"}),
|
React.createElement("img", { src: "shared/img/glyph-link-16x16.svg" }),
|
||||||
React.createElement("p", null, mozL10n.get(this.state.copiedUrl ?
|
React.createElement(
|
||||||
"invite_copied_link_button" : "invite_copy_link_button"))
|
"p",
|
||||||
|
null,
|
||||||
|
mozL10n.get(this.state.copiedUrl ? "invite_copied_link_button" : "invite_copy_link_button")
|
||||||
|
)
|
||||||
),
|
),
|
||||||
React.createElement("div", {className: "btn-email invite-button",
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "btn-email invite-button",
|
||||||
onClick: this.handleEmailButtonClick,
|
onClick: this.handleEmailButtonClick,
|
||||||
onMouseOver: this.resetTriggeredButtons},
|
onMouseOver: this.resetTriggeredButtons },
|
||||||
React.createElement("img", {src: "shared/img/glyph-email-16x16.svg"}),
|
React.createElement("img", { src: "shared/img/glyph-email-16x16.svg" }),
|
||||||
React.createElement("p", null, mozL10n.get("invite_email_link_button"))
|
React.createElement(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
mozL10n.get("invite_email_link_button")
|
||||||
|
)
|
||||||
),
|
),
|
||||||
React.createElement("div", {className: "btn-facebook invite-button",
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "btn-facebook invite-button",
|
||||||
onClick: this.handleFacebookButtonClick,
|
onClick: this.handleFacebookButtonClick,
|
||||||
onMouseOver: this.resetTriggeredButtons},
|
onMouseOver: this.resetTriggeredButtons },
|
||||||
React.createElement("img", {src: "shared/img/glyph-facebook-16x16.svg"}),
|
React.createElement("img", { src: "shared/img/glyph-facebook-16x16.svg" }),
|
||||||
React.createElement("p", null, mozL10n.get("invite_facebook_button3"))
|
React.createElement(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
mozL10n.get("invite_facebook_button3")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
React.createElement(SocialShareDropdown, {
|
React.createElement(SocialShareDropdown, {
|
||||||
@@ -362,222 +396,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
ref: "menu",
|
ref: "menu",
|
||||||
roomUrl: this.props.roomData.roomUrl,
|
roomUrl: this.props.roomData.roomUrl,
|
||||||
show: this.state.showMenu,
|
show: this.state.showMenu,
|
||||||
socialShareProviders: this.props.socialShareProviders}),
|
socialShareProviders: this.props.socialShareProviders })
|
||||||
React.createElement(DesktopRoomEditContextView, {
|
|
||||||
dispatcher: this.props.dispatcher,
|
|
||||||
error: this.props.error,
|
|
||||||
onClose: this.handleEditContextClose,
|
|
||||||
roomData: this.props.roomData,
|
|
||||||
savingContext: this.props.savingContext,
|
|
||||||
show: this.props.showEditContext})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var DesktopRoomEditContextView = React.createClass({displayName: "DesktopRoomEditContextView",
|
|
||||||
mixins: [React.addons.LinkedStateMixin],
|
|
||||||
maxRoomNameLength: 124,
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
|
||||||
error: React.PropTypes.object,
|
|
||||||
onClose: React.PropTypes.func,
|
|
||||||
// This data is supplied by the activeRoomStore.
|
|
||||||
roomData: React.PropTypes.object.isRequired,
|
|
||||||
savingContext: React.PropTypes.bool.isRequired,
|
|
||||||
show: React.PropTypes.bool.isRequired
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
|
||||||
this._fetchMetadata();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
|
||||||
var newState = {};
|
|
||||||
// When the 'show' prop is changed from outside this component, we do need
|
|
||||||
// to update the state.
|
|
||||||
if (("show" in nextProps) && nextProps.show !== this.props.show) {
|
|
||||||
newState.show = nextProps.show;
|
|
||||||
if (nextProps.show) {
|
|
||||||
this._fetchMetadata();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// When we receive an update for the `roomData` property, make sure that
|
|
||||||
// the current form fields reflect reality. This is necessary, because the
|
|
||||||
// form state is maintained in the components' state.
|
|
||||||
if (nextProps.roomData) {
|
|
||||||
// Right now it's only necessary to update the form input states when
|
|
||||||
// they contain no text yet.
|
|
||||||
if (!this.state.newRoomName && nextProps.roomData.roomName) {
|
|
||||||
newState.newRoomName = nextProps.roomData.roomName;
|
|
||||||
}
|
|
||||||
var url = this._getURL(nextProps.roomData);
|
|
||||||
if (url) {
|
|
||||||
if (!this.state.newRoomURL && url.location) {
|
|
||||||
newState.newRoomURL = url.location;
|
|
||||||
}
|
|
||||||
if (!this.state.newRoomDescription && url.description) {
|
|
||||||
newState.newRoomDescription = url.description;
|
|
||||||
}
|
|
||||||
if (!this.state.newRoomThumbnail && url.thumbnail) {
|
|
||||||
newState.newRoomThumbnail = url.thumbnail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feature support: when a context save completed without error, we can
|
|
||||||
// close the context edit form.
|
|
||||||
if (("savingContext" in nextProps) && this.props.savingContext &&
|
|
||||||
this.props.savingContext !== nextProps.savingContext && this.state.show
|
|
||||||
&& !this.props.error && !nextProps.error) {
|
|
||||||
newState.show = false;
|
|
||||||
if (this.props.onClose) {
|
|
||||||
this.props.onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.getOwnPropertyNames(newState).length) {
|
|
||||||
this.setState(newState);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
var url = this._getURL();
|
|
||||||
return {
|
|
||||||
// `availableContext` prop only used in tests.
|
|
||||||
availableContext: null,
|
|
||||||
show: this.props.show,
|
|
||||||
newRoomName: this.props.roomData.roomName || "",
|
|
||||||
newRoomURL: url && url.location || "",
|
|
||||||
newRoomDescription: url && url.description || "",
|
|
||||||
newRoomThumbnail: url && url.thumbnail || ""
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
_fetchMetadata: function() {
|
|
||||||
loop.request("GetSelectedTabMetadata").then(function(metadata) {
|
|
||||||
var previewImage = metadata.favicon || "";
|
|
||||||
var description = metadata.title || metadata.description;
|
|
||||||
var metaUrl = metadata.url;
|
|
||||||
this.setState({
|
|
||||||
availableContext: {
|
|
||||||
previewImage: previewImage,
|
|
||||||
description: description,
|
|
||||||
url: metaUrl
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCloseClick: function(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.setState({ show: false });
|
|
||||||
if (this.props.onClose) {
|
|
||||||
this.props.onClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleContextClick: function(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
var url = this._getURL();
|
|
||||||
if (!url || !url.location) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop.requestMulti(
|
|
||||||
["OpenURL", url.location],
|
|
||||||
["TelemetryAddValue", "LOOP_ROOM_CONTEXT_CLICK", 1]);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleFormSubmit: function(event) {
|
|
||||||
event && event.preventDefault();
|
|
||||||
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.UpdateRoomContext({
|
|
||||||
roomToken: this.props.roomData.roomToken,
|
|
||||||
newRoomName: this.state.newRoomName,
|
|
||||||
newRoomURL: this.state.newRoomURL,
|
|
||||||
newRoomDescription: this.state.newRoomDescription,
|
|
||||||
newRoomThumbnail: this.state.newRoomThumbnail
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextareaKeyDown: function(event) {
|
|
||||||
// Submit the form as soon as the user press Enter in that field
|
|
||||||
// Note: We're using a textarea instead of a simple text input to display
|
|
||||||
// placeholder and entered text on two lines, to circumvent l10n
|
|
||||||
// rendering/UX issues for some locales.
|
|
||||||
if (event.which === 13) {
|
|
||||||
this.handleFormSubmit(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to extract URL context data from the `roomData` property
|
|
||||||
* that can also be supplied as an argument.
|
|
||||||
*
|
|
||||||
* @param {Object} roomData Optional room data object to use, equivalent to
|
|
||||||
* the activeRoomStore state.
|
|
||||||
* @return {Object} The first context URL found on the `roomData` object.
|
|
||||||
*/
|
|
||||||
_getURL: function(roomData) {
|
|
||||||
roomData = roomData || this.props.roomData;
|
|
||||||
return this.props.roomData.roomContextUrls &&
|
|
||||||
this.props.roomData.roomContextUrls[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
if (!this.state.show) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = this._getURL();
|
|
||||||
var thumbnail = url && url.thumbnail || "shared/img/icons-16x16.svg#globe";
|
|
||||||
var urlDescription = url && url.description || "";
|
|
||||||
var location = url && url.location || "";
|
|
||||||
|
|
||||||
var cx = classNames;
|
|
||||||
var availableContext = this.state.availableContext;
|
|
||||||
return (
|
|
||||||
React.createElement("div", {className: "room-context"},
|
|
||||||
React.createElement("p", {className: cx({ "error": !!this.props.error,
|
|
||||||
"error-display-area": true })},
|
|
||||||
mozL10n.get("rooms_change_failed_label")
|
|
||||||
),
|
|
||||||
React.createElement("h2", {className: "room-context-header"}, mozL10n.get("context_inroom_header")),
|
|
||||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
|
||||||
React.createElement("input", {className: "room-context-name",
|
|
||||||
maxLength: this.maxRoomNameLength,
|
|
||||||
onKeyDown: this.handleTextareaKeyDown,
|
|
||||||
placeholder: mozL10n.get("context_edit_name_placeholder"),
|
|
||||||
type: "text",
|
|
||||||
valueLink: this.linkState("newRoomName")}),
|
|
||||||
React.createElement("input", {className: "room-context-url",
|
|
||||||
disabled: availableContext && availableContext.url === this.state.newRoomURL,
|
|
||||||
onKeyDown: this.handleTextareaKeyDown,
|
|
||||||
placeholder: "https://",
|
|
||||||
type: "text",
|
|
||||||
valueLink: this.linkState("newRoomURL")}),
|
|
||||||
React.createElement("textarea", {className: "room-context-comments",
|
|
||||||
onKeyDown: this.handleTextareaKeyDown,
|
|
||||||
placeholder: mozL10n.get("context_edit_comments_placeholder"),
|
|
||||||
rows: "2", type: "text",
|
|
||||||
valueLink: this.linkState("newRoomDescription")}),
|
|
||||||
React.createElement(sharedViews.ButtonGroup, null,
|
|
||||||
React.createElement(sharedViews.Button, {additionalClass: "button-cancel",
|
|
||||||
caption: mozL10n.get("context_cancel_label"),
|
|
||||||
onClick: this.handleCloseClick}),
|
|
||||||
React.createElement(sharedViews.Button, {additionalClass: "button-accept",
|
|
||||||
caption: mozL10n.get("context_done_label"),
|
|
||||||
disabled: this.props.savingContext,
|
|
||||||
onClick: this.handleFormSubmit})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -585,14 +404,10 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
/**
|
/**
|
||||||
* Desktop room conversation view.
|
* Desktop room conversation view.
|
||||||
*/
|
*/
|
||||||
var DesktopRoomConversationView = React.createClass({displayName: "DesktopRoomConversationView",
|
var DesktopRoomConversationView = React.createClass({
|
||||||
mixins: [
|
displayName: "DesktopRoomConversationView",
|
||||||
ActiveRoomStoreMixin,
|
|
||||||
sharedMixins.DocumentTitleMixin,
|
mixins: [ActiveRoomStoreMixin, sharedMixins.DocumentTitleMixin, sharedMixins.MediaSetupMixin, sharedMixins.RoomsAudioMixin, sharedMixins.WindowCloseMixin],
|
||||||
sharedMixins.MediaSetupMixin,
|
|
||||||
sharedMixins.RoomsAudioMixin,
|
|
||||||
sharedMixins.WindowCloseMixin
|
|
||||||
],
|
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
chatWindowDetached: React.PropTypes.bool.isRequired,
|
chatWindowDetached: React.PropTypes.bool.isRequired,
|
||||||
@@ -604,18 +419,11 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
|
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
componentWillUpdate: function (nextProps, nextState) {
|
||||||
return {
|
|
||||||
showEditContext: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUpdate: function(nextProps, nextState) {
|
|
||||||
// The SDK needs to know about the configuration and the elements to use
|
// The SDK needs to know about the configuration and the elements to use
|
||||||
// for display. So the best way seems to pass the information here - ideally
|
// for display. So the best way seems to pass the information here - ideally
|
||||||
// the sdk wouldn't need to know this, but we can't change that.
|
// the sdk wouldn't need to know this, but we can't change that.
|
||||||
if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
|
if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT && nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
|
||||||
nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
publisherConfig: this.getDefaultPublisherConfig({
|
publisherConfig: this.getDefaultPublisherConfig({
|
||||||
publishVideo: !this.state.videoMuted
|
publishVideo: !this.state.videoMuted
|
||||||
@@ -624,8 +432,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Automatically start sharing a tab now we're ready to share.
|
// Automatically start sharing a tab now we're ready to share.
|
||||||
if (this.state.roomState !== ROOM_STATES.SESSION_CONNECTED &&
|
if (this.state.roomState !== ROOM_STATES.SESSION_CONNECTED && nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
|
||||||
nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.StartBrowserShare());
|
this.props.dispatcher.dispatch(new sharedActions.StartBrowserShare());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -633,7 +440,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
/**
|
/**
|
||||||
* User clicked on the "Leave" button.
|
* User clicked on the "Leave" button.
|
||||||
*/
|
*/
|
||||||
leaveRoom: function() {
|
leaveRoom: function () {
|
||||||
if (this.state.used) {
|
if (this.state.used) {
|
||||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||||
} else {
|
} else {
|
||||||
@@ -647,9 +454,8 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
* @param {String} type The type of stream, e.g. "audio" or "video".
|
* @param {String} type The type of stream, e.g. "audio" or "video".
|
||||||
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
||||||
*/
|
*/
|
||||||
publishStream: function(type, enabled) {
|
publishStream: function (type, enabled) {
|
||||||
this.props.dispatcher.dispatch(
|
this.props.dispatcher.dispatch(new sharedActions.SetMute({
|
||||||
new sharedActions.SetMute({
|
|
||||||
type: type,
|
type: type,
|
||||||
enabled: enabled
|
enabled: enabled
|
||||||
}));
|
}));
|
||||||
@@ -660,9 +466,8 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
*
|
*
|
||||||
* @return {Boolean} True if there's no guests.
|
* @return {Boolean} True if there's no guests.
|
||||||
*/
|
*/
|
||||||
_shouldRenderInvitationOverlay: function() {
|
_shouldRenderInvitationOverlay: function () {
|
||||||
var hasGuests = typeof this.state.participants === "object" &&
|
var hasGuests = typeof this.state.participants === "object" && this.state.participants.filter(function (participant) {
|
||||||
this.state.participants.filter(function(participant) {
|
|
||||||
return !participant.owner;
|
return !participant.owner;
|
||||||
}).length > 0;
|
}).length > 0;
|
||||||
|
|
||||||
@@ -680,7 +485,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
* XXX Refactor shouldRenderRemoteVideo & shouldRenderLoading into one fn
|
* XXX Refactor shouldRenderRemoteVideo & shouldRenderLoading into one fn
|
||||||
* that returns an enum
|
* that returns an enum
|
||||||
*/
|
*/
|
||||||
shouldRenderRemoteVideo: function() {
|
shouldRenderRemoteVideo: function () {
|
||||||
switch (this.state.roomState) {
|
switch (this.state.roomState) {
|
||||||
case ROOM_STATES.HAS_PARTICIPANTS:
|
case ROOM_STATES.HAS_PARTICIPANTS:
|
||||||
if (this.state.remoteVideoEnabled) {
|
if (this.state.remoteVideoEnabled) {
|
||||||
@@ -710,8 +515,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" +
|
console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" + " unexpected roomState: ", this.state.roomState);
|
||||||
" unexpected roomState: ", this.state.roomState);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -723,9 +527,8 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_isLocalLoading: function() {
|
_isLocalLoading: function () {
|
||||||
return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
|
return this.state.roomState === ROOM_STATES.MEDIA_WAIT && !this.state.localSrcMediaElement;
|
||||||
!this.state.localSrcMediaElement;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -735,78 +538,55 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_isRemoteLoading: function() {
|
_isRemoteLoading: function () {
|
||||||
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
|
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS && !this.state.remoteSrcMediaElement && !this.state.mediaConnected);
|
||||||
!this.state.remoteSrcMediaElement &&
|
|
||||||
!this.state.mediaConnected);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAddContextClick: function() {
|
componentDidUpdate: function (prevProps, prevState) {
|
||||||
this.setState({ showEditContext: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEditContextClick: function() {
|
|
||||||
this.setState({ showEditContext: !this.state.showEditContext });
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEditContextClose: function() {
|
|
||||||
this.setState({ showEditContext: false });
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidUpdate: function(prevProps, prevState) {
|
|
||||||
// Handle timestamp and window closing only when the call has terminated.
|
// Handle timestamp and window closing only when the call has terminated.
|
||||||
if (prevState.roomState === ROOM_STATES.ENDED &&
|
if (prevState.roomState === ROOM_STATES.ENDED && this.state.roomState === ROOM_STATES.ENDED) {
|
||||||
this.state.roomState === ROOM_STATES.ENDED) {
|
|
||||||
this.props.onCallTerminated();
|
this.props.onCallTerminated();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContextMenu: function(e) {
|
handleContextMenu: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
if (this.state.roomName || this.state.roomContextUrls) {
|
if (this.state.roomName || this.state.roomContextUrls) {
|
||||||
var roomTitle = this.state.roomName ||
|
var roomTitle = this.state.roomName || this.state.roomContextUrls[0].description || this.state.roomContextUrls[0].location;
|
||||||
this.state.roomContextUrls[0].description ||
|
|
||||||
this.state.roomContextUrls[0].location;
|
|
||||||
this.setTitle(roomTitle);
|
this.setTitle(roomTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
|
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
|
||||||
var shouldRenderEditContextView = this.state.showEditContext;
|
|
||||||
var roomData = this.props.roomStore.getStoreState("activeRoom");
|
var roomData = this.props.roomStore.getStoreState("activeRoom");
|
||||||
|
|
||||||
switch (this.state.roomState) {
|
switch (this.state.roomState) {
|
||||||
case ROOM_STATES.FAILED:
|
case ROOM_STATES.FAILED:
|
||||||
case ROOM_STATES.FULL: {
|
case ROOM_STATES.FULL:
|
||||||
|
{
|
||||||
// Note: While rooms are set to hold a maximum of 2 participants, the
|
// Note: While rooms are set to hold a maximum of 2 participants, the
|
||||||
// FULL case should never happen on desktop.
|
// FULL case should never happen on desktop.
|
||||||
return (
|
return React.createElement(RoomFailureView, {
|
||||||
React.createElement(RoomFailureView, {
|
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
failureReason: this.state.failureReason})
|
failureReason: this.state.failureReason });
|
||||||
);
|
|
||||||
}
|
}
|
||||||
case ROOM_STATES.ENDED: {
|
case ROOM_STATES.ENDED:
|
||||||
|
{
|
||||||
// When conversation ended we either display a feedback form or
|
// When conversation ended we either display a feedback form or
|
||||||
// close the window. This is decided in the AppControllerView.
|
// close the window. This is decided in the AppControllerView.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
default: {
|
default:
|
||||||
var settingsMenuItems = [
|
{
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "room-conversation-wrapper desktop-room-wrapper",
|
||||||
|
onContextMenu: this.handleContextMenu },
|
||||||
|
React.createElement(
|
||||||
|
sharedViews.MediaLayoutView,
|
||||||
{
|
{
|
||||||
id: "edit",
|
|
||||||
enabled: !this.state.showEditContext,
|
|
||||||
visible: true,
|
|
||||||
onClick: this.handleEditContextClick
|
|
||||||
},
|
|
||||||
{ id: "help" }
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
React.createElement("div", {className: "room-conversation-wrapper desktop-room-wrapper",
|
|
||||||
onContextMenu: this.handleContextMenu},
|
|
||||||
React.createElement(sharedViews.MediaLayoutView, {
|
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
displayScreenShare: false,
|
displayScreenShare: false,
|
||||||
isLocalLoading: this._isLocalLoading(),
|
isLocalLoading: this._isLocalLoading(),
|
||||||
@@ -822,34 +602,20 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
screenShareMediaElement: this.state.screenShareMediaElement,
|
screenShareMediaElement: this.state.screenShareMediaElement,
|
||||||
screenSharePosterUrl: null,
|
screenSharePosterUrl: null,
|
||||||
showInitialContext: false,
|
showInitialContext: false,
|
||||||
useDesktopPaths: true},
|
useDesktopPaths: true },
|
||||||
React.createElement(sharedViews.ConversationToolbar, {
|
React.createElement(sharedViews.ConversationToolbar, {
|
||||||
audio: { enabled: !this.state.audioMuted, visible: true},
|
audio: { enabled: !this.state.audioMuted, visible: true },
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
hangup: this.leaveRoom,
|
hangup: this.leaveRoom,
|
||||||
publishStream: this.publishStream,
|
publishStream: this.publishStream,
|
||||||
settingsMenuItems: settingsMenuItems,
|
|
||||||
show: !shouldRenderEditContextView,
|
|
||||||
showHangup: this.props.chatWindowDetached,
|
showHangup: this.props.chatWindowDetached,
|
||||||
video: { enabled: !this.state.videoMuted, visible: true}}),
|
video: { enabled: !this.state.videoMuted, visible: true } }),
|
||||||
React.createElement(DesktopRoomInvitationView, {
|
React.createElement(DesktopRoomInvitationView, {
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
error: this.state.error,
|
error: this.state.error,
|
||||||
onAddContextClick: this.handleAddContextClick,
|
|
||||||
onEditContextClose: this.handleEditContextClose,
|
|
||||||
roomData: roomData,
|
roomData: roomData,
|
||||||
savingContext: this.state.savingContext,
|
|
||||||
show: shouldRenderInvitationOverlay,
|
show: shouldRenderInvitationOverlay,
|
||||||
showEditContext: shouldRenderInvitationOverlay && shouldRenderEditContextView,
|
socialShareProviders: this.state.socialShareProviders })
|
||||||
socialShareProviders: this.state.socialShareProviders}),
|
|
||||||
React.createElement(DesktopRoomEditContextView, {
|
|
||||||
dispatcher: this.props.dispatcher,
|
|
||||||
error: this.state.error,
|
|
||||||
onClose: this.handleEditContextClose,
|
|
||||||
roomData: roomData,
|
|
||||||
savingContext: this.state.savingContext,
|
|
||||||
show: !shouldRenderInvitationOverlay && shouldRenderEditContextView})
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -862,9 +628,7 @@ loop.roomViews = (function(mozL10n) {
|
|||||||
FailureInfoView: FailureInfoView,
|
FailureInfoView: FailureInfoView,
|
||||||
RoomFailureView: RoomFailureView,
|
RoomFailureView: RoomFailureView,
|
||||||
SocialShareDropdown: SocialShareDropdown,
|
SocialShareDropdown: SocialShareDropdown,
|
||||||
DesktopRoomEditContextView: DesktopRoomEditContextView,
|
|
||||||
DesktopRoomConversationView: DesktopRoomConversationView,
|
DesktopRoomConversationView: DesktopRoomConversationView,
|
||||||
DesktopRoomInvitationView: DesktopRoomInvitationView
|
DesktopRoomInvitationView: DesktopRoomInvitationView
|
||||||
};
|
};
|
||||||
|
}(document.mozL10n || navigator.mozL10n);
|
||||||
})(document.mozL10n || navigator.mozL10n);
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
<div id="main"></div>
|
<div id="main"></div>
|
||||||
|
|
||||||
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/react-0.13.3.js"></script>
|
<script type="text/javascript" src="shared/vendor/react.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/lodash-3.9.3.js"></script>
|
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/backbone-1.2.1.js"></script>
|
<script type="text/javascript" src="shared/vendor/backbone.js"></script>
|
||||||
<script type="text/javascript" src="shared/vendor/classnames-2.2.0.js"></script>
|
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||||
|
|||||||
@@ -7,4 +7,9 @@
|
|||||||
"destructuring": true,
|
"destructuring": true,
|
||||||
"forOf": true
|
"forOf": true
|
||||||
},
|
},
|
||||||
|
"rules": {
|
||||||
|
// This is useful for some of the tests, e.g.
|
||||||
|
// expect(new Foo()).to.Throw(/error/)
|
||||||
|
"no-new": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,10 @@
|
|||||||
describe("loop.conversation", function() {
|
describe("loop.conversation", function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var expect = chai.expect;
|
|
||||||
var FeedbackView = loop.feedbackViews.FeedbackView;
|
var FeedbackView = loop.feedbackViews.FeedbackView;
|
||||||
var TestUtils = React.addons.TestUtils;
|
var TestUtils = React.addons.TestUtils;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedModels = loop.shared.models;
|
var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet;
|
||||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
|
||||||
var fakeWindow, sandbox, getLoopPrefStub, setLoopPrefStub, mozL10nGet;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = LoopMochaUtils.createSandbox();
|
sandbox = LoopMochaUtils.createSandbox();
|
||||||
@@ -43,8 +40,6 @@ describe("loop.conversation", function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
StartAlerting: sinon.stub(),
|
|
||||||
StopAlerting: sinon.stub(),
|
|
||||||
EnsureRegistered: sinon.stub(),
|
EnsureRegistered: sinon.stub(),
|
||||||
GetAppVersionInfo: function() {
|
GetAppVersionInfo: function() {
|
||||||
return {
|
return {
|
||||||
@@ -53,7 +48,7 @@ describe("loop.conversation", function() {
|
|||||||
platform: "test"
|
platform: "test"
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
GetAudioBlob: sinon.spy(function(name) {
|
GetAudioBlob: sinon.spy(function() {
|
||||||
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });
|
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });
|
||||||
}),
|
}),
|
||||||
GetSelectedTabMetadata: function() {
|
GetSelectedTabMetadata: function() {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Loop desktop-local mocha tests</title>
|
<title>Loop desktop-local mocha tests</title>
|
||||||
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.2.5.css">
|
<link rel="stylesheet" media="all" href="/test/vendor/mocha.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mocha">
|
<div id="mocha">
|
||||||
@@ -14,22 +14,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="messages"></div>
|
<div id="messages"></div>
|
||||||
<div id="fixtures"></div>
|
<div id="fixtures"></div>
|
||||||
<script src="../../content/shared/vendor/lodash-3.9.3.js"></script>
|
<script src="/shared/vendor/lodash.js"></script>
|
||||||
<script src="../shared/loop_mocha_utils.js"></script>
|
<script src="/shared/test/loop_mocha_utils.js"></script>
|
||||||
<script>
|
<script>
|
||||||
LoopMochaUtils.trapErrors();
|
LoopMochaUtils.trapErrors();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- libs -->
|
<!-- libs -->
|
||||||
<script src="../../content/panels/vendor/l10n.js"></script>
|
|
||||||
<script src="../../content/shared/vendor/react-0.13.3.js"></script>
|
|
||||||
<script src="../../content/shared/vendor/classnames-2.2.0.js"></script>
|
|
||||||
<script src="../../content/shared/vendor/backbone-1.2.1.js"></script>
|
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<script src="../shared/vendor/mocha-2.2.5.js"></script>
|
<script src="/add-on/panels/vendor/l10n.js"></script>
|
||||||
<script src="../shared/vendor/chai-3.0.0.js"></script>
|
<script src="/shared/vendor/react.js"></script>
|
||||||
<script src="../shared/vendor/sinon-1.16.1.js"></script>
|
<script src="/shared/vendor/classnames.js"></script>
|
||||||
|
<script src="/shared/vendor/backbone.js"></script>
|
||||||
|
|
||||||
|
<!-- test dependencies -->
|
||||||
|
<script src="/test/vendor/mocha.js"></script>
|
||||||
|
<script src="/test/vendor/chai.js"></script>
|
||||||
|
<script src="/test/vendor/chai-as-promised.js"></script>
|
||||||
|
<script src="/test/vendor/sinon.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/*global chai,mocha */
|
/*global chai,mocha */
|
||||||
chai.config.includeStack = true;
|
chai.config.includeStack = true;
|
||||||
@@ -37,25 +41,25 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- App scripts -->
|
<!-- App scripts -->
|
||||||
<script src="../../content/shared/js/loopapi-client.js"></script>
|
<script src="/add-on/shared/js/loopapi-client.js"></script>
|
||||||
<script src="../../content/shared/js/utils.js"></script>
|
<script src="/add-on/shared/js/utils.js"></script>
|
||||||
<script src="../../content/shared/js/models.js"></script>
|
<script src="/add-on/shared/js/models.js"></script>
|
||||||
<script src="../../content/shared/js/mixins.js"></script>
|
<script src="/add-on/shared/js/mixins.js"></script>
|
||||||
<script src="../../content/shared/js/actions.js"></script>
|
<script src="/add-on/shared/js/actions.js"></script>
|
||||||
<script src="../../content/shared/js/validate.js"></script>
|
<script src="/add-on/shared/js/validate.js"></script>
|
||||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
<script src="/add-on/shared/js/dispatcher.js"></script>
|
||||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
<script src="/add-on/shared/js/otSdkDriver.js"></script>
|
||||||
<script src="../../content/shared/js/store.js"></script>
|
<script src="/add-on/shared/js/store.js"></script>
|
||||||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
<script src="/add-on/shared/js/activeRoomStore.js"></script>
|
||||||
<script src="../../content/shared/js/views.js"></script>
|
<script src="/add-on/shared/js/views.js"></script>
|
||||||
<script src="../../content/shared/js/textChatStore.js"></script>
|
<script src="/add-on/shared/js/textChatStore.js"></script>
|
||||||
<script src="../../content/shared/js/textChatView.js"></script>
|
<script src="/add-on/shared/js/textChatView.js"></script>
|
||||||
<script src="../../content/panels/js/conversationAppStore.js"></script>
|
<script src="/add-on/panels/js/conversationAppStore.js"></script>
|
||||||
<script src="../../content/panels/js/roomStore.js"></script>
|
<script src="/add-on/panels/js/roomStore.js"></script>
|
||||||
<script src="../../content/panels/js/roomViews.js"></script>
|
<script src="/add-on/panels/js/roomViews.js"></script>
|
||||||
<script src="../../content/panels/js/feedbackViews.js"></script>
|
<script src="/add-on/panels/js/feedbackViews.js"></script>
|
||||||
<script src="../../content/panels/js/conversation.js"></script>
|
<script src="/add-on/panels/js/conversation.js"></script>
|
||||||
<script src="../../content/panels/js/panel.js"></script>
|
<script src="/add-on/panels/js/panel.js"></script>
|
||||||
|
|
||||||
<!-- Test scripts -->
|
<!-- Test scripts -->
|
||||||
<script src="conversationAppStore_test.js"></script>
|
<script src="conversationAppStore_test.js"></script>
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ describe("loop.panel", function() {
|
|||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var TestUtils = React.addons.TestUtils;
|
var TestUtils = React.addons.TestUtils;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedUtils = loop.shared.utils;
|
|
||||||
|
|
||||||
var sandbox, notifications, requestStubs;
|
var sandbox, notifications, requestStubs;
|
||||||
var fakeXHR, fakeWindow, fakeEvent;
|
var fakeXHR, fakeWindow, fakeEvent;
|
||||||
var requests = [];
|
var requests = [];
|
||||||
var roomData, roomData2, roomList, roomName;
|
var roomData, roomData2, roomList, roomName;
|
||||||
var mozL10nGetSpy;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = LoopMochaUtils.createSandbox();
|
sandbox = LoopMochaUtils.createSandbox();
|
||||||
@@ -56,13 +54,16 @@ describe("loop.panel", function() {
|
|||||||
GetPluralRule: sinon.stub(),
|
GetPluralRule: sinon.stub(),
|
||||||
SetLoopPref: sinon.stub(),
|
SetLoopPref: sinon.stub(),
|
||||||
GetLoopPref: function(prefName) {
|
GetLoopPref: function(prefName) {
|
||||||
|
if (prefName === "debug.dispatcher") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
SetPanelHeight: function() { return null; },
|
SetPanelHeight: function() { return null; },
|
||||||
GetPluralForm: function() {
|
GetPluralForm: function() {
|
||||||
return "fakeText";
|
return "fakeText";
|
||||||
},
|
},
|
||||||
"Rooms:GetAll": function(version) {
|
"Rooms:GetAll": function() {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
"Rooms:PushSubscription": sinon.stub(),
|
"Rooms:PushSubscription": sinon.stub(),
|
||||||
@@ -184,14 +185,9 @@ describe("loop.panel", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("loop.panel.PanelView", function() {
|
describe("loop.panel.PanelView", function() {
|
||||||
var dispatcher, roomStore, callUrlData;
|
var dispatcher, roomStore;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
callUrlData = {
|
|
||||||
callUrl: "http://call.invalid/",
|
|
||||||
expiresAt: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatcher = new loop.Dispatcher();
|
dispatcher = new loop.Dispatcher();
|
||||||
roomStore = new loop.store.RoomStore(dispatcher, {
|
roomStore = new loop.store.RoomStore(dispatcher, {
|
||||||
constants: {}
|
constants: {}
|
||||||
@@ -216,7 +212,7 @@ describe("loop.panel", function() {
|
|||||||
var view = TestUtils.renderIntoDocument(
|
var view = TestUtils.renderIntoDocument(
|
||||||
React.createElement(loop.panel.SettingsDropdown));
|
React.createElement(loop.panel.SettingsDropdown));
|
||||||
|
|
||||||
expect(view.getDOMNode().querySelectorAll(".icon-account"))
|
expect(view.getDOMNode().querySelectorAll(".entry-settings-account"))
|
||||||
.to.have.length.of(0);
|
.to.have.length.of(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,19 +262,11 @@ describe("loop.panel", function() {
|
|||||||
expect(view.getDOMNode()).to.be.null;
|
expect(view.getDOMNode()).to.be.null;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should add ellipsis to text over 24chars", function() {
|
|
||||||
loop.storedRequests.GetUserProfile = { email: "reallyreallylongtext@example.com" };
|
|
||||||
var view = createTestPanelView();
|
|
||||||
var node = view.getDOMNode().querySelector(".user-identity");
|
|
||||||
|
|
||||||
expect(node.textContent).to.eql("reallyreallylongtext@exa…");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should warn when user profile is different from {} or null",
|
it("should warn when user profile is different from {} or null",
|
||||||
function() {
|
function() {
|
||||||
var warnstub = sandbox.stub(console, "warn");
|
var warnstub = sandbox.stub(console, "warn");
|
||||||
|
|
||||||
var view = TestUtils.renderIntoDocument(React.createElement(
|
TestUtils.renderIntoDocument(React.createElement(
|
||||||
loop.panel.AccountLink, {
|
loop.panel.AccountLink, {
|
||||||
fxAEnabled: false,
|
fxAEnabled: false,
|
||||||
userProfile: []
|
userProfile: []
|
||||||
@@ -294,7 +282,7 @@ describe("loop.panel", function() {
|
|||||||
function() {
|
function() {
|
||||||
var warnstub = sandbox.stub(console, "warn");
|
var warnstub = sandbox.stub(console, "warn");
|
||||||
|
|
||||||
var view = TestUtils.renderIntoDocument(React.createElement(
|
TestUtils.renderIntoDocument(React.createElement(
|
||||||
loop.panel.AccountLink, {
|
loop.panel.AccountLink, {
|
||||||
fxAEnabled: false,
|
fxAEnabled: false,
|
||||||
userProfile: {}
|
userProfile: {}
|
||||||
@@ -311,7 +299,7 @@ describe("loop.panel", function() {
|
|||||||
React.createElement(loop.panel.SettingsDropdown));
|
React.createElement(loop.panel.SettingsDropdown));
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginToFxAStub, logoutFromFxAStub, openFxASettingsStub;
|
var openFxASettingsStub;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
openFxASettingsStub = sandbox.stub();
|
openFxASettingsStub = sandbox.stub();
|
||||||
@@ -342,7 +330,7 @@ describe("loop.panel", function() {
|
|||||||
function() {
|
function() {
|
||||||
var view = mountTestComponent();
|
var view = mountTestComponent();
|
||||||
|
|
||||||
expect(view.getDOMNode().querySelectorAll(".icon-account"))
|
expect(view.getDOMNode().querySelectorAll(".entry-settings-account"))
|
||||||
.to.have.length.of(0);
|
.to.have.length.of(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -354,36 +342,48 @@ describe("loop.panel", function() {
|
|||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.LoginToFxA);
|
sinon.assert.calledOnce(requestStubs.LoginToFxA);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should close the menu on clicking sign in", function() {
|
||||||
|
var view = mountTestComponent();
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(view.getDOMNode()
|
||||||
|
.querySelector(".entry-settings-signin"));
|
||||||
|
|
||||||
|
expect(view.state.showMenu).eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should close the panel on clicking sign in", function() {
|
||||||
|
var view = mountTestComponent();
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(view.getDOMNode()
|
||||||
|
.querySelector(".entry-settings-signin"));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeWindow.close);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("UserLoggedIn", function() {
|
||||||
|
var view;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
||||||
|
view = mountTestComponent();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show a signout entry when user is authenticated", function() {
|
it("should show a signout entry when user is authenticated", function() {
|
||||||
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
expect(view.getDOMNode().querySelectorAll(".entry-settings-signout"))
|
||||||
|
.to.have.length.of(1);
|
||||||
var view = mountTestComponent();
|
expect(view.getDOMNode().querySelectorAll(".entry-settings-signin"))
|
||||||
|
.to.have.length.of(0);
|
||||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
|
||||||
"settings_menu_item_signout");
|
|
||||||
sinon.assert.neverCalledWith(document.mozL10n.get,
|
|
||||||
"settings_menu_item_signin");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show an account entry when user is authenticated", function() {
|
it("should show an account entry when user is authenticated", function() {
|
||||||
LoopMochaUtils.stubLoopRequest({
|
expect(view.getDOMNode().querySelectorAll(".entry-settings-account"))
|
||||||
GetUserProfile: function() { return { email: "test@example.com" }; }
|
.to.have.length.of(1);
|
||||||
});
|
|
||||||
|
|
||||||
var view = mountTestComponent();
|
|
||||||
|
|
||||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
|
||||||
"settings_menu_item_settings");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should open the FxA settings when the account entry is clicked",
|
it("should open the FxA settings when the account entry is clicked",
|
||||||
function() {
|
function() {
|
||||||
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
|
||||||
|
|
||||||
var view = mountTestComponent();
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(view.getDOMNode()
|
TestUtils.Simulate.click(view.getDOMNode()
|
||||||
.querySelector(".entry-settings-account"));
|
.querySelector(".entry-settings-account"));
|
||||||
|
|
||||||
@@ -391,15 +391,33 @@ describe("loop.panel", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should sign out the user on click when authenticated", function() {
|
it("should sign out the user on click when authenticated", function() {
|
||||||
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
|
||||||
var view = mountTestComponent();
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(view.getDOMNode()
|
TestUtils.Simulate.click(view.getDOMNode()
|
||||||
.querySelector(".entry-settings-signout"));
|
.querySelector(".entry-settings-signout"));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.LogoutFromFxA);
|
sinon.assert.calledOnce(requestStubs.LogoutFromFxA);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should close the dropdown menu on clicking sign out", function() {
|
||||||
|
LoopMochaUtils.stubLoopRequest({
|
||||||
|
GetUserProfile: function() { return { email: "test@example.com" }; }
|
||||||
|
});
|
||||||
|
|
||||||
|
view.setState({ showMenu: true });
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(view.getDOMNode()
|
||||||
|
.querySelector(".entry-settings-signout"));
|
||||||
|
|
||||||
|
expect(view.state.showMenu).eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not close the panel on clicking sign out", function() {
|
||||||
|
TestUtils.Simulate.click(view.getDOMNode()
|
||||||
|
.querySelector(".entry-settings-signout"));
|
||||||
|
|
||||||
|
sinon.assert.notCalled(fakeWindow.close);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Toggle Notifications", function() {
|
describe("Toggle Notifications", function() {
|
||||||
var view;
|
var view;
|
||||||
|
|
||||||
@@ -813,22 +831,6 @@ describe("loop.panel", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Room Entry click", function() {
|
|
||||||
var roomEntry, roomEntryNode;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
sandbox.stub(dispatcher, "dispatch");
|
|
||||||
|
|
||||||
roomEntry = mountRoomEntry({
|
|
||||||
dispatcher: dispatcher,
|
|
||||||
isOpenedRoom: false,
|
|
||||||
room: new loop.store.Room(roomData)
|
|
||||||
});
|
|
||||||
roomEntryNode = roomEntry.getDOMNode();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Room name updated", function() {
|
describe("Room name updated", function() {
|
||||||
it("should update room name", function() {
|
it("should update room name", function() {
|
||||||
var roomEntry = mountRoomEntry({
|
var roomEntry = mountRoomEntry({
|
||||||
@@ -953,7 +955,7 @@ describe("loop.panel", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should close the panel once a room is created and there is no error", function() {
|
it("should close the panel once a room is created and there is no error", function() {
|
||||||
var view = createTestComponent();
|
createTestComponent();
|
||||||
|
|
||||||
roomStore.setStoreState({ pendingCreation: true });
|
roomStore.setStoreState({ pendingCreation: true });
|
||||||
|
|
||||||
@@ -964,20 +966,10 @@ describe("loop.panel", function() {
|
|||||||
sinon.assert.calledOnce(fakeWindow.close);
|
sinon.assert.calledOnce(fakeWindow.close);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the no rooms view when no rooms available", function() {
|
it("should not render the room list view when no rooms available", function() {
|
||||||
var view = createTestComponent();
|
var view = createTestComponent();
|
||||||
var node = view.getDOMNode();
|
var node = view.getDOMNode();
|
||||||
|
expect(node.querySelectorAll(".room-list").length).to.eql(0);
|
||||||
expect(node.querySelectorAll(".room-list-empty").length).to.eql(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call mozL10n.get for room empty strings", function() {
|
|
||||||
var view = createTestComponent();
|
|
||||||
|
|
||||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
|
||||||
"no_conversations_message_heading2");
|
|
||||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
|
||||||
"no_conversations_start_message2");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display a loading animation when rooms are pending", function() {
|
it("should display a loading animation when rooms are pending", function() {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ describe("loop.store.RoomStore", function() {
|
|||||||
describe("MozLoop rooms event listeners", function() {
|
describe("MozLoop rooms event listeners", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
LoopMochaUtils.stubLoopRequest({
|
LoopMochaUtils.stubLoopRequest({
|
||||||
"Rooms:GetAll": function(version) {
|
"Rooms:GetAll": function() {
|
||||||
return fakeRoomList;
|
return fakeRoomList;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -226,8 +226,6 @@ describe("loop.store.RoomStore", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#createRoom", function() {
|
describe("#createRoom", function() {
|
||||||
var fakeLocalRoomId = "777";
|
|
||||||
var fakeOwner = "fake@invalid";
|
|
||||||
var fakeRoomCreationData;
|
var fakeRoomCreationData;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
@@ -320,6 +318,19 @@ describe("loop.store.RoomStore", function() {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should dispatch a CreateRoomError action if the operation fails with no result",
|
||||||
|
function() {
|
||||||
|
requestStubs["Rooms:Create"].returns();
|
||||||
|
|
||||||
|
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.CreateRoomError({
|
||||||
|
error: new Error("no result")
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
it("should log a telemetry event when the operation is successful", function() {
|
it("should log a telemetry event when the operation is successful", function() {
|
||||||
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
|
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
|
||||||
|
|
||||||
@@ -339,6 +350,16 @@ describe("loop.store.RoomStore", function() {
|
|||||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
||||||
"LOOP_ROOM_CREATE", 1);
|
"LOOP_ROOM_CREATE", 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should log a telemetry event when the operation fails with no result", function() {
|
||||||
|
requestStubs["Rooms:Create"].returns();
|
||||||
|
|
||||||
|
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
||||||
|
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
||||||
|
"LOOP_ROOM_CREATE", 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#createdRoom", function() {
|
describe("#createdRoom", function() {
|
||||||
@@ -725,14 +746,14 @@ describe("loop.store.RoomStore", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#openRoom", function() {
|
describe("#openRoom", function() {
|
||||||
var store, fakeMozLoop;
|
var store;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
store = new loop.store.RoomStore(dispatcher, { constants: {} });
|
store = new loop.store.RoomStore(dispatcher, { constants: {} });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should open the room via mozLoop", function() {
|
it("should open the room via mozLoop", function() {
|
||||||
dispatcher.dispatch(new sharedActions.OpenRoom({ roomToken: "42abc" }));
|
store.openRoom(new sharedActions.OpenRoom({ roomToken: "42abc" }));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs["Rooms:Open"]);
|
sinon.assert.calledOnce(requestStubs["Rooms:Open"]);
|
||||||
sinon.assert.calledWithExactly(requestStubs["Rooms:Open"], "42abc");
|
sinon.assert.calledWithExactly(requestStubs["Rooms:Open"], "42abc");
|
||||||
@@ -781,7 +802,7 @@ describe("loop.store.RoomStore", function() {
|
|||||||
expect(store.getStoreState().savingContext).to.eql(false);
|
expect(store.getStoreState().savingContext).to.eql(false);
|
||||||
|
|
||||||
LoopMochaUtils.stubLoopRequest({
|
LoopMochaUtils.stubLoopRequest({
|
||||||
"Rooms:Update": function(roomToken, roomData) {
|
"Rooms:Update": function() {
|
||||||
expect(store.getStoreState().savingContext).to.eql(true);
|
expect(store.getStoreState().savingContext).to.eql(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -799,7 +820,7 @@ describe("loop.store.RoomStore", function() {
|
|||||||
err.isError = true;
|
err.isError = true;
|
||||||
|
|
||||||
LoopMochaUtils.stubLoopRequest({
|
LoopMochaUtils.stubLoopRequest({
|
||||||
"Rooms:Update": function(roomToken, roomData) {
|
"Rooms:Update": function() {
|
||||||
expect(store.getStoreState().savingContext).to.eql(true);
|
expect(store.getStoreState().savingContext).to.eql(true);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,19 @@ describe("loop.roomViews", function() {
|
|||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var TestUtils = React.addons.TestUtils;
|
var TestUtils = React.addons.TestUtils;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedUtils = loop.shared.utils;
|
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||||
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
|
|
||||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||||
|
|
||||||
var sandbox, dispatcher, roomStore, activeRoomStore, view;
|
var sandbox, dispatcher, roomStore, activeRoomStore, view;
|
||||||
var clock, fakeWindow, requestStubs, fakeContextURL;
|
var clock, fakeWindow, requestStubs;
|
||||||
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = LoopMochaUtils.createSandbox();
|
sandbox = LoopMochaUtils.createSandbox();
|
||||||
|
|
||||||
LoopMochaUtils.stubLoopRequest(requestStubs = {
|
LoopMochaUtils.stubLoopRequest(requestStubs = {
|
||||||
GetAudioBlob: sinon.spy(function(name) {
|
GetAudioBlob: sinon.spy(function() {
|
||||||
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });
|
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });
|
||||||
}),
|
}),
|
||||||
GetLoopPref: sinon.stub(),
|
GetLoopPref: sinon.stub(),
|
||||||
@@ -79,11 +77,6 @@ describe("loop.roomViews", function() {
|
|||||||
textChatStore: textChatStore
|
textChatStore: textChatStore
|
||||||
});
|
});
|
||||||
|
|
||||||
fakeContextURL = {
|
|
||||||
description: "An invalid page",
|
|
||||||
location: "http://invalid.com",
|
|
||||||
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
|
|
||||||
};
|
|
||||||
sandbox.stub(dispatcher, "dispatch");
|
sandbox.stub(dispatcher, "dispatch");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -315,16 +308,6 @@ describe("loop.roomViews", function() {
|
|||||||
expect(copyBtn.textContent).eql("invite_copy_link_button");
|
expect(copyBtn.textContent).eql("invite_copy_link_button");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Edit Context", function() {
|
|
||||||
it("should show the edit context view", function() {
|
|
||||||
view = mountTestComponent({
|
|
||||||
showEditContext: true
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("DesktopRoomConversationView", function() {
|
describe("DesktopRoomConversationView", function() {
|
||||||
@@ -433,7 +416,7 @@ describe("loop.roomViews", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#componentWillUpdate", function() {
|
describe("#componentWillUpdate", function() {
|
||||||
function expectActionDispatched(component) {
|
function expectActionDispatched() {
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
sinon.match.instanceOf(sharedActions.SetupStreamElements));
|
sinon.match.instanceOf(sharedActions.SetupStreamElements));
|
||||||
@@ -441,29 +424,29 @@ describe("loop.roomViews", function() {
|
|||||||
|
|
||||||
it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state is entered", function() {
|
it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state is entered", function() {
|
||||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
||||||
var component = mountTestComponent();
|
mountTestComponent();
|
||||||
|
|
||||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
|
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
|
||||||
|
|
||||||
expectActionDispatched(component);
|
expectActionDispatched();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is re-entered", function() {
|
it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is re-entered", function() {
|
||||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.ENDED });
|
activeRoomStore.setStoreState({ roomState: ROOM_STATES.ENDED });
|
||||||
var component = mountTestComponent();
|
mountTestComponent();
|
||||||
|
|
||||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
|
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
|
||||||
|
|
||||||
expectActionDispatched(component);
|
expectActionDispatched();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch a `StartBrowserShare` action when the SESSION_CONNECTED state is entered", function() {
|
it("should dispatch a `StartBrowserShare` action when the SESSION_CONNECTED state is entered", function() {
|
||||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
||||||
var component = mountTestComponent();
|
mountTestComponent();
|
||||||
|
|
||||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
|
activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
|
||||||
|
|
||||||
expectActionDispatched("startBrowserShare");
|
expectActionDispatched();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -680,7 +663,6 @@ describe("loop.roomViews", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Room name priority", function() {
|
describe("Room name priority", function() {
|
||||||
var roomEntry;
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
activeRoomStore.setStoreState({
|
activeRoomStore.setStoreState({
|
||||||
participants: [{}],
|
participants: [{}],
|
||||||
@@ -721,29 +703,6 @@ describe("loop.roomViews", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Edit Context", function() {
|
|
||||||
it("should show the form when the edit button is clicked", function() {
|
|
||||||
view = mountTestComponent();
|
|
||||||
var node = view.getDOMNode();
|
|
||||||
|
|
||||||
expect(node.querySelector(".room-context")).to.eql(null);
|
|
||||||
|
|
||||||
var editButton = node.querySelector(".settings-menu > li.entry-settings-edit");
|
|
||||||
React.addons.TestUtils.Simulate.click(editButton);
|
|
||||||
|
|
||||||
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not have a settings menu when the edit button is clicked", function() {
|
|
||||||
view = mountTestComponent();
|
|
||||||
|
|
||||||
var editButton = view.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
|
|
||||||
React.addons.TestUtils.Simulate.click(editButton);
|
|
||||||
|
|
||||||
expect(view.getDOMNode().querySelector(".settings-menu")).to.eql(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SocialShareDropdown", function() {
|
describe("SocialShareDropdown", function() {
|
||||||
@@ -839,175 +798,4 @@ describe("loop.roomViews", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("DesktopRoomEditContextView", function() {
|
|
||||||
function mountTestComponent(props) {
|
|
||||||
props = _.extend({
|
|
||||||
dispatcher: dispatcher,
|
|
||||||
savingContext: false,
|
|
||||||
show: true,
|
|
||||||
roomData: {
|
|
||||||
roomToken: "fakeToken"
|
|
||||||
}
|
|
||||||
}, props);
|
|
||||||
return TestUtils.renderIntoDocument(
|
|
||||||
React.createElement(loop.roomViews.DesktopRoomEditContextView, props));
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("#render", function() {
|
|
||||||
it("should not render the component when 'show' is false", function() {
|
|
||||||
view = mountTestComponent({
|
|
||||||
show: false
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(view.getDOMNode()).to.eql(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should close the view when the cancel button is clicked", function() {
|
|
||||||
view = mountTestComponent({
|
|
||||||
roomData: { roomContextUrls: [fakeContextURL] }
|
|
||||||
});
|
|
||||||
|
|
||||||
var closeBtn = view.getDOMNode().querySelector(".button-cancel");
|
|
||||||
React.addons.TestUtils.Simulate.click(closeBtn);
|
|
||||||
expect(view.getDOMNode()).to.eql(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render the view correctly", function() {
|
|
||||||
var roomName = "Hello, is it me you're looking for?";
|
|
||||||
view = mountTestComponent({
|
|
||||||
roomData: {
|
|
||||||
roomName: roomName,
|
|
||||||
roomContextUrls: [fakeContextURL]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var node = view.getDOMNode();
|
|
||||||
expect(node.querySelector("form")).to.not.eql(null);
|
|
||||||
// Check the contents of the form fields.
|
|
||||||
expect(node.querySelector(".room-context-name").value).to.eql(roomName);
|
|
||||||
expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
|
|
||||||
expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Update Room", function() {
|
|
||||||
var roomNameBox;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
view = mountTestComponent({
|
|
||||||
editMode: true,
|
|
||||||
roomData: {
|
|
||||||
roomToken: "fakeToken",
|
|
||||||
roomName: "fakeName",
|
|
||||||
roomContextUrls: [fakeContextURL]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
roomNameBox = view.getDOMNode().querySelector(".room-context-name");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should dispatch a UpdateRoomContext action when the save button is clicked",
|
|
||||||
function() {
|
|
||||||
React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
|
|
||||||
value: "reallyFake"
|
|
||||||
} });
|
|
||||||
|
|
||||||
React.addons.TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-accept"));
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
||||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
|
||||||
new sharedActions.UpdateRoomContext({
|
|
||||||
roomToken: "fakeToken",
|
|
||||||
newRoomName: "reallyFake",
|
|
||||||
newRoomDescription: fakeContextURL.description,
|
|
||||||
newRoomURL: fakeContextURL.location,
|
|
||||||
newRoomThumbnail: fakeContextURL.thumbnail
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should dispatch a UpdateRoomContext action when Enter key is pressed",
|
|
||||||
function() {
|
|
||||||
React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
|
|
||||||
value: "reallyFake"
|
|
||||||
} });
|
|
||||||
|
|
||||||
TestUtils.Simulate.keyDown(roomNameBox, { key: "Enter", which: 13 });
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
||||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
|
||||||
new sharedActions.UpdateRoomContext({
|
|
||||||
roomToken: "fakeToken",
|
|
||||||
newRoomName: "reallyFake",
|
|
||||||
newRoomDescription: fakeContextURL.description,
|
|
||||||
newRoomURL: fakeContextURL.location,
|
|
||||||
newRoomThumbnail: fakeContextURL.thumbnail
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should close the edit form when context was saved successfully", function(done) {
|
|
||||||
view.setProps({ savingContext: true }, function() {
|
|
||||||
var node = view.getDOMNode();
|
|
||||||
// The button should show up as disabled.
|
|
||||||
expect(node.querySelector(".button-accept").hasAttribute("disabled")).to.eql(true);
|
|
||||||
|
|
||||||
// Now simulate a successful save.
|
|
||||||
view.setProps({ savingContext: false }, function() {
|
|
||||||
// The 'show flag should be updated.
|
|
||||||
expect(view.state.show).to.eql(false);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#handleContextClick", function() {
|
|
||||||
var fakeEvent;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
fakeEvent = {
|
|
||||||
preventDefault: sinon.stub(),
|
|
||||||
stopPropagation: sinon.stub()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not attempt to open a URL when none is attached", function() {
|
|
||||||
view = mountTestComponent({
|
|
||||||
roomData: {
|
|
||||||
roomToken: "fakeToken",
|
|
||||||
roomName: "fakeName"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
view.handleContextClick(fakeEvent);
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(fakeEvent.preventDefault);
|
|
||||||
sinon.assert.calledOnce(fakeEvent.stopPropagation);
|
|
||||||
|
|
||||||
sinon.assert.notCalled(requestStubs.OpenURL);
|
|
||||||
sinon.assert.notCalled(requestStubs.TelemetryAddValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should open a URL", function() {
|
|
||||||
view = mountTestComponent({
|
|
||||||
roomData: {
|
|
||||||
roomToken: "fakeToken",
|
|
||||||
roomName: "fakeName",
|
|
||||||
roomContextUrls: [fakeContextURL]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
view.handleContextClick(fakeEvent);
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(fakeEvent.preventDefault);
|
|
||||||
sinon.assert.calledOnce(fakeEvent.stopPropagation);
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.OpenURL);
|
|
||||||
sinon.assert.calledWithExactly(requestStubs.OpenURL, fakeContextURL.location);
|
|
||||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
|
||||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
|
||||||
"LOOP_ROOM_CONTEXT_CLICK", 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# need to get this dir in the path so that we make the import work
|
# need to get this dir in the path so that we make the import work
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'shared'))
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'shared', 'test'))
|
||||||
|
|
||||||
from frontend_tester import BaseTestFrontendUnits
|
from frontend_tester import BaseTestFrontendUnits
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ class TestDesktopUnits(BaseTestFrontendUnits):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDesktopUnits, self).setUp()
|
super(TestDesktopUnits, self).setUp()
|
||||||
self.set_server_prefix("../desktop-local/")
|
self.set_server_prefix("../../../../")
|
||||||
|
|
||||||
def test_units(self):
|
def test_units(self):
|
||||||
self.check_page("index.html")
|
self.check_page("chrome/content/panels/test/index.html")
|
||||||
|
|||||||
@@ -221,14 +221,19 @@
|
|||||||
get: translateString,
|
get: translateString,
|
||||||
|
|
||||||
// get the document language
|
// get the document language
|
||||||
getLanguage: function() { return gLanguage; },
|
language: {
|
||||||
|
set code(lang) {
|
||||||
// get the direction (ltr|rtl) of the current language
|
throw new Error("unsupported");
|
||||||
getDirection: function() {
|
},
|
||||||
|
get code() {
|
||||||
|
return gLanguage;
|
||||||
|
},
|
||||||
|
get direction() {
|
||||||
// http://www.w3.org/International/questions/qa-scripts
|
// http://www.w3.org/International/questions/qa-scripts
|
||||||
// Arabic, Hebrew, Farsi, Pashto, Urdu
|
// Arabic, Hebrew, Farsi, Pashto, Urdu
|
||||||
var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
|
var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
|
||||||
return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
|
return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// translate an element or document fragment
|
// translate an element or document fragment
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ pref("loop.enabled", true);
|
|||||||
pref("loop.textChat.enabled", true);
|
pref("loop.textChat.enabled", true);
|
||||||
pref("loop.server", "https://loop.services.mozilla.com/v0");
|
pref("loop.server", "https://loop.services.mozilla.com/v0");
|
||||||
pref("loop.linkClicker.url", "https://hello.firefox.com/");
|
pref("loop.linkClicker.url", "https://hello.firefox.com/");
|
||||||
pref("loop.gettingStarted.latestFTUVersion", 0);
|
pref("loop.gettingStarted.latestFTUVersion", 1);
|
||||||
pref("loop.facebook.shareUrl", "https://www.facebook.com/sharer/sharer.php?u=%ROOM_URL%");
|
pref("loop.facebook.shareUrl", "https://www.facebook.com/sharer/sharer.php?u=%ROOM_URL%");
|
||||||
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
||||||
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
||||||
@@ -10,7 +10,6 @@ pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
|
|||||||
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
|
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
|
||||||
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
|
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
|
||||||
pref("loop.do_not_disturb", false);
|
pref("loop.do_not_disturb", false);
|
||||||
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
|
|
||||||
pref("loop.retry_delay.start", 60000);
|
pref("loop.retry_delay.start", 60000);
|
||||||
pref("loop.retry_delay.limit", 300000);
|
pref("loop.retry_delay.limit", 300000);
|
||||||
pref("loop.ping.interval", 1800000);
|
pref("loop.ping.interval", 1800000);
|
||||||
@@ -30,5 +29,5 @@ pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src * data:; font
|
|||||||
#endif
|
#endif
|
||||||
pref("loop.fxa_oauth.tokendata", "");
|
pref("loop.fxa_oauth.tokendata", "");
|
||||||
pref("loop.fxa_oauth.profile", "");
|
pref("loop.fxa_oauth.profile", "");
|
||||||
pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
|
pref("loop.support_url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cobrowsing");
|
||||||
pref("loop.browserSharing.showInfoBar", true);
|
pref("loop.browserSharing.showInfoBar", true);
|
||||||
|
|||||||
@@ -587,10 +587,6 @@ html[dir="rtl"] .context-wrapper > .context-preview {
|
|||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clicks-allowed.context-wrapper:hover {
|
|
||||||
border: 2px solid #5cccee;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Only underline the url, not the associated text */
|
/* Only underline the url, not the associated text */
|
||||||
.clicks-allowed.context-wrapper:hover > .context-info > .context-url {
|
.clicks-allowed.context-wrapper:hover > .context-info > .context-url {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ button::-moz-focus-inner {
|
|||||||
z-index: 1020; /* required to have it superimposed to the video element */
|
z-index: 1020; /* required to have it superimposed to the video element */
|
||||||
border: 0;
|
border: 0;
|
||||||
left: 1.2rem;
|
left: 1.2rem;
|
||||||
right: 1.2rem;
|
|
||||||
height: 2.4rem;
|
height: 2.4rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 1.2rem;
|
bottom: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[dir="rtl"] .conversation-toolbar {
|
html[dir="rtl"] .conversation-toolbar {
|
||||||
@@ -95,14 +95,6 @@ html[dir="rtl"] .conversation-toolbar-media-btn-group-box > button:first-child:h
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-toolbar-btn-box.btn-edit-entry {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[dir="rtl"] .conversation-toolbar-btn-box.btn-edit-entry {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* conversationViews.jsx */
|
/* conversationViews.jsx */
|
||||||
|
|
||||||
.conversation-toolbar .btn-hangup {
|
.conversation-toolbar .btn-hangup {
|
||||||
@@ -168,8 +160,7 @@ html[dir="rtl"] .conversation-toolbar-btn-box.btn-edit-entry {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-action-group > .btn,
|
.call-action-group > .btn {
|
||||||
.room-context > .btn {
|
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
@@ -509,92 +500,15 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
|||||||
margin: 0 1rem 1.5rem 1rem;
|
margin: 0 1rem 1.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-context {
|
.room-invitation-content {
|
||||||
background: #fff;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
font-size: .9em;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
align-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
/* Make the context view float atop the video elements. */
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-invitation-overlay .room-context {
|
|
||||||
position: relative;
|
|
||||||
left: auto;
|
|
||||||
bottom: auto;
|
|
||||||
flex: 0 1 auto;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-context > .error-display-area.error {
|
|
||||||
display: block;
|
|
||||||
background-color: rgba(215,67,69,.8);
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-context > .error-display-area {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-context > .error-display-area.error {
|
|
||||||
margin: 1em 0 .5em 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 1px 1px 0 rgba(0,0,0,.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-invitation-content,
|
|
||||||
.room-context-header {
|
|
||||||
color: #333;
|
color: #333;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-context > form {
|
.room-invitation-content > p {
|
||||||
margin-bottom: 1rem;
|
margin-left: 10px;
|
||||||
padding: .5rem;
|
margin-right: 10px;
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-context > form > textarea,
|
|
||||||
.room-context > form > input[type="text"] {
|
|
||||||
border: 1px solid #c3c3c3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: none;
|
|
||||||
color: #4a4a4a;
|
|
||||||
display: block;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
height: 2.6rem;
|
|
||||||
margin: 10px 0;
|
|
||||||
outline: none;
|
|
||||||
padding: 6px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-context > form > textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
height: 5.2rem;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-context > form > textarea::-moz-placeholder,
|
|
||||||
.room-context > form > input::-moz-placeholder {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-context > form > textarea:focus,
|
|
||||||
.room-context > form > input:focus {
|
|
||||||
border: 0.1rem solid #5cccee;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-layout {
|
.media-layout {
|
||||||
@@ -802,8 +716,16 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* e.g. very narrow widths similar to conversation window */
|
/* e.g. very narrow widths similar to conversation window.
|
||||||
@media screen and (max-width:350px) {
|
Note: on some displays (e.g. windows / medium size) the width
|
||||||
|
may be very slightly over the expected width, so we add on 2px
|
||||||
|
just in case. */
|
||||||
|
@media screen and (max-width:352px) {
|
||||||
|
.conversation-toolbar {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
.media-layout > .media-wrapper {
|
.media-layout > .media-wrapper {
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
}
|
}
|
||||||
@@ -905,23 +827,23 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
|||||||
/* aligns paragraph to right side */
|
/* aligns paragraph to right side */
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 4px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-chat-entry.received {
|
.text-chat-entry.received {
|
||||||
margin-left: 2px;
|
margin-left: 10px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[dir="rtl"] .text-chat-entry.sent {
|
html[dir="rtl"] .text-chat-entry.sent {
|
||||||
margin-left: 5px;
|
margin-left: 10px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
html[dir="rtl"] .text-chat-entry.received {
|
html[dir="rtl"] .text-chat-entry.received {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 5px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-chat-entry > p {
|
.text-chat-entry > p {
|
||||||
@@ -940,6 +862,11 @@ html[dir="rtl"] .text-chat-entry.received {
|
|||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-chat-entry > .context-wrapper {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.text-chat-entry.sent > p {
|
.text-chat-entry.sent > p {
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
@@ -1056,6 +983,21 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
|
|||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Context updated */
|
||||||
|
.text-chat-entry > .context-content > .context-wrapper {
|
||||||
|
max-width: 300px;
|
||||||
|
min-height: 50px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(0,0,0,.10);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 1px 1px rgba(0,0,0,.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-chat-entry.received > .context-content > .context-wrapper {
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
.text-chat-header.special.room-name {
|
.text-chat-header.special.room-name {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -1120,8 +1062,11 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
|
|||||||
border-top: 1px solid #66c9f2;
|
border-top: 1px solid #66c9f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* e.g. very narrow widths similar to conversation window */
|
/* e.g. very narrow widths similar to conversation window.
|
||||||
@media screen and (max-width:350px) {
|
Note: on some displays (e.g. windows / medium size) the width
|
||||||
|
may be very slightly over the expected width, so we add on 2px
|
||||||
|
just in case. */
|
||||||
|
@media screen and (max-width:352px) {
|
||||||
.text-chat-view {
|
.text-chat-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
|
|||||||
@@ -326,6 +326,7 @@ loop.shared.actions = (function() {
|
|||||||
// newRoomDescription: String, Optional.
|
// newRoomDescription: String, Optional.
|
||||||
// newRoomThumbnail: String, Optional.
|
// newRoomThumbnail: String, Optional.
|
||||||
// newRoomURL: String Optional.
|
// newRoomURL: String Optional.
|
||||||
|
// sentTimestamp: String, Optional.
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
* and decryption is complete.
|
* and decryption is complete.
|
||||||
*/
|
*/
|
||||||
_getRoomDataForStandalone: function(roomCryptoKey) {
|
_getRoomDataForStandalone: function(roomCryptoKey) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve) {
|
||||||
loop.request("Rooms:Get", this._storeState.roomToken).then(function(result) {
|
loop.request("Rooms:Get", this._storeState.roomToken).then(function(result) {
|
||||||
if (result.isError) {
|
if (result.isError) {
|
||||||
resolve(new sharedActions.RoomFailure({
|
resolve(new sharedActions.RoomFailure({
|
||||||
@@ -438,7 +438,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
roomInfoData.roomName = realResult.roomName;
|
roomInfoData.roomName = realResult.roomName;
|
||||||
|
|
||||||
resolve(roomInfoData);
|
resolve(roomInfoData);
|
||||||
}, function(error) {
|
}, function() {
|
||||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
|
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
|
||||||
resolve(roomInfoData);
|
resolve(roomInfoData);
|
||||||
});
|
});
|
||||||
@@ -454,7 +454,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
* if Firefox can handle the room.
|
* if Firefox can handle the room.
|
||||||
*/
|
*/
|
||||||
_promiseDetectUserAgentHandles: function() {
|
_promiseDetectUserAgentHandles: function() {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve) {
|
||||||
function resolveWithNotHandlingResponse() {
|
function resolveWithNotHandlingResponse() {
|
||||||
resolve(new sharedActions.UserAgentHandlesRoom({
|
resolve(new sharedActions.UserAgentHandlesRoom({
|
||||||
handlesRoom: false
|
handlesRoom: false
|
||||||
@@ -568,9 +568,8 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
/**
|
/**
|
||||||
* Handles the deletion of a room, notified by the Loop rooms API.
|
* Handles the deletion of a room, notified by the Loop rooms API.
|
||||||
*
|
*
|
||||||
* @param {Object} roomData The roomData of the deleted room
|
|
||||||
*/
|
*/
|
||||||
_handleRoomDelete: function(roomData) {
|
_handleRoomDelete: function() {
|
||||||
this._sdkDriver.forceDisconnectAll(function() {
|
this._sdkDriver.forceDisconnectAll(function() {
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
@@ -926,7 +925,8 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
|
|
||||||
// The browser being shared changed, so update to the new context
|
// The browser being shared changed, so update to the new context
|
||||||
loop.request("GetSelectedTabMetadata").then(function(meta) {
|
loop.request("GetSelectedTabMetadata").then(function(meta) {
|
||||||
if (!meta) {
|
// Avoid sending the event if there is no data nor participants nor url
|
||||||
|
if (!meta || !meta.url || !this._hasParticipants()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -951,7 +951,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
*
|
*
|
||||||
* @param {sharedActions.StartBrowserShare} actionData
|
* @param {sharedActions.StartBrowserShare} actionData
|
||||||
*/
|
*/
|
||||||
startBrowserShare: function(actionData) {
|
startBrowserShare: function() {
|
||||||
// For the unit test we already set the state here, instead of indirectly
|
// For the unit test we already set the state here, instead of indirectly
|
||||||
// via an action, because actions are queued thus depending on the
|
// via an action, because actions are queued thus depending on the
|
||||||
// asynchronous nature of `loop.request`.
|
// asynchronous nature of `loop.request`.
|
||||||
@@ -960,9 +960,6 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
state: SCREEN_SHARE_STATES.PENDING
|
state: SCREEN_SHARE_STATES.PENDING
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var options = {
|
|
||||||
videoSource: "browser"
|
|
||||||
};
|
|
||||||
this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
|
this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
|
||||||
|
|
||||||
// Set up a listener for watching screen shares. This will get notified
|
// Set up a listener for watching screen shares. This will get notified
|
||||||
@@ -1224,6 +1221,24 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
*/
|
*/
|
||||||
sendTextChatMessage: function(actionData) {
|
sendTextChatMessage: function(actionData) {
|
||||||
this._handleTextChatMessage(actionData);
|
this._handleTextChatMessage(actionData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the room is empty or has participants.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_hasParticipants: function() {
|
||||||
|
// Update the participants to just the owner.
|
||||||
|
var participants = this.getStoreState("participants");
|
||||||
|
if (participants) {
|
||||||
|
participants = participants.filter(function(participant) {
|
||||||
|
return !participant.owner;
|
||||||
|
});
|
||||||
|
|
||||||
|
return participants.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -5,7 +7,7 @@
|
|||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.shared = loop.shared || {};
|
loop.shared = loop.shared || {};
|
||||||
loop.shared.views = loop.shared.views || {};
|
loop.shared.views = loop.shared.views || {};
|
||||||
loop.shared.views.LinkifiedTextView = (function() {
|
loop.shared.views.LinkifiedTextView = function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,7 +15,10 @@ loop.shared.views.LinkifiedTextView = (function() {
|
|||||||
* links starting with http://, https://, or ftp:// as actual clickable
|
* links starting with http://, https://, or ftp:// as actual clickable
|
||||||
* links inside a <p> container.
|
* links inside a <p> container.
|
||||||
*/
|
*/
|
||||||
var LinkifiedTextView = React.createClass({displayName: "LinkifiedTextView",
|
|
||||||
|
var LinkifiedTextView = React.createClass({
|
||||||
|
displayName: "LinkifiedTextView",
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// Call this instead of allowing the default <a> click semantics, if
|
// Call this instead of allowing the default <a> click semantics, if
|
||||||
// given. Also causes sendReferrer and suppressTarget attributes to be
|
// given. Also causes sendReferrer and suppressTarget attributes to be
|
||||||
@@ -28,18 +33,16 @@ loop.shared.views.LinkifiedTextView = (function() {
|
|||||||
suppressTarget: React.PropTypes.bool
|
suppressTarget: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [
|
mixins: [React.addons.PureRenderMixin],
|
||||||
React.addons.PureRenderMixin
|
|
||||||
],
|
|
||||||
|
|
||||||
_handleClickEvent: function(e) {
|
_handleClickEvent: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
this.props.linkClickHandler(e.currentTarget.href);
|
this.props.linkClickHandler(e.currentTarget.href);
|
||||||
},
|
},
|
||||||
|
|
||||||
_generateLinkAttributes: function(href) {
|
_generateLinkAttributes: function (href) {
|
||||||
var linkAttributes = {
|
var linkAttributes = {
|
||||||
href: href
|
href: href
|
||||||
};
|
};
|
||||||
@@ -71,7 +74,7 @@ loop.shared.views.LinkifiedTextView = (function() {
|
|||||||
*
|
*
|
||||||
* @returns {Array} of strings and React <a> elements in order.
|
* @returns {Array} of strings and React <a> elements in order.
|
||||||
*/
|
*/
|
||||||
parseStringToElements: function(s) {
|
parseStringToElements: function (s) {
|
||||||
var elements = [];
|
var elements = [];
|
||||||
var result = loop.shared.urlRegExps.fullUrlMatch.exec(s);
|
var result = loop.shared.urlRegExps.fullUrlMatch.exec(s);
|
||||||
var reactElementsCounter = 0; // For giving keys to each ReactElement.
|
var reactElementsCounter = 0; // For giving keys to each ReactElement.
|
||||||
@@ -85,12 +88,12 @@ loop.shared.views.LinkifiedTextView = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push the first link itself, and advance the string pointer again.
|
// Push the first link itself, and advance the string pointer again.
|
||||||
elements.push(
|
elements.push(React.createElement(
|
||||||
React.createElement("a", React.__spread({}, this._generateLinkAttributes(result[0]) ,
|
"a",
|
||||||
{key: reactElementsCounter++}),
|
_extends({}, this._generateLinkAttributes(result[0]), {
|
||||||
|
key: reactElementsCounter++ }),
|
||||||
result[0]
|
result[0]
|
||||||
)
|
));
|
||||||
);
|
|
||||||
s = s.substr(result[0].length);
|
s = s.substr(result[0].length);
|
||||||
|
|
||||||
// Check for another link, and perhaps continue...
|
// Check for another link, and perhaps continue...
|
||||||
@@ -104,13 +107,14 @@ loop.shared.views.LinkifiedTextView = (function() {
|
|||||||
return elements;
|
return elements;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("p", null, this.parseStringToElements(this.props.rawText))
|
"p",
|
||||||
|
null,
|
||||||
|
this.parseStringToElements(this.props.rawText)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return LinkifiedTextView;
|
return LinkifiedTextView;
|
||||||
|
}();
|
||||||
})();
|
|
||||||
|
|||||||
@@ -133,11 +133,12 @@ loop.OTSdkDriver = (function() {
|
|||||||
// the initial connect of the session. This saves time when setting up
|
// the initial connect of the session. This saves time when setting up
|
||||||
// the media.
|
// the media.
|
||||||
this.publisher = this.sdk.initPublisher(this._mockPublisherEl,
|
this.publisher = this.sdk.initPublisher(this._mockPublisherEl,
|
||||||
_.extend(this._getDataChannelSettings, this._getCopyPublisherConfig));
|
_.extend(this._getDataChannelSettings, this._getCopyPublisherConfig),
|
||||||
|
this._onPublishComplete.bind(this));
|
||||||
|
|
||||||
this.publisher.on("streamCreated", this._onLocalStreamCreated.bind(this));
|
this.publisher.on("streamCreated", this._onLocalStreamCreated.bind(this));
|
||||||
this.publisher.on("streamDestroyed", this._onLocalStreamDestroyed.bind(this));
|
this.publisher.on("streamDestroyed", this._onLocalStreamDestroyed.bind(this));
|
||||||
this.publisher.on("accessAllowed", this._onPublishComplete.bind(this));
|
this.publisher.on("accessAllowed", this._onPublishAllowed.bind(this));
|
||||||
this.publisher.on("accessDenied", this._onPublishDenied.bind(this));
|
this.publisher.on("accessDenied", this._onPublishDenied.bind(this));
|
||||||
this.publisher.on("accessDialogOpened",
|
this.publisher.on("accessDialogOpened",
|
||||||
this._onAccessDialogOpened.bind(this));
|
this._onAccessDialogOpened.bind(this));
|
||||||
@@ -185,9 +186,9 @@ loop.OTSdkDriver = (function() {
|
|||||||
this._mockScreenSharePreviewEl = document.createElement("div");
|
this._mockScreenSharePreviewEl = document.createElement("div");
|
||||||
|
|
||||||
this.screenshare = this.sdk.initPublisher(this._mockScreenSharePreviewEl,
|
this.screenshare = this.sdk.initPublisher(this._mockScreenSharePreviewEl,
|
||||||
config);
|
config, this._onScreenSharePublishComplete.bind(this));
|
||||||
this.screenshare.on("accessAllowed", this._onScreenShareGranted.bind(this));
|
this.screenshare.on("accessAllowed", this._onScreenShareGranted.bind(this));
|
||||||
this.screenshare.on("accessDenied", this._onScreenShareDenied.bind(this));
|
this.screenshare.on("accessDenied", this._onScreenSharePublishError.bind(this));
|
||||||
this.screenshare.on("streamCreated", this._onScreenShareStreamCreated.bind(this));
|
this.screenshare.on("streamCreated", this._onScreenShareStreamCreated.bind(this));
|
||||||
|
|
||||||
this._noteSharingState(options.videoSource, true);
|
this._noteSharingState(options.videoSource, true);
|
||||||
@@ -902,7 +903,7 @@ loop.OTSdkDriver = (function() {
|
|||||||
*
|
*
|
||||||
* @param {OT.Event} event
|
* @param {OT.Event} event
|
||||||
*/
|
*/
|
||||||
_onPublishComplete: function(event) {
|
_onPublishAllowed: function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this._publisherReady = true;
|
this._publisherReady = true;
|
||||||
|
|
||||||
@@ -911,6 +912,33 @@ loop.OTSdkDriver = (function() {
|
|||||||
this._maybePublishLocalStream();
|
this._maybePublishLocalStream();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Handles publisher Complete.
|
||||||
|
*
|
||||||
|
* @param {Error} error An OT error object, null if there was no error.
|
||||||
|
*/
|
||||||
|
_onPublishComplete: function(error) {
|
||||||
|
if (!error) {
|
||||||
|
// Nothing to do for the success case.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(error.message && error.message === "DENIED")) {
|
||||||
|
// We free up the publisher here in case the store wants to try
|
||||||
|
// grabbing the media again.
|
||||||
|
if (this.publisher) {
|
||||||
|
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
|
||||||
|
this.publisher.destroy();
|
||||||
|
delete this.publisher;
|
||||||
|
delete this._mockPublisherEl;
|
||||||
|
}
|
||||||
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
|
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||||
|
}));
|
||||||
|
this._notifyMetricsEvent("sdk.exception." + error.code + "." + error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles publishing of media being denied.
|
* Handles publishing of media being denied.
|
||||||
*
|
*
|
||||||
@@ -936,26 +964,6 @@ loop.OTSdkDriver = (function() {
|
|||||||
}));
|
}));
|
||||||
this._notifyMetricsEvent("sdk.exception." + event.code);
|
this._notifyMetricsEvent("sdk.exception." + event.code);
|
||||||
break;
|
break;
|
||||||
case OT.ExceptionCodes.UNABLE_TO_PUBLISH:
|
|
||||||
if (event.message === "GetUserMedia") {
|
|
||||||
// We free up the publisher here in case the store wants to try
|
|
||||||
// grabbing the media again.
|
|
||||||
if (this.publisher) {
|
|
||||||
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
|
|
||||||
this.publisher.destroy();
|
|
||||||
delete this.publisher;
|
|
||||||
delete this._mockPublisherEl;
|
|
||||||
}
|
|
||||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
|
||||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
|
||||||
}));
|
|
||||||
// No exception logging as this is a handled event.
|
|
||||||
} else {
|
|
||||||
// We need to log the message so that we can understand where the exception
|
|
||||||
// is coming from. Potentially a temporary addition.
|
|
||||||
this._notifyMetricsEvent("sdk.exception." + event.code + "." + event.message);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE:
|
case OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE:
|
||||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
reason: FAILURE_DETAILS.TOS_FAILURE
|
reason: FAILURE_DETAILS.TOS_FAILURE
|
||||||
@@ -1054,12 +1062,37 @@ loop.OTSdkDriver = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a screenshare is denied. Notifies the other stores.
|
* Called when a screenshare is complete.
|
||||||
|
*
|
||||||
|
* @param {Error} error An OT error object, null if there was no error.
|
||||||
*/
|
*/
|
||||||
_onScreenShareDenied: function() {
|
_onScreenSharePublishComplete: function(error) {
|
||||||
|
if (!error) {
|
||||||
|
// Nothing to do for the success case.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free up publisher
|
||||||
|
this.screenshare.off("accessAllowed accessDenied streamCreated");
|
||||||
|
this.screenshare.destroy();
|
||||||
|
delete this.screenshare;
|
||||||
|
delete this._mockScreenSharePreviewEl;
|
||||||
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
|
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
|
||||||
state: SCREEN_SHARE_STATES.INACTIVE
|
state: SCREEN_SHARE_STATES.INACTIVE
|
||||||
}));
|
}));
|
||||||
|
this._notifyMetricsEvent("sdk.exception.screen." + error.code + "." + error.message);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a screenshare is denied. Notifies the other stores.
|
||||||
|
*/
|
||||||
|
_onScreenSharePublishError: function() {
|
||||||
|
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
|
||||||
|
state: SCREEN_SHARE_STATES.INACTIVE
|
||||||
|
}));
|
||||||
|
this.screenshare.off("accessAllowed accessDenied streamCreated");
|
||||||
|
this.screenshare.destroy();
|
||||||
|
delete this.screenshare;
|
||||||
delete this._mockScreenSharePreviewEl;
|
delete this._mockScreenSharePreviewEl;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ loop.store.TextChatStore = (function() {
|
|||||||
"dataChannelsAvailable",
|
"dataChannelsAvailable",
|
||||||
"receivedTextChatMessage",
|
"receivedTextChatMessage",
|
||||||
"sendTextChatMessage",
|
"sendTextChatMessage",
|
||||||
"updateRoomInfo"
|
"updateRoomInfo",
|
||||||
|
"updateRoomContext"
|
||||||
],
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,7 +135,8 @@ loop.store.TextChatStore = (function() {
|
|||||||
receivedTextChatMessage: function(actionData) {
|
receivedTextChatMessage: function(actionData) {
|
||||||
// If we don't know how to deal with this content, then skip it
|
// If we don't know how to deal with this content, then skip it
|
||||||
// as this version doesn't support it.
|
// as this version doesn't support it.
|
||||||
if (actionData.contentType !== CHAT_CONTENT_TYPES.TEXT) {
|
if (actionData.contentType !== CHAT_CONTENT_TYPES.TEXT &&
|
||||||
|
actionData.contentType !== CHAT_CONTENT_TYPES.CONTEXT_TILE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +189,57 @@ loop.store.TextChatStore = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles receiving information about the room context due to a change of the tabs
|
||||||
|
*
|
||||||
|
* @param {sharedActions.updateRoomContext} actionData
|
||||||
|
*/
|
||||||
|
updateRoomContext: function(actionData) {
|
||||||
|
// Firstly, check if there is a previous context tile, if not, create it
|
||||||
|
var contextTile = null;
|
||||||
|
|
||||||
|
for (var i = this._storeState.messageList.length - 1; i >= 0; i--) {
|
||||||
|
if (this._storeState.messageList[i].contentType === CHAT_CONTENT_TYPES.CONTEXT_TILE) {
|
||||||
|
contextTile = this._storeState.messageList[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contextTile) {
|
||||||
|
this._appendContextTileMessage(actionData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldDomain = new URL(contextTile.extraData.newRoomURL).hostname;
|
||||||
|
var currentDomain = new URL(actionData.newRoomURL).hostname;
|
||||||
|
|
||||||
|
if (oldDomain === currentDomain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._appendContextTileMessage(actionData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a context tile message to the UI and sends it.
|
||||||
|
*
|
||||||
|
* @param {sharedActions.updateRoomContext} data
|
||||||
|
*/
|
||||||
|
_appendContextTileMessage: function(data) {
|
||||||
|
var msgData = {
|
||||||
|
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||||
|
message: data.newRoomDescription,
|
||||||
|
extraData: {
|
||||||
|
roomToken: data.roomToken,
|
||||||
|
newRoomThumbnail: data.newRoomThumbnail,
|
||||||
|
newRoomURL: data.newRoomURL
|
||||||
|
},
|
||||||
|
sentTimestamp: (new Date()).toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sendTextChatMessage(msgData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -5,7 +7,7 @@
|
|||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.shared = loop.shared || {};
|
loop.shared = loop.shared || {};
|
||||||
loop.shared.views = loop.shared.views || {};
|
loop.shared.views = loop.shared.views || {};
|
||||||
loop.shared.views.chat = (function(mozL10n) {
|
loop.shared.views.chat = function (mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
@@ -17,15 +19,20 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
/**
|
/**
|
||||||
* Renders an individual entry for the text chat entries view.
|
* Renders an individual entry for the text chat entries view.
|
||||||
*/
|
*/
|
||||||
var TextChatEntry = React.createClass({displayName: "TextChatEntry",
|
var TextChatEntry = React.createClass({
|
||||||
|
displayName: "TextChatEntry",
|
||||||
|
|
||||||
mixins: [React.addons.PureRenderMixin],
|
mixins: [React.addons.PureRenderMixin],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
contentType: React.PropTypes.string.isRequired,
|
contentType: React.PropTypes.string.isRequired,
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
|
||||||
|
extraData: React.PropTypes.object,
|
||||||
message: React.PropTypes.string.isRequired,
|
message: React.PropTypes.string.isRequired,
|
||||||
showTimestamp: React.PropTypes.bool.isRequired,
|
showTimestamp: React.PropTypes.bool.isRequired,
|
||||||
timestamp: React.PropTypes.string.isRequired,
|
timestamp: React.PropTypes.string.isRequired,
|
||||||
type: React.PropTypes.string.isRequired
|
type: React.PropTypes.string.isRequired,
|
||||||
|
useDesktopPaths: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,21 +40,18 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
* (or L10N equivalent).
|
* (or L10N equivalent).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_renderTimestamp: function() {
|
_renderTimestamp: function () {
|
||||||
var date = new Date(this.props.timestamp);
|
var date = new Date(this.props.timestamp);
|
||||||
var language = mozL10n.language ? mozL10n.language.code
|
|
||||||
: mozL10n.getLanguage();
|
|
||||||
|
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("span", {className: "text-chat-entry-timestamp"},
|
"span",
|
||||||
date.toLocaleTimeString(language,
|
{ className: "text-chat-entry-timestamp" },
|
||||||
{ hour: "numeric", minute: "numeric",
|
date.toLocaleTimeString(mozL10n.language.code, { hour: "numeric", minute: "numeric",
|
||||||
hour12: false })
|
hour12: false })
|
||||||
)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
var classes = classNames({
|
var classes = classNames({
|
||||||
"text-chat-entry": true,
|
"text-chat-entry": true,
|
||||||
"received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
|
"received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
|
||||||
@@ -58,33 +62,54 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
|
|
||||||
var optionalProps = {};
|
var optionalProps = {};
|
||||||
if (loop.shared.utils.isDesktop()) {
|
if (loop.shared.utils.isDesktop()) {
|
||||||
optionalProps.linkClickHandler = function(url) {
|
optionalProps.linkClickHandler = function (url) {
|
||||||
loop.request("OpenURL", url);
|
loop.request("OpenURL", url);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (this.props.contentType === CHAT_CONTENT_TYPES.CONTEXT_TILE) {
|
||||||
React.createElement("div", {className: classes},
|
return React.createElement(
|
||||||
React.createElement(sharedViews.LinkifiedTextView, React.__spread({}, optionalProps,
|
"div",
|
||||||
{rawText: this.props.message})),
|
{ className: classes },
|
||||||
React.createElement("span", {className: "text-chat-arrow"}),
|
React.createElement(sharedViews.ContextUrlView, {
|
||||||
|
allowClick: true,
|
||||||
|
description: this.props.message,
|
||||||
|
dispatcher: this.props.dispatcher,
|
||||||
|
thumbnail: this.props.extraData.newRoomThumbnail,
|
||||||
|
url: this.props.extraData.newRoomURL,
|
||||||
|
useDesktopPaths: this.props.useDesktopPaths }),
|
||||||
|
this.props.showTimestamp ? this._renderTimestamp() : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: classes },
|
||||||
|
React.createElement(sharedViews.LinkifiedTextView, _extends({}, optionalProps, {
|
||||||
|
rawText: this.props.message })),
|
||||||
|
React.createElement("span", { className: "text-chat-arrow" }),
|
||||||
this.props.showTimestamp ? this._renderTimestamp() : null
|
this.props.showTimestamp ? this._renderTimestamp() : null
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var TextChatRoomName = React.createClass({displayName: "TextChatRoomName",
|
var TextChatRoomName = React.createClass({
|
||||||
|
displayName: "TextChatRoomName",
|
||||||
|
|
||||||
mixins: [React.addons.PureRenderMixin],
|
mixins: [React.addons.PureRenderMixin],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
message: React.PropTypes.string.isRequired
|
message: React.PropTypes.string.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: "text-chat-header special room-name"},
|
"div",
|
||||||
React.createElement("p", null, mozL10n.get("rooms_welcome_title", { conversationName: this.props.message }))
|
{ className: "text-chat-header special room-name" },
|
||||||
|
React.createElement(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
mozL10n.get("rooms_welcome_title", { conversationName: this.props.message })
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -95,11 +120,10 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
* TextChatView so that scrolling can be managed more efficiently - this
|
* TextChatView so that scrolling can be managed more efficiently - this
|
||||||
* component only updates when the message list is changed.
|
* component only updates when the message list is changed.
|
||||||
*/
|
*/
|
||||||
var TextChatEntriesView = React.createClass({displayName: "TextChatEntriesView",
|
var TextChatEntriesView = React.createClass({
|
||||||
mixins: [
|
displayName: "TextChatEntriesView",
|
||||||
React.addons.PureRenderMixin,
|
|
||||||
sharedMixins.AudioMixin
|
mixins: [React.addons.PureRenderMixin, sharedMixins.AudioMixin],
|
||||||
],
|
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
ONE_MINUTE: 60
|
ONE_MINUTE: 60
|
||||||
@@ -112,31 +136,30 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
useDesktopPaths: React.PropTypes.bool.isRequired
|
useDesktopPaths: React.PropTypes.bool.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
receivedMessageCount: 0
|
receivedMessageCount: 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
_hasChatMessages: function() {
|
_hasChatMessages: function () {
|
||||||
return this.props.messageList.some(function(message) {
|
return this.props.messageList.some(function (message) {
|
||||||
return message.contentType === CHAT_CONTENT_TYPES.TEXT;
|
return message.contentType === CHAT_CONTENT_TYPES.TEXT;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUpdate: function() {
|
componentWillUpdate: function () {
|
||||||
var node = this.getDOMNode();
|
var node = this.getDOMNode();
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Scroll only if we're right at the bottom of the display, or if we've
|
// Scroll only if we're right at the bottom of the display, or if we've
|
||||||
// not had any chat messages so far.
|
// not had any chat messages so far.
|
||||||
this.shouldScroll = !this._hasChatMessages() ||
|
this.shouldScroll = !this._hasChatMessages() || node.scrollHeight === node.scrollTop + node.clientHeight;
|
||||||
node.scrollHeight === node.scrollTop + node.clientHeight;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps: function (nextProps) {
|
||||||
var receivedMessageCount = nextProps.messageList.filter(function(message) {
|
var receivedMessageCount = nextProps.messageList.filter(function (message) {
|
||||||
return message.type === CHAT_MESSAGE_TYPES.RECEIVED;
|
return message.type === CHAT_MESSAGE_TYPES.RECEIVED;
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
@@ -147,12 +170,12 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function () {
|
||||||
// Don't scroll if we haven't got any chat messages yet - e.g. for context
|
// Don't scroll if we haven't got any chat messages yet - e.g. for context
|
||||||
// display, we want to display starting at the top.
|
// display, we want to display starting at the top.
|
||||||
if (this.shouldScroll && this._hasChatMessages()) {
|
if (this.shouldScroll && this._hasChatMessages()) {
|
||||||
// This ensures the paint is complete.
|
// This ensures the paint is complete.
|
||||||
window.requestAnimationFrame(function() {
|
window.requestAnimationFrame(function () {
|
||||||
try {
|
try {
|
||||||
var node = this.getDOMNode();
|
var node = this.getDOMNode();
|
||||||
node.scrollTop = node.scrollHeight - node.clientHeight;
|
node.scrollTop = node.scrollHeight - node.clientHeight;
|
||||||
@@ -163,7 +186,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
/* Keep track of the last printed timestamp. */
|
/* Keep track of the last printed timestamp. */
|
||||||
var lastTimestamp = 0;
|
var lastTimestamp = 0;
|
||||||
|
|
||||||
@@ -171,35 +194,36 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
"text-chat-entries": true
|
"text-chat-entries": true
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: entriesClasses},
|
"div",
|
||||||
React.createElement("div", {className: "text-chat-scroller"},
|
{ className: entriesClasses },
|
||||||
|
React.createElement(
|
||||||
this.props.messageList.map(function(entry, i) {
|
"div",
|
||||||
|
{ className: "text-chat-scroller" },
|
||||||
|
this.props.messageList.map(function (entry, i) {
|
||||||
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
|
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
|
||||||
if (!this.props.showInitialContext) { return null; }
|
if (!this.props.showInitialContext) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
switch (entry.contentType) {
|
switch (entry.contentType) {
|
||||||
case CHAT_CONTENT_TYPES.ROOM_NAME:
|
case CHAT_CONTENT_TYPES.ROOM_NAME:
|
||||||
return (
|
return React.createElement(TextChatRoomName, {
|
||||||
React.createElement(TextChatRoomName, {
|
|
||||||
key: i,
|
key: i,
|
||||||
message: entry.message})
|
message: entry.message });
|
||||||
);
|
|
||||||
case CHAT_CONTENT_TYPES.CONTEXT:
|
case CHAT_CONTENT_TYPES.CONTEXT:
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: "context-url-view-wrapper", key: i},
|
"div",
|
||||||
|
{ className: "context-url-view-wrapper", key: i },
|
||||||
React.createElement(sharedViews.ContextUrlView, {
|
React.createElement(sharedViews.ContextUrlView, {
|
||||||
allowClick: true,
|
allowClick: true,
|
||||||
description: entry.message,
|
description: entry.message,
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
thumbnail: entry.extraData.thumbnail,
|
thumbnail: entry.extraData.thumbnail,
|
||||||
url: entry.extraData.location,
|
url: entry.extraData.location,
|
||||||
useDesktopPaths: this.props.useDesktopPaths})
|
useDesktopPaths: this.props.useDesktopPaths })
|
||||||
)
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
console.error("Unsupported contentType",
|
console.error("Unsupported contentType", entry.contentType);
|
||||||
entry.contentType);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,24 +232,22 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
var timestamp = entry.receivedTimestamp || entry.sentTimestamp;
|
var timestamp = entry.receivedTimestamp || entry.sentTimestamp;
|
||||||
|
|
||||||
var timeDiff = this._isOneMinDelta(timestamp, lastTimestamp);
|
var timeDiff = this._isOneMinDelta(timestamp, lastTimestamp);
|
||||||
var shouldShowTimestamp = this._shouldShowTimestamp(i,
|
var shouldShowTimestamp = this._shouldShowTimestamp(i, timeDiff);
|
||||||
timeDiff);
|
|
||||||
|
|
||||||
if (shouldShowTimestamp) {
|
if (shouldShowTimestamp) {
|
||||||
lastTimestamp = timestamp;
|
lastTimestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return React.createElement(TextChatEntry, { contentType: entry.contentType,
|
||||||
React.createElement(TextChatEntry, {contentType: entry.contentType,
|
dispatcher: this.props.dispatcher,
|
||||||
|
extraData: entry.extraData,
|
||||||
key: i,
|
key: i,
|
||||||
message: entry.message,
|
message: entry.message,
|
||||||
showTimestamp: shouldShowTimestamp,
|
showTimestamp: shouldShowTimestamp,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
type: entry.type})
|
type: entry.type,
|
||||||
);
|
useDesktopPaths: this.props.useDesktopPaths });
|
||||||
}, this)
|
}, this)
|
||||||
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -239,14 +261,13 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
* @param {boolean} timeDiff If difference between consecutive messages is
|
* @param {boolean} timeDiff If difference between consecutive messages is
|
||||||
* bigger than one minute.
|
* bigger than one minute.
|
||||||
*/
|
*/
|
||||||
_shouldShowTimestamp: function(idx, timeDiff) {
|
_shouldShowTimestamp: function (idx, timeDiff) {
|
||||||
if (!idx) {
|
if (!idx) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If consecutive messages are from different senders */
|
/* If consecutive messages are from different senders */
|
||||||
if (this.props.messageList[idx].type !==
|
if (this.props.messageList[idx].type !== this.props.messageList[idx - 1].type) {
|
||||||
this.props.messageList[idx - 1].type) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +283,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
* @param {string} currTime Timestamp of message yet to be rendered.
|
* @param {string} currTime Timestamp of message yet to be rendered.
|
||||||
* @param {string} prevTime Last timestamp printed in the chat view.
|
* @param {string} prevTime Last timestamp printed in the chat view.
|
||||||
*/
|
*/
|
||||||
_isOneMinDelta: function(currTime, prevTime) {
|
_isOneMinDelta: function (currTime, prevTime) {
|
||||||
var date1 = new Date(currTime);
|
var date1 = new Date(currTime);
|
||||||
var date2 = new Date(prevTime);
|
var date2 = new Date(prevTime);
|
||||||
var delta = date1 - date2;
|
var delta = date1 - date2;
|
||||||
@@ -283,11 +304,10 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
* @property {Boolean} textChatEnabled Set to true to enable the box. If false, the
|
* @property {Boolean} textChatEnabled Set to true to enable the box. If false, the
|
||||||
* text chat box won't be displayed.
|
* text chat box won't be displayed.
|
||||||
*/
|
*/
|
||||||
var TextChatInputView = React.createClass({displayName: "TextChatInputView",
|
var TextChatInputView = React.createClass({
|
||||||
mixins: [
|
displayName: "TextChatInputView",
|
||||||
React.addons.LinkedStateMixin,
|
|
||||||
React.addons.PureRenderMixin
|
mixins: [React.addons.LinkedStateMixin, React.addons.PureRenderMixin],
|
||||||
],
|
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
@@ -295,7 +315,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
textChatEnabled: React.PropTypes.bool.isRequired
|
textChatEnabled: React.PropTypes.bool.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
messageDetail: ""
|
messageDetail: ""
|
||||||
};
|
};
|
||||||
@@ -307,7 +327,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
*
|
*
|
||||||
* @param {Object} event The DOM event.
|
* @param {Object} event The DOM event.
|
||||||
*/
|
*/
|
||||||
handleKeyDown: function(event) {
|
handleKeyDown: function (event) {
|
||||||
if (event.which === 13) {
|
if (event.which === 13) {
|
||||||
this.handleFormSubmit(event);
|
this.handleFormSubmit(event);
|
||||||
}
|
}
|
||||||
@@ -318,7 +338,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
*
|
*
|
||||||
* @param {Object} event The DOM event.
|
* @param {Object} event The DOM event.
|
||||||
*/
|
*/
|
||||||
handleFormSubmit: function(event) {
|
handleFormSubmit: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Don't send empty messages.
|
// Don't send empty messages.
|
||||||
@@ -329,27 +349,29 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
|
this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
|
||||||
contentType: CHAT_CONTENT_TYPES.TEXT,
|
contentType: CHAT_CONTENT_TYPES.TEXT,
|
||||||
message: this.state.messageDetail,
|
message: this.state.messageDetail,
|
||||||
sentTimestamp: (new Date()).toISOString()
|
sentTimestamp: new Date().toISOString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Reset the form to empty, ready for the next message.
|
// Reset the form to empty, ready for the next message.
|
||||||
this.setState({ messageDetail: "" });
|
this.setState({ messageDetail: "" });
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
if (!this.props.textChatEnabled) {
|
if (!this.props.textChatEnabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: "text-chat-box"},
|
"div",
|
||||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
{ className: "text-chat-box" },
|
||||||
|
React.createElement(
|
||||||
|
"form",
|
||||||
|
{ onSubmit: this.handleFormSubmit },
|
||||||
React.createElement("input", {
|
React.createElement("input", {
|
||||||
onKeyDown: this.handleKeyDown,
|
onKeyDown: this.handleKeyDown,
|
||||||
placeholder: this.props.showPlaceholder ? mozL10n.get("chat_textbox_placeholder") : "",
|
placeholder: this.props.showPlaceholder ? mozL10n.get("chat_textbox_placeholder") : "",
|
||||||
type: "text",
|
type: "text",
|
||||||
valueLink: this.linkState("messageDetail")})
|
valueLink: this.linkState("messageDetail") })
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -363,11 +385,10 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
* @property {Boolean} showInitialContext Set to true to show the room name
|
* @property {Boolean} showInitialContext Set to true to show the room name
|
||||||
* and initial context tile for linker clicker's special list items
|
* and initial context tile for linker clicker's special list items
|
||||||
*/
|
*/
|
||||||
var TextChatView = React.createClass({displayName: "TextChatView",
|
var TextChatView = React.createClass({
|
||||||
mixins: [
|
displayName: "TextChatView",
|
||||||
React.addons.LinkedStateMixin,
|
|
||||||
loop.store.StoreMixin("textChatStore")
|
mixins: [React.addons.LinkedStateMixin, loop.store.StoreMixin("textChatStore")],
|
||||||
],
|
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
@@ -375,25 +396,23 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
useDesktopPaths: React.PropTypes.bool.isRequired
|
useDesktopPaths: React.PropTypes.bool.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function () {
|
||||||
return this.getStoreState();
|
return this.getStoreState();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function () {
|
||||||
var messageList = this.state.messageList;
|
var messageList = this.state.messageList;
|
||||||
|
|
||||||
// Filter out items not displayed when showing initial context.
|
// Filter out items not displayed when showing initial context.
|
||||||
// We do this here so that we can set the classes correctly on the view.
|
// We do this here so that we can set the classes correctly on the view.
|
||||||
if (!this.props.showInitialContext) {
|
if (!this.props.showInitialContext) {
|
||||||
messageList = messageList.filter(function(item) {
|
messageList = messageList.filter(function (item) {
|
||||||
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
|
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL || item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME && item.contentType !== CHAT_CONTENT_TYPES.CONTEXT;
|
||||||
(item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME &&
|
|
||||||
item.contentType !== CHAT_CONTENT_TYPES.CONTEXT);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show the placeholder if we've sent messages.
|
// Only show the placeholder if we've sent messages.
|
||||||
var hasSentMessages = messageList.some(function(item) {
|
var hasSentMessages = messageList.some(function (item) {
|
||||||
return item.type === CHAT_MESSAGE_TYPES.SENT;
|
return item.type === CHAT_MESSAGE_TYPES.SENT;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -403,18 +422,18 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
"text-chat-disabled": !this.state.textChatEnabled
|
"text-chat-disabled": !this.state.textChatEnabled
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return React.createElement(
|
||||||
React.createElement("div", {className: textChatViewClasses},
|
"div",
|
||||||
|
{ className: textChatViewClasses },
|
||||||
React.createElement(TextChatEntriesView, {
|
React.createElement(TextChatEntriesView, {
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
messageList: messageList,
|
messageList: messageList,
|
||||||
showInitialContext: this.props.showInitialContext,
|
showInitialContext: this.props.showInitialContext,
|
||||||
useDesktopPaths: this.props.useDesktopPaths}),
|
useDesktopPaths: this.props.useDesktopPaths }),
|
||||||
React.createElement(TextChatInputView, {
|
React.createElement(TextChatInputView, {
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
showPlaceholder: !hasSentMessages,
|
showPlaceholder: !hasSentMessages,
|
||||||
textChatEnabled: this.state.textChatEnabled})
|
textChatEnabled: this.state.textChatEnabled })
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -424,4 +443,4 @@ loop.shared.views.chat = (function(mozL10n) {
|
|||||||
TextChatEntry: TextChatEntry,
|
TextChatEntry: TextChatEntry,
|
||||||
TextChatView: TextChatView
|
TextChatView: TextChatView
|
||||||
};
|
};
|
||||||
})(navigator.mozL10n || document.mozL10n);
|
}(navigator.mozL10n || document.mozL10n);
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
|||||||
var CHAT_CONTENT_TYPES = {
|
var CHAT_CONTENT_TYPES = {
|
||||||
CONTEXT: "chat-context",
|
CONTEXT: "chat-context",
|
||||||
TEXT: "chat-text",
|
TEXT: "chat-text",
|
||||||
ROOM_NAME: "room-name"
|
ROOM_NAME: "room-name",
|
||||||
|
CONTEXT_TILE: "context-tile"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -720,34 +721,6 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Truncate a string if it exceeds the length as defined in `maxLen`, which
|
|
||||||
* is defined as '72' characters by default. If the string needs trimming,
|
|
||||||
* it'll be suffixed with the unicode ellipsis char, \u2026.
|
|
||||||
*
|
|
||||||
* @param {String} str The string to truncate, if needed.
|
|
||||||
* @param {Number} maxLen Maximum number of characters that the string is
|
|
||||||
* allowed to contain. Optional, defaults to 72.
|
|
||||||
* @return {String} Truncated version of `str`.
|
|
||||||
*/
|
|
||||||
function truncate(str, maxLen) {
|
|
||||||
maxLen = maxLen || 72;
|
|
||||||
|
|
||||||
if (str.length > maxLen) {
|
|
||||||
var substring = str.substr(0, maxLen);
|
|
||||||
// XXX Due to the fact that we have two different l10n libraries.
|
|
||||||
var direction = mozL10n.getDirection ? mozL10n.getDirection() :
|
|
||||||
mozL10n.language.direction;
|
|
||||||
if (direction === "rtl") {
|
|
||||||
return "…" + substring;
|
|
||||||
}
|
|
||||||
|
|
||||||
return substring + "…";
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up the DOM hierarchy for a node matching `selector`.
|
* Look up the DOM hierarchy for a node matching `selector`.
|
||||||
* If it is not found return the parent node, this is a sane default so
|
* If it is not found return the parent node, this is a sane default so
|
||||||
@@ -802,7 +775,6 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
|||||||
strToUint8Array: strToUint8Array,
|
strToUint8Array: strToUint8Array,
|
||||||
Uint8ArrayToStr: Uint8ArrayToStr,
|
Uint8ArrayToStr: Uint8ArrayToStr,
|
||||||
objectDiff: objectDiff,
|
objectDiff: objectDiff,
|
||||||
stripFalsyValues: stripFalsyValues,
|
stripFalsyValues: stripFalsyValues
|
||||||
truncate: truncate
|
|
||||||
};
|
};
|
||||||
}).call(inChrome ? this : loop.shared);
|
}).call(inChrome ? this : loop.shared);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
// This is useful for some of the tests, e.g.
|
||||||
|
// expect(new Foo()).to.Throw(/error/)
|
||||||
|
"no-new": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -540,7 +540,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
// easier.
|
// easier.
|
||||||
sandbox.stub(loop.crypto, "decryptBytes", function() {
|
sandbox.stub(loop.crypto, "decryptBytes", function() {
|
||||||
return {
|
return {
|
||||||
then: function(resolve, reject) {
|
then: function(resolve) {
|
||||||
resolve(JSON.stringify(roomContext));
|
resolve(JSON.stringify(roomContext));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1552,10 +1552,15 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
store.setStoreState({
|
store.setStoreState({
|
||||||
roomState: ROOM_STATES.JOINED,
|
roomState: ROOM_STATES.JOINED,
|
||||||
roomToken: "fakeToken",
|
roomToken: "fakeToken",
|
||||||
sessionToken: "1627384950"
|
sessionToken: "1627384950",
|
||||||
|
participants: [{
|
||||||
|
displayName: "Owner",
|
||||||
|
owner: true
|
||||||
|
}, {
|
||||||
|
displayName: "Guest",
|
||||||
|
owner: false
|
||||||
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
@@ -1563,6 +1568,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should set the state to 'pending'", function() {
|
it("should set the state to 'pending'", function() {
|
||||||
|
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
sinon.assert.calledWith(dispatcher.dispatch,
|
sinon.assert.calledWith(dispatcher.dispatch,
|
||||||
new sharedActions.ScreenSharingState({
|
new sharedActions.ScreenSharingState({
|
||||||
@@ -1571,10 +1577,12 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should add a browser sharing listener for tab sharing", function() {
|
it("should add a browser sharing listener for tab sharing", function() {
|
||||||
|
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||||
sinon.assert.calledOnce(requestStubs.AddBrowserSharingListener);
|
sinon.assert.calledOnce(requestStubs.AddBrowserSharingListener);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should invoke the SDK driver with the correct options for tab sharing", function() {
|
it("should invoke the SDK driver with the correct options for tab sharing", function() {
|
||||||
|
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||||
sinon.assert.calledOnce(fakeSdkDriver.startScreenShare);
|
sinon.assert.calledOnce(fakeSdkDriver.startScreenShare);
|
||||||
sinon.assert.calledWith(fakeSdkDriver.startScreenShare, {
|
sinon.assert.calledWith(fakeSdkDriver.startScreenShare, {
|
||||||
videoSource: "browser",
|
videoSource: "browser",
|
||||||
@@ -1586,6 +1594,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should request the new metadata when the browser being shared change", function() {
|
it("should request the new metadata when the browser being shared change", function() {
|
||||||
|
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||||
clock.tick(500);
|
clock.tick(500);
|
||||||
sinon.assert.calledOnce(getSelectedTabMetadataStub);
|
sinon.assert.calledOnce(getSelectedTabMetadataStub);
|
||||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||||
@@ -1599,6 +1608,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should process only one request", function() {
|
it("should process only one request", function() {
|
||||||
|
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||||
// Simulates multiple requests.
|
// Simulates multiple requests.
|
||||||
LoopMochaUtils.publish("BrowserSwitch", 72);
|
LoopMochaUtils.publish("BrowserSwitch", 72);
|
||||||
LoopMochaUtils.publish("BrowserSwitch", 72);
|
LoopMochaUtils.publish("BrowserSwitch", 72);
|
||||||
@@ -1614,6 +1624,39 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
roomToken: store.getStoreState().roomToken
|
roomToken: store.getStoreState().roomToken
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not process a request without url", function() {
|
||||||
|
clock.tick(500);
|
||||||
|
getSelectedTabMetadataStub.returns({
|
||||||
|
title: "fakeTitle",
|
||||||
|
favicon: "fakeFavicon"
|
||||||
|
});
|
||||||
|
|
||||||
|
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||||
|
sinon.assert.calledOnce(getSelectedTabMetadataStub);
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not process a request if no-one is in the room", function() {
|
||||||
|
store.setStoreState({
|
||||||
|
roomState: ROOM_STATES.JOINED,
|
||||||
|
roomToken: "fakeToken",
|
||||||
|
sessionToken: "1627384950",
|
||||||
|
participants: [{
|
||||||
|
displayName: "Owner",
|
||||||
|
owner: true
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
clock.tick(500);
|
||||||
|
getSelectedTabMetadataStub.returns({
|
||||||
|
title: "fakeTitle",
|
||||||
|
favicon: "fakeFavicon"
|
||||||
|
});
|
||||||
|
|
||||||
|
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||||
|
sinon.assert.calledOnce(getSelectedTabMetadataStub);
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Screen share Events", function() {
|
describe("Screen share Events", function() {
|
||||||
|
|||||||
@@ -4,26 +4,71 @@ import threading
|
|||||||
import SimpleHTTPServer
|
import SimpleHTTPServer
|
||||||
import SocketServer
|
import SocketServer
|
||||||
import BaseHTTPServer
|
import BaseHTTPServer
|
||||||
import socket
|
|
||||||
import urllib
|
import urllib
|
||||||
import urlparse
|
import urlparse
|
||||||
import os
|
import os
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
|
|
||||||
# XXX Once we're on a branch with bug 993478 landed, we may want to get
|
# XXX Once we're on a branch with bug 993478 landed, we may want to get
|
||||||
# rid of this HTTP server and just use the built-in one from Marionette,
|
# rid of this HTTP server and just use the built-in one from Marionette,
|
||||||
# since there will less code to maintain, and it will be faster. We'll
|
# since there will less code to maintain, and it will be faster. We'll
|
||||||
# need to consider whether this code wants to be shared with WebDriver tests
|
# need to consider whether this code wants to be shared with WebDriver tests
|
||||||
# for other browsers, though.
|
# for other browsers, though.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
gCommonDir = None
|
||||||
|
|
||||||
|
# These redirects map the paths expected by the index.html files to the paths
|
||||||
|
# in mozilla-central. In the github repo, the unit tests are run entirely within
|
||||||
|
# karma, where the files are loaded directly. For mozilla-central we must map
|
||||||
|
# the files to the appropriate place.
|
||||||
|
REDIRECTIONS = {
|
||||||
|
"/test/vendor": "/chrome/content/shared/test/vendor",
|
||||||
|
"/shared/js": "/chrome/content/shared/js",
|
||||||
|
"/shared/test": "/chrome/content/shared/test",
|
||||||
|
"/shared/vendor": "/chrome/content/shared/vendor",
|
||||||
|
"/add-on/panels/vendor": "/chrome/content/panels/vendor",
|
||||||
|
"/add-on/panels/js": "/chrome/content/panels/js",
|
||||||
|
"/add-on/shared/js": "/chrome/content/shared/js",
|
||||||
|
"/add-on/shared/vendor": "/chrome/content/shared/vendor",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ThreadingSimpleServer(SocketServer.ThreadingMixIn,
|
class ThreadingSimpleServer(SocketServer.ThreadingMixIn,
|
||||||
BaseHTTPServer.HTTPServer):
|
BaseHTTPServer.HTTPServer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QuietHttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
class HttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||||
|
def do_HEAD(s):
|
||||||
|
lastSlash = s.path.rfind("/")
|
||||||
|
path = s.path[:lastSlash]
|
||||||
|
|
||||||
|
if (path in REDIRECTIONS):
|
||||||
|
filename = s.path[lastSlash:]
|
||||||
|
|
||||||
|
s.send_response(301)
|
||||||
|
# Prefix the redirections with the common directory segment.
|
||||||
|
s.send_header("Location", "/" + gCommonDir + REDIRECTIONS.get(path, "/") + filename)
|
||||||
|
s.end_headers()
|
||||||
|
else:
|
||||||
|
SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(s)
|
||||||
|
|
||||||
|
def do_GET(s):
|
||||||
|
lastSlash = s.path.rfind("/")
|
||||||
|
path = s.path[:lastSlash]
|
||||||
|
|
||||||
|
if (path in REDIRECTIONS):
|
||||||
|
s.do_HEAD()
|
||||||
|
else:
|
||||||
|
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(s)
|
||||||
|
|
||||||
def log_message(self, format, *args, **kwargs):
|
def log_message(self, format, *args, **kwargs):
|
||||||
|
if DEBUG:
|
||||||
|
BaseHTTPServer.BaseHTTPRequestHandler.log_message(self, format, *args, **kwargs)
|
||||||
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -33,13 +78,8 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(BaseTestFrontendUnits, cls).setUpClass()
|
super(BaseTestFrontendUnits, cls).setUpClass()
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
handler = SimpleHTTPServer.SimpleHTTPRequestHandler
|
|
||||||
else:
|
|
||||||
handler = QuietHttpRequestHandler
|
|
||||||
|
|
||||||
# Port 0 means to select an arbitrary unused port
|
# Port 0 means to select an arbitrary unused port
|
||||||
cls.server = ThreadingSimpleServer(('', 0), handler)
|
cls.server = ThreadingSimpleServer(('', 0), HttpRequestHandler)
|
||||||
cls.ip, cls.port = cls.server.server_address
|
cls.ip, cls.port = cls.server.server_address
|
||||||
|
|
||||||
cls.server_thread = threading.Thread(target=cls.server.serve_forever)
|
cls.server_thread = threading.Thread(target=cls.server.serve_forever)
|
||||||
@@ -78,6 +118,8 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
|||||||
|
|
||||||
# srcdir_path should be the directory relative to this file.
|
# srcdir_path should be the directory relative to this file.
|
||||||
def set_server_prefix(self, srcdir_path):
|
def set_server_prefix(self, srcdir_path):
|
||||||
|
global gCommonDir
|
||||||
|
|
||||||
# We may be run from a different path than topsrcdir, e.g. in the case
|
# We may be run from a different path than topsrcdir, e.g. in the case
|
||||||
# of packaged tests. If so, then we have to work out the right directory
|
# of packaged tests. If so, then we have to work out the right directory
|
||||||
# for the local server.
|
# for the local server.
|
||||||
@@ -90,6 +132,13 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
|||||||
|
|
||||||
self.relPath = urllib.pathname2url(os.path.join(self.relPath, srcdir_path))
|
self.relPath = urllib.pathname2url(os.path.join(self.relPath, srcdir_path))
|
||||||
|
|
||||||
|
# This is the common directory segment, what you need to get from the
|
||||||
|
# common path to the relative path location. Used to get the redirects
|
||||||
|
# correct both locally and on the build systems.
|
||||||
|
# The .replace is to change windows path slashes to unix like ones for
|
||||||
|
# the url.
|
||||||
|
gCommonDir = os.path.normpath(self.relPath).replace("\\", "//")
|
||||||
|
|
||||||
# Finally join the relative path with the given src path
|
# Finally join the relative path with the given src path
|
||||||
self.server_prefix = urlparse.urljoin("http://localhost:" + str(self.port),
|
self.server_prefix = urlparse.urljoin("http://localhost:" + str(self.port),
|
||||||
self.relPath)
|
self.relPath)
|
||||||
@@ -122,8 +171,9 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
|||||||
# but not from marionette, uncomment the two lines below to break
|
# but not from marionette, uncomment the two lines below to break
|
||||||
# on failing tests, so that the browsers won't be torn down, and you
|
# on failing tests, so that the browsers won't be torn down, and you
|
||||||
# can use the browser debugging facilities to see what's going on.
|
# can use the browser debugging facilities to see what's going on.
|
||||||
#from ipdb import set_trace
|
#
|
||||||
#set_trace()
|
# from ipdb import set_trace
|
||||||
|
# set_trace()
|
||||||
|
|
||||||
raise AssertionError(self.get_failure_details(page))
|
raise AssertionError(self.get_failure_details(page))
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Loop shared mocha tests</title>
|
<title>Loop shared mocha tests</title>
|
||||||
<link rel="stylesheet" media="all" href="vendor/mocha-2.2.5.css">
|
<link rel="stylesheet" media="all" href="/test/vendor/mocha.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mocha">
|
<div id="mocha">
|
||||||
@@ -14,23 +14,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="messages"></div>
|
<div id="messages"></div>
|
||||||
<div id="fixtures"></div>
|
<div id="fixtures"></div>
|
||||||
<script src="../../content/shared/vendor/lodash-3.9.3.js"></script>
|
<script src="/shared/vendor/lodash.js"></script>
|
||||||
<script src="../shared/loop_mocha_utils.js"></script>
|
<script src="loop_mocha_utils.js"></script>
|
||||||
<script>
|
<script>
|
||||||
LoopMochaUtils.trapErrors();
|
LoopMochaUtils.trapErrors();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- libs -->
|
<!-- libs -->
|
||||||
<script src="../../content/shared/vendor/react-0.13.3.js"></script>
|
<script src="/shared/vendor/react.js"></script>
|
||||||
<script src="../../content/shared/vendor/classnames-2.2.0.js"></script>
|
<script src="/shared/vendor/classnames.js"></script>
|
||||||
<script src="../../content/shared/vendor/backbone-1.2.1.js"></script>
|
<script src="/shared/vendor/backbone.js"></script>
|
||||||
<script src="../../standalone/content/vendor/l10n-gaia-02ca67948fe8.js"></script>
|
|
||||||
|
<!-- Use desktop's l10n.js, since that's all we have when we export to
|
||||||
|
mozilla-central. -->
|
||||||
|
<script src="/add-on/panels/vendor/l10n.js"></script>
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<script src="vendor/mocha-2.2.5.js"></script>
|
<script src="/test/vendor/mocha.js"></script>
|
||||||
<script src="vendor/chai-3.0.0.js"></script>
|
<script src="/test/vendor/chai.js"></script>
|
||||||
<script src="vendor/chai-as-promised-5.1.0.js"></script>
|
<script src="/test/vendor/chai-as-promised.js"></script>
|
||||||
<script src="vendor/sinon-1.16.1.js"></script>
|
<script src="/test/vendor/sinon.js"></script>
|
||||||
<script>
|
<script>
|
||||||
/*global chai, mocha */
|
/*global chai, mocha */
|
||||||
chai.config.includeStack = true;
|
chai.config.includeStack = true;
|
||||||
@@ -38,22 +41,22 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- App scripts -->
|
<!-- App scripts -->
|
||||||
<script src="../../content/shared/js/loopapi-client.js"></script>
|
<script src="/shared/js/loopapi-client.js"></script>
|
||||||
<script src="../../content/shared/js/utils.js"></script>
|
<script src="/shared/js/utils.js"></script>
|
||||||
<script src="../../content/shared/js/models.js"></script>
|
<script src="/shared/js/models.js"></script>
|
||||||
<script src="../../content/shared/js/mixins.js"></script>
|
<script src="/shared/js/mixins.js"></script>
|
||||||
<script src="../../content/shared/js/crypto.js"></script>
|
<script src="/shared/js/crypto.js"></script>
|
||||||
<script src="../../content/shared/js/validate.js"></script>
|
<script src="/shared/js/validate.js"></script>
|
||||||
<script src="../../content/shared/js/actions.js"></script>
|
<script src="/shared/js/actions.js"></script>
|
||||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
<script src="/shared/js/dispatcher.js"></script>
|
||||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
<script src="/shared/js/otSdkDriver.js"></script>
|
||||||
<script src="../../content/shared/js/store.js"></script>
|
<script src="/shared/js/store.js"></script>
|
||||||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
<script src="/shared/js/activeRoomStore.js"></script>
|
||||||
<script src="../../content/shared/js/views.js"></script>
|
<script src="/shared/js/views.js"></script>
|
||||||
<script src="../../content/shared/js/textChatStore.js"></script>
|
<script src="/shared/js/textChatStore.js"></script>
|
||||||
<script src="../../content/shared/js/textChatView.js"></script>
|
<script src="/shared/js/textChatView.js"></script>
|
||||||
<script src="../../content/shared/js/urlRegExps.js"></script>
|
<script src="/shared/js/urlRegExps.js"></script>
|
||||||
<script src="../../content/shared/js/linkifiedTextView.js"></script>
|
<script src="/shared/js/linkifiedTextView.js"></script>
|
||||||
|
|
||||||
<!-- Test scripts -->
|
<!-- Test scripts -->
|
||||||
<script src="models_test.js"></script>
|
<script src="models_test.js"></script>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/* exported LoopMochaUtils */
|
||||||
|
|
||||||
var LoopMochaUtils = (function(global, _) {
|
var LoopMochaUtils = (function(global, _) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ var LoopMochaUtils = (function(global, _) {
|
|||||||
* @param {Function} asyncFn Function to invoke directly that contains async
|
* @param {Function} asyncFn Function to invoke directly that contains async
|
||||||
* continuation(s).
|
* continuation(s).
|
||||||
*/
|
*/
|
||||||
function syncThenable(asyncFn) {
|
function SyncThenable(asyncFn) {
|
||||||
var continuations = [];
|
var continuations = [];
|
||||||
var resolved = false;
|
var resolved = false;
|
||||||
var resolvedWith = null;
|
var resolvedWith = null;
|
||||||
@@ -45,6 +47,12 @@ var LoopMochaUtils = (function(global, _) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to resolve an object of type SyncThenable.
|
||||||
|
*
|
||||||
|
* @param {*} result The result to return.
|
||||||
|
* @return {SyncThenable} A resolved SyncThenable object.
|
||||||
|
*/
|
||||||
this.resolve = function(result) {
|
this.resolve = function(result) {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
resolvedWith = result;
|
resolvedWith = result;
|
||||||
@@ -69,8 +77,8 @@ var LoopMochaUtils = (function(global, _) {
|
|||||||
asyncFn(this.resolve.bind(this), this.reject.bind(this));
|
asyncFn(this.resolve.bind(this), this.reject.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
syncThenable.all = function(promises) {
|
SyncThenable.all = function(promises) {
|
||||||
return new syncThenable(function(resolve) {
|
return new SyncThenable(function(resolve) {
|
||||||
var results = [];
|
var results = [];
|
||||||
|
|
||||||
promises.forEach(function(promise) {
|
promises.forEach(function(promise) {
|
||||||
@@ -83,15 +91,28 @@ var LoopMochaUtils = (function(global, _) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This simulates the equivalent of Promise.resolve() - calling the
|
||||||
|
* resolve function on the raw object.
|
||||||
|
*
|
||||||
|
* @param {*} result The result to return.
|
||||||
|
* @return {SyncThenable} A resolved SyncThenable object.
|
||||||
|
*/
|
||||||
|
SyncThenable.resolve = function(result) {
|
||||||
|
return new SyncThenable(function(resolve) {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple wrapper around `sinon.sandbox.create()` to also stub the native Promise
|
* Simple wrapper around `sinon.sandbox.create()` to also stub the native Promise
|
||||||
* object out with `syncThenable`.
|
* object out with `SyncThenable`.
|
||||||
*
|
*
|
||||||
* @return {Sandbox} A Sinon.JS sandbox object.
|
* @return {Sandbox} A Sinon.JS sandbox object.
|
||||||
*/
|
*/
|
||||||
function createSandbox() {
|
function createSandbox() {
|
||||||
var sandbox = sinon.sandbox.create();
|
var sandbox = sinon.sandbox.create();
|
||||||
sandbox.stub(global, "Promise", syncThenable);
|
sandbox.stub(global, "Promise", SyncThenable);
|
||||||
return sandbox;
|
return sandbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +222,8 @@ var LoopMochaUtils = (function(global, _) {
|
|||||||
* to the listeners.
|
* to the listeners.
|
||||||
*/
|
*/
|
||||||
function publish() {
|
function publish() {
|
||||||
var args = Array.slice(arguments);
|
// Convert to a proper array.
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
var name = args.shift();
|
var name = args.shift();
|
||||||
gPushListenerCallbacks.forEach(function(cb) {
|
gPushListenerCallbacks.forEach(function(cb) {
|
||||||
cb({ data: [name, args] });
|
cb({ data: [name, args] });
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ describe("loopapi-client", function() {
|
|||||||
|
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var sandbox, clock, replyTimeoutMs;
|
var sandbox, clock, replyTimeoutMs;
|
||||||
var sharedMixins = loop.shared.mixins;
|
|
||||||
var TestUtils = React.addons.TestUtils;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ describe("loop.shared.models", function() {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var l10n = navigator.mozL10n;
|
var l10n = navigator.mozL10n || document.mozL10n;
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var sandbox;
|
var sandbox;
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ describe("loop.OTSdkDriver", function() {
|
|||||||
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
|
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
|
||||||
|
|
||||||
var sandbox, constants;
|
var sandbox, constants;
|
||||||
var dispatcher, driver, requestStubs, publisher, sdk, session, sessionData, subscriber;
|
var dispatcher, driver, requestStubs, publisher, screenshare, sdk, session;
|
||||||
var publisherConfig, fakeEvent;
|
var sessionData, subscriber, publisherConfig, fakeEvent;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = LoopMochaUtils.createSandbox();
|
sandbox = LoopMochaUtils.createSandbox();
|
||||||
@@ -59,6 +59,8 @@ describe("loop.OTSdkDriver", function() {
|
|||||||
}
|
}
|
||||||
}, Backbone.Events);
|
}, Backbone.Events);
|
||||||
|
|
||||||
|
screenshare = publisher;
|
||||||
|
|
||||||
subscriber = _.extend({
|
subscriber = _.extend({
|
||||||
_: {
|
_: {
|
||||||
getDataChannel: sinon.stub()
|
getDataChannel: sinon.stub()
|
||||||
@@ -141,11 +143,14 @@ describe("loop.OTSdkDriver", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#setupStreamElements", function() {
|
describe("#setupStreamElements", function() {
|
||||||
it("should call initPublisher", function() {
|
beforeEach(function() {
|
||||||
|
sandbox.stub(publisher, "off");
|
||||||
driver.setupStreamElements(new sharedActions.SetupStreamElements({
|
driver.setupStreamElements(new sharedActions.SetupStreamElements({
|
||||||
publisherConfig: publisherConfig
|
publisherConfig: publisherConfig
|
||||||
}));
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call initPublisher", function() {
|
||||||
var expectedConfig = _.extend({
|
var expectedConfig = _.extend({
|
||||||
channels: {
|
channels: {
|
||||||
text: {}
|
text: {}
|
||||||
@@ -157,6 +162,45 @@ describe("loop.OTSdkDriver", function() {
|
|||||||
sinon.match.instanceOf(HTMLDivElement),
|
sinon.match.instanceOf(HTMLDivElement),
|
||||||
expectedConfig);
|
expectedConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not do anything if publisher completed successfully", function() {
|
||||||
|
sdk.initPublisher.callArg(2);
|
||||||
|
|
||||||
|
sinon.assert.notCalled(publisher.off);
|
||||||
|
sinon.assert.notCalled(dispatcher.dispatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clean up publisher if an error occurred", function() {
|
||||||
|
sdk.initPublisher.callArgWith(2, { message: "FAKE" });
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(publisher.off);
|
||||||
|
sinon.assert.calledOnce(publisher.destroy);
|
||||||
|
expect(driver.publisher).to.equal(undefined);
|
||||||
|
expect(driver._mockPublisherEl).to.equal(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch ConnectionFailure if an error occurred", function() {
|
||||||
|
sdk.initPublisher.callArgWith(2, { message: "FAKE" });
|
||||||
|
|
||||||
|
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.ConnectionFailure({
|
||||||
|
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should notify metrics if an error occurred", function() {
|
||||||
|
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||||
|
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.ConnectionStatus({
|
||||||
|
event: "sdk.exception.123.FAKE",
|
||||||
|
state: "starting",
|
||||||
|
connections: 0,
|
||||||
|
sendStreams: 0,
|
||||||
|
recvStreams: 0
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#setMute", function() {
|
describe("#setMute", function() {
|
||||||
@@ -190,28 +234,76 @@ describe("loop.OTSdkDriver", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#startScreenShare", function() {
|
describe("#startScreenShare", function() {
|
||||||
beforeEach(function() {
|
var options = {};
|
||||||
sandbox.stub(driver, "_noteSharingState");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should initialize a publisher", function() {
|
beforeEach(function() {
|
||||||
// We're testing with `videoSource` set to 'browser', not 'window', as it
|
sandbox.stub(screenshare, "off");
|
||||||
// has multiple options.
|
sandbox.stub(driver, "_noteSharingState");
|
||||||
var options = {
|
options = {
|
||||||
videoSource: "browser",
|
videoSource: "browser",
|
||||||
constraints: {
|
constraints: {
|
||||||
browserWindow: 42,
|
browserWindow: 42,
|
||||||
scrollWithPage: true
|
scrollWithPage: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
driver.startScreenShare(options);
|
|
||||||
|
|
||||||
|
driver.startScreenShare(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize a publisher", function() {
|
||||||
sinon.assert.calledOnce(sdk.initPublisher);
|
sinon.assert.calledOnce(sdk.initPublisher);
|
||||||
sinon.assert.calledWithMatch(sdk.initPublisher,
|
sinon.assert.calledWithMatch(sdk.initPublisher,
|
||||||
sinon.match.instanceOf(HTMLDivElement), options);
|
sinon.match.instanceOf(HTMLDivElement), options);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should log a telemetry action", function() {
|
it("should log a telemetry action", function() {
|
||||||
|
sinon.assert.calledWithExactly(driver._noteSharingState, "browser", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not do anything if publisher completed successfully", function() {
|
||||||
|
sdk.initPublisher.callArg(2);
|
||||||
|
|
||||||
|
sinon.assert.notCalled(screenshare.off);
|
||||||
|
sinon.assert.notCalled(dispatcher.dispatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clean up publisher if an error occurred", function() {
|
||||||
|
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(screenshare.off);
|
||||||
|
sinon.assert.calledOnce(screenshare.destroy);
|
||||||
|
expect(driver.screenshare).to.equal(undefined);
|
||||||
|
expect(driver._mockScreenSharePreviewEl).to.equal(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch ConnectionFailure if an error occurred", function() {
|
||||||
|
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||||
|
|
||||||
|
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.ScreenSharingState({
|
||||||
|
state: SCREEN_SHARE_STATES.INACTIVE
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should notify metrics if an error occurred", function() {
|
||||||
|
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||||
|
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.ConnectionStatus({
|
||||||
|
event: "sdk.exception.screen.123.FAKE",
|
||||||
|
state: "starting",
|
||||||
|
connections: 0,
|
||||||
|
sendStreams: 0,
|
||||||
|
recvStreams: 0
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Screenshare Access Denied", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
sandbox.stub(screenshare, "off");
|
||||||
|
sandbox.stub(driver, "_noteSharingState");
|
||||||
var options = {
|
var options = {
|
||||||
videoSource: "browser",
|
videoSource: "browser",
|
||||||
constraints: {
|
constraints: {
|
||||||
@@ -220,8 +312,23 @@ describe("loop.OTSdkDriver", function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
driver.startScreenShare(options);
|
driver.startScreenShare(options);
|
||||||
|
sdk.initPublisher.callArg(2);
|
||||||
|
driver.screenshare.trigger("accessDenied");
|
||||||
|
});
|
||||||
|
|
||||||
sinon.assert.calledWithExactly(driver._noteSharingState, "browser", true);
|
it("should clean up publisher", function() {
|
||||||
|
sinon.assert.calledOnce(screenshare.off);
|
||||||
|
sinon.assert.calledOnce(screenshare.destroy);
|
||||||
|
expect(driver.screenshare).to.equal(undefined);
|
||||||
|
expect(driver._mockScreenSharePreviewEl).to.equal(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch ConnectionFailure", function() {
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.ScreenSharingState({
|
||||||
|
state: SCREEN_SHARE_STATES.INACTIVE
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1678,69 +1785,6 @@ describe("loop.OTSdkDriver", function() {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Unable to publish (not GetUserMedia)", function() {
|
|
||||||
it("should notify metrics", function() {
|
|
||||||
sdk.trigger("exception", {
|
|
||||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
|
||||||
message: "Fake",
|
|
||||||
title: "Connect Failed"
|
|
||||||
});
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
||||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
|
||||||
new sharedActions.ConnectionStatus({
|
|
||||||
event: "sdk.exception." + OT.ExceptionCodes.UNABLE_TO_PUBLISH + ".Fake",
|
|
||||||
state: "starting",
|
|
||||||
connections: 0,
|
|
||||||
sendStreams: 0,
|
|
||||||
recvStreams: 0
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Unable to publish (GetUserMedia)", function() {
|
|
||||||
it("should destroy the publisher", function() {
|
|
||||||
sdk.trigger("exception", {
|
|
||||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
|
||||||
message: "GetUserMedia"
|
|
||||||
});
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(publisher.destroy);
|
|
||||||
});
|
|
||||||
|
|
||||||
// XXX We should remove this when we stop being unable to publish as a
|
|
||||||
// workaround for knowing if the user has video as well as audio devices
|
|
||||||
// installed (bug 1138851).
|
|
||||||
it("should not notify metrics", function() {
|
|
||||||
sdk.trigger("exception", {
|
|
||||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
|
||||||
message: "GetUserMedia"
|
|
||||||
});
|
|
||||||
|
|
||||||
sinon.assert.neverCalledWith(dispatcher.dispatch,
|
|
||||||
new sharedActions.ConnectionStatus({
|
|
||||||
event: "sdk.exception." + OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
|
||||||
state: "starting",
|
|
||||||
connections: 0,
|
|
||||||
sendStreams: 0,
|
|
||||||
recvStreams: 0
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should dispatch a ConnectionFailure action", function() {
|
|
||||||
sdk.trigger("exception", {
|
|
||||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
|
||||||
message: "GetUserMedia"
|
|
||||||
});
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
||||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
|
||||||
new sharedActions.ConnectionFailure({
|
|
||||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("ToS Failure", function() {
|
describe("ToS Failure", function() {
|
||||||
it("should dispatch a ConnectionFailure action", function() {
|
it("should dispatch a ConnectionFailure action", function() {
|
||||||
sdk.trigger("exception", {
|
sdk.trigger("exception", {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class TestSharedUnits(BaseTestFrontendUnits):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestSharedUnits, self).setUp()
|
super(TestSharedUnits, self).setUp()
|
||||||
self.set_server_prefix(".")
|
self.set_server_prefix("../../../../")
|
||||||
|
|
||||||
def test_units(self):
|
def test_units(self):
|
||||||
self.check_page("index.html")
|
self.check_page("chrome/content/shared/test/index.html")
|
||||||
|
|||||||
@@ -86,6 +86,34 @@ describe("loop.store.TextChatStore", function() {
|
|||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should add the context tile to the list", function() {
|
||||||
|
store.receivedTextChatMessage({
|
||||||
|
type: CHAT_MESSAGE_TYPES.SENT,
|
||||||
|
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||||
|
message: "fake",
|
||||||
|
extraData: {
|
||||||
|
roomToken: "fakeRoomToken",
|
||||||
|
newRoomThumbnail: "favicon",
|
||||||
|
newRoomURL: "https://www.fakeurl.com"
|
||||||
|
},
|
||||||
|
sentTimestamp: "2015-06-24T23:58:53.848Z",
|
||||||
|
receivedTimestamp: "1970-01-01T00:00:00.000Z"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.getStoreState("messageList")).eql([{
|
||||||
|
type: CHAT_MESSAGE_TYPES.RECEIVED,
|
||||||
|
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||||
|
message: "fake",
|
||||||
|
extraData: {
|
||||||
|
roomToken: "fakeRoomToken",
|
||||||
|
newRoomThumbnail: "favicon",
|
||||||
|
newRoomURL: "https://www.fakeurl.com"
|
||||||
|
},
|
||||||
|
sentTimestamp: "2015-06-24T23:58:53.848Z",
|
||||||
|
receivedTimestamp: "1970-01-01T00:00:00.000Z"
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should not add messages for unknown content types", function() {
|
it("should not add messages for unknown content types", function() {
|
||||||
store.receivedTextChatMessage({
|
store.receivedTextChatMessage({
|
||||||
contentType: "invalid type",
|
contentType: "invalid type",
|
||||||
@@ -257,4 +285,53 @@ describe("loop.store.TextChatStore", function() {
|
|||||||
sinon.assert.notCalled(window.dispatchEvent);
|
sinon.assert.notCalled(window.dispatchEvent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#updateRoomContext", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
store.setStoreState({ messageList: [] });
|
||||||
|
store.updateRoomContext(new sharedActions.UpdateRoomContext({
|
||||||
|
newRoomDescription: "fake",
|
||||||
|
newRoomThumbnail: "favicon",
|
||||||
|
newRoomURL: "https://www.fakeurl.com",
|
||||||
|
roomToken: "fakeRoomToken"
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add the room context to the list", function() {
|
||||||
|
expect(store.getStoreState("messageList")).eql([{
|
||||||
|
type: CHAT_MESSAGE_TYPES.SENT,
|
||||||
|
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||||
|
message: "fake",
|
||||||
|
extraData: {
|
||||||
|
roomToken: "fakeRoomToken",
|
||||||
|
newRoomThumbnail: "favicon",
|
||||||
|
newRoomURL: "https://www.fakeurl.com"
|
||||||
|
},
|
||||||
|
sentTimestamp: "1970-01-01T00:00:00.000Z",
|
||||||
|
receivedTimestamp: undefined
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not add the room context if the last tile has the same domain", function() {
|
||||||
|
store.updateRoomContext(new sharedActions.UpdateRoomContext({
|
||||||
|
newRoomDescription: "fake",
|
||||||
|
newRoomThumbnail: "favicon",
|
||||||
|
newRoomURL: "https://www.fakeurl.com/test/same_domain",
|
||||||
|
roomToken: "fakeRoomToken"
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(store.getStoreState("messageList").length).eql(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add the room context if the last tile has not the same domain", function() {
|
||||||
|
store.updateRoomContext(new sharedActions.UpdateRoomContext({
|
||||||
|
newRoomDescription: "fake",
|
||||||
|
newRoomThumbnail: "favicon",
|
||||||
|
newRoomURL: "https://www.myfakeurl.com",
|
||||||
|
roomToken: "fakeRoomToken"
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(store.getStoreState("messageList").length).eql(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,18 +6,17 @@ describe("loop.shared.views.TextChatView", function() {
|
|||||||
|
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedUtils = loop.shared.utils;
|
|
||||||
var sharedViews = loop.shared.views;
|
|
||||||
var TestUtils = React.addons.TestUtils;
|
var TestUtils = React.addons.TestUtils;
|
||||||
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
|
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
|
||||||
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
|
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
|
||||||
var fixtures = document.querySelector("#fixtures");
|
var fixtures = document.querySelector("#fixtures");
|
||||||
|
var mozL10n = navigator.mozL10n || document.mozL10n;
|
||||||
|
|
||||||
var dispatcher, fakeSdkDriver, sandbox, store, fakeClock;
|
var dispatcher, fakeSdkDriver, originalLanguage, sandbox, store;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = LoopMochaUtils.createSandbox();
|
sandbox = LoopMochaUtils.createSandbox();
|
||||||
fakeClock = sandbox.useFakeTimers();
|
sandbox.useFakeTimers();
|
||||||
|
|
||||||
dispatcher = new loop.Dispatcher();
|
dispatcher = new loop.Dispatcher();
|
||||||
sandbox.stub(dispatcher, "dispatch");
|
sandbox.stub(dispatcher, "dispatch");
|
||||||
@@ -33,11 +32,23 @@ describe("loop.shared.views.TextChatView", function() {
|
|||||||
loop.store.StoreMixin.register({
|
loop.store.StoreMixin.register({
|
||||||
textChatStore: store
|
textChatStore: store
|
||||||
});
|
});
|
||||||
|
|
||||||
|
originalLanguage = mozL10n.language;
|
||||||
|
|
||||||
|
mozL10n.language = {
|
||||||
|
code: "en-US",
|
||||||
|
direction: "rtl"
|
||||||
|
};
|
||||||
|
|
||||||
|
sandbox.stub(mozL10n, "get", function(string) {
|
||||||
|
return string;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
React.unmountComponentAtNode(fixtures);
|
React.unmountComponentAtNode(fixtures);
|
||||||
|
mozL10n.language = originalLanguage;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("TextChatEntriesView", function() {
|
describe("TextChatEntriesView", function() {
|
||||||
@@ -411,10 +422,6 @@ describe("loop.shared.views.TextChatView", function() {
|
|||||||
// Fake server to catch all XHR requests.
|
// Fake server to catch all XHR requests.
|
||||||
fakeServer = sinon.fakeServer.create();
|
fakeServer = sinon.fakeServer.create();
|
||||||
store.setStoreState({ textChatEnabled: true });
|
store.setStoreState({ textChatEnabled: true });
|
||||||
|
|
||||||
sandbox.stub(navigator.mozL10n, "get", function(string) {
|
|
||||||
return string;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ describe("loop.shared.utils", function() {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
|
var mozL10n = navigator.mozL10n || document.mozL10n;
|
||||||
var sandbox;
|
var sandbox;
|
||||||
var sharedUtils = loop.shared.utils;
|
var sharedUtils = loop.shared.utils;
|
||||||
|
|
||||||
@@ -333,7 +334,7 @@ describe("loop.shared.utils", function() {
|
|||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
// fake mozL10n
|
// fake mozL10n
|
||||||
sandbox.stub(navigator.mozL10n, "get", function(id) {
|
sandbox.stub(mozL10n, "get", function(id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case "share_email_subject7":
|
case "share_email_subject7":
|
||||||
return "subject";
|
return "subject";
|
||||||
@@ -635,45 +636,4 @@ describe("loop.shared.utils", function() {
|
|||||||
expect(obj).to.eql({ prop1: "null", prop3: true });
|
expect(obj).to.eql({ prop1: "null", prop3: true });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#truncate", function() {
|
|
||||||
describe("ltr support", function() {
|
|
||||||
it("should default to 72 chars", function() {
|
|
||||||
var output = sharedUtils.truncate(new Array(75).join());
|
|
||||||
|
|
||||||
expect(output.length).to.eql(73); // 72 + …
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should take a max size argument", function() {
|
|
||||||
var output = sharedUtils.truncate(new Array(73).join(), 20);
|
|
||||||
|
|
||||||
expect(output.length).to.eql(21); // 20 + …
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("rtl support", function() {
|
|
||||||
var directionStub;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
// XXX should use sandbox
|
|
||||||
// https://github.com/cjohansen/Sinon.JS/issues/781
|
|
||||||
directionStub = sinon.stub(navigator.mozL10n.language, "direction", {
|
|
||||||
get: function() {
|
|
||||||
return "rtl";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
directionStub.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support RTL", function() {
|
|
||||||
var output = sharedUtils.truncate(new Array(73).join(), 20);
|
|
||||||
|
|
||||||
expect(output.length).to.eql(21); // 20 + …
|
|
||||||
expect(output.substr(0, 1)).to.eql("…");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
371
browser/extensions/loop/chrome/content/shared/test/vendor/chai-as-promised.js
vendored
Normal file
371
browser/extensions/loop/chrome/content/shared/test/vendor/chai-as-promised.js
vendored
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Module systems magic dance.
|
||||||
|
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
||||||
|
// NodeJS
|
||||||
|
module.exports = chaiAsPromised;
|
||||||
|
} else if (typeof define === "function" && define.amd) {
|
||||||
|
// AMD
|
||||||
|
define(function () {
|
||||||
|
return chaiAsPromised;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/*global self: false */
|
||||||
|
|
||||||
|
// Other environment (usually <script> tag): plug in to global chai instance directly.
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
|
// Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
|
||||||
|
self.chaiAsPromised = chaiAsPromised;
|
||||||
|
}
|
||||||
|
|
||||||
|
chaiAsPromised.transferPromiseness = function (assertion, promise) {
|
||||||
|
assertion.then = promise.then.bind(promise);
|
||||||
|
};
|
||||||
|
|
||||||
|
chaiAsPromised.transformAsserterArgs = function (values) {
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
function chaiAsPromised(chai, utils) {
|
||||||
|
var Assertion = chai.Assertion;
|
||||||
|
var assert = chai.assert;
|
||||||
|
|
||||||
|
function isJQueryPromise(thenable) {
|
||||||
|
return typeof thenable.always === "function" &&
|
||||||
|
typeof thenable.done === "function" &&
|
||||||
|
typeof thenable.fail === "function" &&
|
||||||
|
typeof thenable.pipe === "function" &&
|
||||||
|
typeof thenable.progress === "function" &&
|
||||||
|
typeof thenable.state === "function";
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertIsAboutPromise(assertion) {
|
||||||
|
if (typeof assertion._obj.then !== "function") {
|
||||||
|
throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
|
||||||
|
}
|
||||||
|
if (isJQueryPromise(assertion._obj)) {
|
||||||
|
throw new TypeError("Chai as Promised is incompatible with jQuery's thenables, sorry! Please use a " +
|
||||||
|
"Promises/A+ compatible library (see http://promisesaplus.com/).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function method(name, asserter) {
|
||||||
|
utils.addMethod(Assertion.prototype, name, function () {
|
||||||
|
assertIsAboutPromise(this);
|
||||||
|
return asserter.apply(this, arguments);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function property(name, asserter) {
|
||||||
|
utils.addProperty(Assertion.prototype, name, function () {
|
||||||
|
assertIsAboutPromise(this);
|
||||||
|
return asserter.apply(this, arguments);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doNotify(promise, done) {
|
||||||
|
promise.then(function () { done(); }, done);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
|
||||||
|
function assertIfNegated(assertion, message, extra) {
|
||||||
|
assertion.assert(true, null, message, extra.expected, extra.actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertIfNotNegated(assertion, message, extra) {
|
||||||
|
assertion.assert(false, message, null, extra.expected, extra.actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBasePromise(assertion) {
|
||||||
|
// We need to chain subsequent asserters on top of ones in the chain already (consider
|
||||||
|
// `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
|
||||||
|
// So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
|
||||||
|
// previously derived promises, to chain off of.
|
||||||
|
return typeof assertion.then === "function" ? assertion : assertion._obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab these first, before we modify `Assertion.prototype`.
|
||||||
|
|
||||||
|
var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
|
||||||
|
|
||||||
|
var propertyDescs = {};
|
||||||
|
propertyNames.forEach(function (name) {
|
||||||
|
propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
|
||||||
|
});
|
||||||
|
|
||||||
|
property("fulfilled", function () {
|
||||||
|
var that = this;
|
||||||
|
var derivedPromise = getBasePromise(that).then(
|
||||||
|
function (value) {
|
||||||
|
that._obj = value;
|
||||||
|
assertIfNegated(that,
|
||||||
|
"expected promise not to be fulfilled but it was fulfilled with #{act}",
|
||||||
|
{ actual: value });
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
function (reason) {
|
||||||
|
assertIfNotNegated(that,
|
||||||
|
"expected promise to be fulfilled but it was rejected with #{act}",
|
||||||
|
{ actual: reason });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||||
|
});
|
||||||
|
|
||||||
|
property("rejected", function () {
|
||||||
|
var that = this;
|
||||||
|
var derivedPromise = getBasePromise(that).then(
|
||||||
|
function (value) {
|
||||||
|
that._obj = value;
|
||||||
|
assertIfNotNegated(that,
|
||||||
|
"expected promise to be rejected but it was fulfilled with #{act}",
|
||||||
|
{ actual: value });
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
function (reason) {
|
||||||
|
assertIfNegated(that,
|
||||||
|
"expected promise not to be rejected but it was rejected with #{act}",
|
||||||
|
{ actual: reason });
|
||||||
|
|
||||||
|
// Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
|
||||||
|
// `promise.should.be.rejected.and.eventually.equal("reason")`.
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||||
|
});
|
||||||
|
|
||||||
|
method("rejectedWith", function (Constructor, message) {
|
||||||
|
var desiredReason = null;
|
||||||
|
var constructorName = null;
|
||||||
|
|
||||||
|
if (Constructor instanceof RegExp || typeof Constructor === "string") {
|
||||||
|
message = Constructor;
|
||||||
|
Constructor = null;
|
||||||
|
} else if (Constructor && Constructor instanceof Error) {
|
||||||
|
desiredReason = Constructor;
|
||||||
|
Constructor = null;
|
||||||
|
message = null;
|
||||||
|
} else if (typeof Constructor === "function") {
|
||||||
|
constructorName = (new Constructor()).name;
|
||||||
|
} else {
|
||||||
|
Constructor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
var derivedPromise = getBasePromise(that).then(
|
||||||
|
function (value) {
|
||||||
|
var assertionMessage = null;
|
||||||
|
var expected = null;
|
||||||
|
|
||||||
|
if (Constructor) {
|
||||||
|
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||||
|
"#{act}";
|
||||||
|
expected = constructorName;
|
||||||
|
} else if (message) {
|
||||||
|
var verb = message instanceof RegExp ? "matching" : "including";
|
||||||
|
assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it " +
|
||||||
|
"was fulfilled with #{act}";
|
||||||
|
expected = message;
|
||||||
|
} else if (desiredReason) {
|
||||||
|
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||||
|
"#{act}";
|
||||||
|
expected = desiredReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
that._obj = value;
|
||||||
|
|
||||||
|
assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
|
||||||
|
},
|
||||||
|
function (reason) {
|
||||||
|
if (Constructor) {
|
||||||
|
that.assert(reason instanceof Constructor,
|
||||||
|
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||||
|
"expected promise not to be rejected with #{exp} but it was rejected with #{act}",
|
||||||
|
constructorName,
|
||||||
|
reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
var reasonMessage = utils.type(reason) === "object" && "message" in reason ?
|
||||||
|
reason.message :
|
||||||
|
"" + reason;
|
||||||
|
if (message && reasonMessage !== null && reasonMessage !== undefined) {
|
||||||
|
if (message instanceof RegExp) {
|
||||||
|
that.assert(message.test(reasonMessage),
|
||||||
|
"expected promise to be rejected with an error matching #{exp} but got #{act}",
|
||||||
|
"expected promise not to be rejected with an error matching #{exp}",
|
||||||
|
message,
|
||||||
|
reasonMessage);
|
||||||
|
}
|
||||||
|
if (typeof message === "string") {
|
||||||
|
that.assert(reasonMessage.indexOf(message) !== -1,
|
||||||
|
"expected promise to be rejected with an error including #{exp} but got #{act}",
|
||||||
|
"expected promise not to be rejected with an error including #{exp}",
|
||||||
|
message,
|
||||||
|
reasonMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desiredReason) {
|
||||||
|
that.assert(reason === desiredReason,
|
||||||
|
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||||
|
"expected promise not to be rejected with #{exp}",
|
||||||
|
desiredReason,
|
||||||
|
reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||||
|
});
|
||||||
|
|
||||||
|
property("eventually", function () {
|
||||||
|
utils.flag(this, "eventually", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
method("notify", function (done) {
|
||||||
|
doNotify(getBasePromise(this), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
method("become", function (value) {
|
||||||
|
return this.eventually.deep.equal(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
////////
|
||||||
|
// `eventually`
|
||||||
|
|
||||||
|
// We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
|
||||||
|
var methodNames = propertyNames.filter(function (name) {
|
||||||
|
return name !== "assert" && typeof propertyDescs[name].value === "function";
|
||||||
|
});
|
||||||
|
|
||||||
|
methodNames.forEach(function (methodName) {
|
||||||
|
Assertion.overwriteMethod(methodName, function (originalMethod) {
|
||||||
|
return function () {
|
||||||
|
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var getterNames = propertyNames.filter(function (name) {
|
||||||
|
return name !== "_obj" && typeof propertyDescs[name].get === "function";
|
||||||
|
});
|
||||||
|
|
||||||
|
getterNames.forEach(function (getterName) {
|
||||||
|
// Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
|
||||||
|
// `should.be.an("object")`. We need to handle those specially.
|
||||||
|
var isChainableMethod = Assertion.prototype.__methods.hasOwnProperty(getterName);
|
||||||
|
|
||||||
|
if (isChainableMethod) {
|
||||||
|
Assertion.overwriteChainableMethod(
|
||||||
|
getterName,
|
||||||
|
function (originalMethod) {
|
||||||
|
return function() {
|
||||||
|
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
function (originalGetter) {
|
||||||
|
return function() {
|
||||||
|
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Assertion.overwriteProperty(getterName, function (originalGetter) {
|
||||||
|
return function () {
|
||||||
|
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function doAsserterAsyncAndAddThen(asserter, assertion, args) {
|
||||||
|
// Since we're intercepting all methods/properties, we need to just pass through if they don't want
|
||||||
|
// `eventually`, or if we've already fulfilled the promise (see below).
|
||||||
|
if (!utils.flag(assertion, "eventually")) {
|
||||||
|
return asserter.apply(assertion, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
var derivedPromise = getBasePromise(assertion).then(function (value) {
|
||||||
|
// Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
|
||||||
|
// now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
|
||||||
|
// just the base Chai code that we get to via the short-circuit above.
|
||||||
|
assertion._obj = value;
|
||||||
|
utils.flag(assertion, "eventually", false);
|
||||||
|
|
||||||
|
return args ? chaiAsPromised.transformAsserterArgs(args) : args;
|
||||||
|
}).then(function (args) {
|
||||||
|
asserter.apply(assertion, args);
|
||||||
|
|
||||||
|
// Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
|
||||||
|
// flag), we need to communicate this value change to subsequent chained asserters. Since we build a
|
||||||
|
// promise chain paralleling the asserter chain, we can use it to communicate such changes.
|
||||||
|
return assertion._obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
chaiAsPromised.transferPromiseness(assertion, derivedPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////
|
||||||
|
// Now use the `Assertion` framework to build an `assert` interface.
|
||||||
|
var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
|
||||||
|
return typeof assert[propName] === "function";
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.isFulfilled = function (promise, message) {
|
||||||
|
return (new Assertion(promise, message)).to.be.fulfilled;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.isRejected = function (promise, toTestAgainst, message) {
|
||||||
|
if (typeof toTestAgainst === "string") {
|
||||||
|
message = toTestAgainst;
|
||||||
|
toTestAgainst = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assertion = (new Assertion(promise, message));
|
||||||
|
return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.becomes = function (promise, value, message) {
|
||||||
|
return assert.eventually.deepEqual(promise, value, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.doesNotBecome = function (promise, value, message) {
|
||||||
|
return assert.eventually.notDeepEqual(promise, value, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.eventually = {};
|
||||||
|
originalAssertMethods.forEach(function (assertMethodName) {
|
||||||
|
assert.eventually[assertMethodName] = function (promise) {
|
||||||
|
var otherArgs = Array.prototype.slice.call(arguments, 1);
|
||||||
|
|
||||||
|
var customRejectionHandler;
|
||||||
|
var message = arguments[assert[assertMethodName].length - 1];
|
||||||
|
if (typeof message === "string") {
|
||||||
|
customRejectionHandler = function (reason) {
|
||||||
|
throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var returnedPromise = promise.then(
|
||||||
|
function (fulfillmentValue) {
|
||||||
|
return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
|
||||||
|
},
|
||||||
|
customRejectionHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
returnedPromise.notify = function (done) {
|
||||||
|
doNotify(returnedPromise, done);
|
||||||
|
};
|
||||||
|
|
||||||
|
return returnedPromise;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}());
|
||||||
5352
browser/extensions/loop/chrome/content/shared/test/vendor/chai.js
vendored
Normal file
5352
browser/extensions/loop/chrome/content/shared/test/vendor/chai.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
270
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
vendored
Normal file
270
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
vendored
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
@charset "utf-8";
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha {
|
||||||
|
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
margin: 60px 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha ul,
|
||||||
|
#mocha li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha h1,
|
||||||
|
#mocha h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha h1 {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha h1 a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha h1 a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .suite .suite h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha h2 {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .suite {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test {
|
||||||
|
margin-left: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pending:hover h2::after {
|
||||||
|
content: '(pending)';
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pass.medium .duration {
|
||||||
|
background: #c09853;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pass.slow .duration {
|
||||||
|
background: #b94a48;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pass::before {
|
||||||
|
content: '✓';
|
||||||
|
font-size: 12px;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
margin-right: 5px;
|
||||||
|
color: #00d6b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pass .duration {
|
||||||
|
font-size: 9px;
|
||||||
|
margin-left: 5px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
color: #fff;
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||||
|
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
-ms-border-radius: 5px;
|
||||||
|
-o-border-radius: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pass.fast .duration {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pending {
|
||||||
|
color: #0b97c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.pending::before {
|
||||||
|
content: '◦';
|
||||||
|
color: #0b97c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.fail {
|
||||||
|
color: #c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.fail pre {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test.fail::before {
|
||||||
|
content: '✖';
|
||||||
|
font-size: 12px;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
margin-right: 5px;
|
||||||
|
color: #c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test pre.error {
|
||||||
|
color: #c00;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (1): approximate for browsers not supporting calc
|
||||||
|
* (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
|
||||||
|
* ^^ seriously
|
||||||
|
*/
|
||||||
|
#mocha .test pre {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
font: 12px/1.5 monaco, monospace;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
max-width: 85%; /*(1)*/
|
||||||
|
max-width: calc(100% - 42px); /*(2)*/
|
||||||
|
word-wrap: break-word;
|
||||||
|
border-bottom-color: #ddd;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-webkit-box-shadow: 0 1px 3px #eee;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-moz-box-shadow: 0 1px 3px #eee;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test h2 {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test a.replay {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
right: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: block;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
text-align: center;
|
||||||
|
background: #eee;
|
||||||
|
font-size: 15px;
|
||||||
|
-moz-border-radius: 15px;
|
||||||
|
border-radius: 15px;
|
||||||
|
-webkit-transition: opacity 200ms;
|
||||||
|
-moz-transition: opacity 200ms;
|
||||||
|
transition: opacity 200ms;
|
||||||
|
opacity: 0.3;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha .test:hover a.replay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-report.pass .test.fail {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-report.fail .test.pass {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-report.pending .test.pass,
|
||||||
|
#mocha-report.pending .test.fail {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#mocha-report.pending .test.pass.pending {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-error {
|
||||||
|
color: #c00;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 100;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-stats {
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
|
right: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0;
|
||||||
|
color: #888;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-stats .progress {
|
||||||
|
float: right;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-stats em {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-stats a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-stats a:hover {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-stats li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 5px;
|
||||||
|
list-style: none;
|
||||||
|
padding-top: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha-stats canvas {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha code .comment { color: #ddd; }
|
||||||
|
#mocha code .init { color: #2f6fad; }
|
||||||
|
#mocha code .string { color: #5890ad; }
|
||||||
|
#mocha code .keyword { color: #8a6343; }
|
||||||
|
#mocha code .number { color: #2f6fad; }
|
||||||
|
|
||||||
|
@media screen and (max-device-width: 480px) {
|
||||||
|
#mocha {
|
||||||
|
margin: 60px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mocha #stats {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
6564
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
vendored
Normal file
6564
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6194
browser/extensions/loop/chrome/content/shared/test/vendor/sinon.js
vendored
Normal file
6194
browser/extensions/loop/chrome/content/shared/test/vendor/sinon.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,12 +11,11 @@ describe("loop.shared.views", function() {
|
|||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
var getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass;
|
var sandbox, clock, dispatcher, OS, OSVersion;
|
||||||
var sandbox, fakeAudioXHR, dispatcher, OS, OSVersion;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = LoopMochaUtils.createSandbox();
|
sandbox = LoopMochaUtils.createSandbox();
|
||||||
sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
|
clock = sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
|
||||||
sandbox.stub(l10n, "get", function(x) {
|
sandbox.stub(l10n, "get", function(x) {
|
||||||
return "translated:" + x;
|
return "translated:" + x;
|
||||||
});
|
});
|
||||||
@@ -28,20 +27,6 @@ describe("loop.shared.views", function() {
|
|||||||
dispatcher = new loop.Dispatcher();
|
dispatcher = new loop.Dispatcher();
|
||||||
sandbox.stub(dispatcher, "dispatch");
|
sandbox.stub(dispatcher, "dispatch");
|
||||||
|
|
||||||
fakeAudioXHR = {
|
|
||||||
open: sinon.spy(),
|
|
||||||
send: function() {},
|
|
||||||
abort: function() {},
|
|
||||||
getResponseHeader: function(header) {
|
|
||||||
if (header === "Content-Type") {
|
|
||||||
return "audio/ogg";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responseType: null,
|
|
||||||
response: new ArrayBuffer(10),
|
|
||||||
onload: null
|
|
||||||
};
|
|
||||||
|
|
||||||
OS = "mac";
|
OS = "mac";
|
||||||
OSVersion = { major: 10, minor: 10 };
|
OSVersion = { major: 10, minor: 10 };
|
||||||
sandbox.stub(loop.shared.utils, "getOS", function() {
|
sandbox.stub(loop.shared.utils, "getOS", function() {
|
||||||
@@ -108,177 +93,12 @@ describe("loop.shared.views", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SettingsControlButton", function() {
|
|
||||||
var requestStubs;
|
|
||||||
var support_url = "https://support.com";
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
LoopMochaUtils.stubLoopRequest(requestStubs = {
|
|
||||||
OpenURL: sandbox.stub(),
|
|
||||||
SetLoopPref: sandbox.stub(),
|
|
||||||
GetLoopPref: function(prefName) {
|
|
||||||
switch (prefName) {
|
|
||||||
case "support_url":
|
|
||||||
return support_url;
|
|
||||||
default:
|
|
||||||
return prefName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sandbox.stub(console, "error");
|
|
||||||
});
|
|
||||||
|
|
||||||
function mountTestComponent(props) {
|
|
||||||
return TestUtils.renderIntoDocument(
|
|
||||||
React.createElement(sharedViews.SettingsControlButton, props));
|
|
||||||
}
|
|
||||||
|
|
||||||
it("should render a visible button", function() {
|
|
||||||
var settingsMenuItems = [{ id: "help" }];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
|
|
||||||
var node = comp.getDOMNode().querySelector(".btn-settings");
|
|
||||||
expect(node.classList.contains("hide")).eql(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not render anything", function() {
|
|
||||||
var comp = mountTestComponent();
|
|
||||||
expect(comp.getDOMNode()).to.eql(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not show an undefined menu option", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{ id: "not Defined" },
|
|
||||||
{ id: "help" }
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
var menuItems = comp.getDOMNode().querySelectorAll(".settings-menu > li");
|
|
||||||
expect(menuItems).to.have.length.of(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should log an error for an undefined menu option", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{ id: "not Defined" },
|
|
||||||
{ id: "help" }
|
|
||||||
];
|
|
||||||
|
|
||||||
mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(console.error);
|
|
||||||
sinon.assert.calledWithMatch(console.error, "Invalid");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not render anything if not exists any valid item to show", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{ id: "not Defined" },
|
|
||||||
{ id: "another wrong menu item" }
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
expect(comp.getDOMNode()).to.eql(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show the settings dropdown on click", function() {
|
|
||||||
var settingsMenuItems = [{ id: "help" }];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
|
|
||||||
expect(comp.state.showMenu).eql(false);
|
|
||||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(".btn-settings"));
|
|
||||||
|
|
||||||
expect(comp.state.showMenu).eql(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have a `menu-below` class on the dropdown when the prop is set.", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{ id: "help" }
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({
|
|
||||||
menuBelow: true,
|
|
||||||
menuItems: settingsMenuItems
|
|
||||||
});
|
|
||||||
var menuItems = comp.getDOMNode().querySelector(".settings-menu");
|
|
||||||
|
|
||||||
expect(menuItems.classList.contains("menu-below")).eql(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not have a `menu-below` class on the dropdown when the prop is not set.", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{ id: "help" }
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({
|
|
||||||
menuItems: settingsMenuItems
|
|
||||||
});
|
|
||||||
var menuItems = comp.getDOMNode().querySelector(".settings-menu");
|
|
||||||
|
|
||||||
expect(menuItems.classList.contains("menu-below")).eql(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show edit Context on menu when the option is enabled", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{
|
|
||||||
id: "edit",
|
|
||||||
enabled: true,
|
|
||||||
visible: true,
|
|
||||||
onClick: function() {}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
|
|
||||||
var node = comp.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
|
|
||||||
expect(node.classList.contains("hide")).eql(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should hide edit Context on menu when the option is not visible", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{
|
|
||||||
id: "edit",
|
|
||||||
enabled: false,
|
|
||||||
visible: false,
|
|
||||||
onClick: function() {}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
|
|
||||||
var node = comp.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
|
|
||||||
expect(node.classList.contains("hide")).eql(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call onClick method when the edit context menu item is clicked", function() {
|
|
||||||
var onClickCalled = false;
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{
|
|
||||||
id: "edit",
|
|
||||||
enabled: true,
|
|
||||||
visible: true,
|
|
||||||
onClick: sandbox.stub()
|
|
||||||
}
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit"));
|
|
||||||
sinon.assert.calledOnce(settingsMenuItems[0].onClick);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should open a tab to the support url when the support menu item is clicked", function() {
|
|
||||||
var settingsMenuItems = [
|
|
||||||
{ id: "help" }
|
|
||||||
];
|
|
||||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(".settings-menu > li:last-child"));
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.OpenURL);
|
|
||||||
sinon.assert.calledWithExactly(requestStubs.OpenURL, support_url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("ConversationToolbar", function() {
|
describe("ConversationToolbar", function() {
|
||||||
var clock, hangup, publishStream;
|
var hangup, publishStream;
|
||||||
|
|
||||||
function mountTestComponent(props) {
|
function mountTestComponent(props) {
|
||||||
props = _.extend({
|
props = _.extend({
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher
|
||||||
show: true
|
|
||||||
}, props || {});
|
}, props || {});
|
||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
React.createElement(sharedViews.ConversationToolbar, props));
|
React.createElement(sharedViews.ConversationToolbar, props));
|
||||||
@@ -287,21 +107,6 @@ describe("loop.shared.views", function() {
|
|||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
hangup = sandbox.stub();
|
hangup = sandbox.stub();
|
||||||
publishStream = sandbox.stub();
|
publishStream = sandbox.stub();
|
||||||
clock = sinon.useFakeTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
clock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not render the component when 'show' is false", function() {
|
|
||||||
var comp = mountTestComponent({
|
|
||||||
hangup: hangup,
|
|
||||||
publishStream: publishStream,
|
|
||||||
show: false
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(comp.getDOMNode()).to.eql(null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should start no idle", function() {
|
it("should start no idle", function() {
|
||||||
@@ -743,7 +548,7 @@ describe("loop.shared.views", function() {
|
|||||||
expect(element.className).eql("no-video");
|
expect(element.className).eql("no-video");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display a video element if a source object is supplied", function() {
|
it("should display a video element if a source object is supplied", function(done) {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
displayAvatar: false,
|
displayAvatar: false,
|
||||||
mediaType: "local",
|
mediaType: "local",
|
||||||
@@ -758,7 +563,18 @@ describe("loop.shared.views", function() {
|
|||||||
|
|
||||||
expect(element).not.eql(null);
|
expect(element).not.eql(null);
|
||||||
expect(element.className).eql("local-video");
|
expect(element.className).eql("local-video");
|
||||||
|
|
||||||
|
// Google Chrome doesn't seem to set "muted" on the element at creation
|
||||||
|
// time, so we need to do the test as async.
|
||||||
|
clock.restore();
|
||||||
|
setTimeout(function() {
|
||||||
|
try {
|
||||||
expect(element.muted).eql(true);
|
expect(element.muted).eql(true);
|
||||||
|
done();
|
||||||
|
} catch (ex) {
|
||||||
|
done(ex);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
// We test this function by itself, as otherwise we'd be into creating fake
|
// We test this function by itself, as otherwise we'd be into creating fake
|
||||||
|
|||||||
1873
browser/extensions/loop/chrome/content/shared/vendor/backbone.js
vendored
Normal file
1873
browser/extensions/loop/chrome/content/shared/vendor/backbone.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
48
browser/extensions/loop/chrome/content/shared/vendor/classnames.js
vendored
Normal file
48
browser/extensions/loop/chrome/content/shared/vendor/classnames.js
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*!
|
||||||
|
Copyright (c) 2015 Jed Watson.
|
||||||
|
Licensed under the MIT License (MIT), see
|
||||||
|
http://jedwatson.github.io/classnames
|
||||||
|
*/
|
||||||
|
/* global define */
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var hasOwn = {}.hasOwnProperty;
|
||||||
|
|
||||||
|
function classNames () {
|
||||||
|
var classes = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < arguments.length; i++) {
|
||||||
|
var arg = arguments[i];
|
||||||
|
if (!arg) continue;
|
||||||
|
|
||||||
|
var argType = typeof arg;
|
||||||
|
|
||||||
|
if (argType === 'string' || argType === 'number') {
|
||||||
|
classes += ' ' + arg;
|
||||||
|
} else if (Array.isArray(arg)) {
|
||||||
|
classes += ' ' + classNames.apply(null, arg);
|
||||||
|
} else if (argType === 'object') {
|
||||||
|
for (var key in arg) {
|
||||||
|
if (hasOwn.call(arg, key) && arg[key]) {
|
||||||
|
classes += ' ' + key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = classNames;
|
||||||
|
} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
|
||||||
|
// register as 'classnames', consistent with npm package name
|
||||||
|
define('classnames', function () {
|
||||||
|
return classNames;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.classNames = classNames;
|
||||||
|
}
|
||||||
|
}());
|
||||||
12235
browser/extensions/loop/chrome/content/shared/vendor/lodash.js
vendored
Normal file
12235
browser/extensions/loop/chrome/content/shared/vendor/lodash.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
browser/extensions/loop/chrome/content/shared/vendor/react-prod.js
vendored
Normal file
18
browser/extensions/loop/chrome/content/shared/vendor/react-prod.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21642
browser/extensions/loop/chrome/content/shared/vendor/react.js
vendored
Normal file
21642
browser/extensions/loop/chrome/content/shared/vendor/react.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
246
browser/extensions/loop/chrome/locale/en-US/loop.properties
Normal file
246
browser/extensions/loop/chrome/locale/en-US/loop.properties
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# Panel Strings
|
||||||
|
|
||||||
|
clientSuperShortname=Hello
|
||||||
|
|
||||||
|
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
|
||||||
|
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
|
||||||
|
## use "..." if \u2026 doesn't suit traditions in your locale.
|
||||||
|
loopMenuItem_label=Start a conversation…
|
||||||
|
loopMenuItem_accesskey=t
|
||||||
|
|
||||||
|
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
|
||||||
|
## These are displayed together at the top of the panel when a user is needed to
|
||||||
|
## sign-in again. The emphesis is on the first line to get the user to sign-in again,
|
||||||
|
## and this is displayed in slightly larger font. Please arrange as necessary for
|
||||||
|
## your locale.
|
||||||
|
## {{clientShortname2}} will be replaced by the brand name for either string.
|
||||||
|
sign_in_again_title_line_one=Please sign in again
|
||||||
|
sign_in_again_title_line_two2=to continue using {{clientShortname2}}
|
||||||
|
sign_in_again_button=Sign In
|
||||||
|
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
|
||||||
|
## will be replaced by the super short brandname.
|
||||||
|
sign_in_again_use_as_guest_button2=Use {{clientSuperShortname}} as a Guest
|
||||||
|
|
||||||
|
panel_browse_with_friend_button=Browse this page with a friend
|
||||||
|
panel_stop_sharing_tabs_button=Stop sharing your tabs
|
||||||
|
|
||||||
|
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
|
||||||
|
## user to create his or her first conversation.
|
||||||
|
first_time_experience_subheading2=Click the Hello button to browse Web pages with a friend.
|
||||||
|
|
||||||
|
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
|
||||||
|
## ways to use Hello project.
|
||||||
|
first_time_experience_content=Use it to plan together, work together, laugh together.
|
||||||
|
first_time_experience_button_label2=See how it works
|
||||||
|
|
||||||
|
invite_header_text_bold=Invite someone to browse this page with you!
|
||||||
|
invite_header_text3=It takes two to use Firefox Hello, so send a friend a link to browse the Web with you!
|
||||||
|
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
|
||||||
|
## invite_email_link_button, invite_facebook_button2): These labels appear under
|
||||||
|
## an iconic button for the invite view.
|
||||||
|
invite_copy_link_button=Copy Link
|
||||||
|
invite_copied_link_button=Copied!
|
||||||
|
invite_email_link_button=Email Link
|
||||||
|
invite_facebook_button3=Facebook
|
||||||
|
invite_your_link=Your link:
|
||||||
|
|
||||||
|
# Status text
|
||||||
|
display_name_guest=Guest
|
||||||
|
|
||||||
|
# Error bars
|
||||||
|
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
|
||||||
|
## These may be displayed at the top of the panel.
|
||||||
|
session_expired_error_description=Session expired. All URLs you have previously created and shared will no longer work.
|
||||||
|
could_not_authenticate=Could Not Authenticate
|
||||||
|
password_changed_question=Did you change your password?
|
||||||
|
try_again_later=Please try again later
|
||||||
|
could_not_connect=Could Not Connect To The Server
|
||||||
|
check_internet_connection=Please check your internet connection
|
||||||
|
login_expired=Your Login Has Expired
|
||||||
|
service_not_available=Service Unavailable At This Time
|
||||||
|
problem_accessing_account=There Was A Problem Accessing Your Account
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
|
||||||
|
## the appropriate action.
|
||||||
|
retry_button=Retry
|
||||||
|
|
||||||
|
share_email_subject7=Your invitation to browse the Web together
|
||||||
|
## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
|
||||||
|
## part between {{..}} and leave the \n\n part alone
|
||||||
|
share_email_body7=A friend is waiting for you on Firefox Hello. Click the link to connect and browse the Web together: {{callUrl}}
|
||||||
|
## LOCALIZATION NOTE (share_email_body_context3): In this item, don't translate
|
||||||
|
## the part between {{..}} and leave the \n\n part alone.
|
||||||
|
share_email_body_context3=A friend is waiting for you on Firefox Hello. Click the link to connect and browse {{title}} together: {{callUrl}}
|
||||||
|
## LOCALIZATION NOTE (share_email_footer2): Common footer content for both email types
|
||||||
|
share_email_footer2=\n\n____________\nFirefox Hello lets you browse the Web with your friends. Use it when you want to get things done: plan together, work together, laugh together. Learn more at http://www.firefox.com/hello
|
||||||
|
## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
|
||||||
|
## between {{..}}. Please keep the text below 117 characters to make sure it fits
|
||||||
|
## in a tweet.
|
||||||
|
share_tweet=Join me for a video conversation on {{clientShortname2}}!
|
||||||
|
|
||||||
|
share_add_service_button=Add a Service
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
|
||||||
|
## These menu items are displayed from a panel's context menu for a conversation.
|
||||||
|
copy_link_menuitem=Copy Link
|
||||||
|
email_link_menuitem=Email Link
|
||||||
|
delete_conversation_menuitem2=Delete
|
||||||
|
|
||||||
|
panel_footer_signin_or_signup_link=Sign In or Sign Up
|
||||||
|
|
||||||
|
settings_menu_item_account=Account
|
||||||
|
settings_menu_item_settings=Settings
|
||||||
|
settings_menu_item_signout=Sign Out
|
||||||
|
settings_menu_item_signin=Sign In
|
||||||
|
settings_menu_item_turnnotificationson=Turn Notifications On
|
||||||
|
settings_menu_item_turnnotificationsoff=Turn Notifications Off
|
||||||
|
settings_menu_item_feedback=Submit Feedback
|
||||||
|
settings_menu_button_tooltip=Settings
|
||||||
|
|
||||||
|
|
||||||
|
# Conversation Window Strings
|
||||||
|
|
||||||
|
initiate_call_button_label2=Ready to start your conversation?
|
||||||
|
incoming_call_title2=Conversation Request
|
||||||
|
incoming_call_block_button=Block
|
||||||
|
hangup_button_title=Hang up
|
||||||
|
hangup_button_caption2=Exit
|
||||||
|
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (call_with_contact_title): The title displayed
|
||||||
|
## when calling a contact. Don't translate the part between {{..}} because
|
||||||
|
## this will be replaced by the contact's name.
|
||||||
|
call_with_contact_title=Conversation with {{contactName}}
|
||||||
|
|
||||||
|
# Outgoing conversation
|
||||||
|
|
||||||
|
outgoing_call_title=Start conversation?
|
||||||
|
initiate_audio_video_call_button2=Start
|
||||||
|
initiate_audio_video_call_tooltip2=Start a video conversation
|
||||||
|
initiate_audio_call_button2=Voice conversation
|
||||||
|
|
||||||
|
peer_ended_conversation2=The person you were calling has ended the conversation.
|
||||||
|
restart_call=Rejoin
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
|
||||||
|
## contact is offline.
|
||||||
|
contact_offline_title=This person is not online
|
||||||
|
## LOCALIZATION NOTE (call_timeout_notification_text): Title which is displayed
|
||||||
|
## when the call didn't go through.
|
||||||
|
call_timeout_notification_text=Your call did not go through.
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (cancel_button):
|
||||||
|
## This button is displayed when a call has failed.
|
||||||
|
cancel_button=Cancel
|
||||||
|
rejoin_button=Rejoin Conversation
|
||||||
|
|
||||||
|
cannot_start_call_session_not_ready=Can't start call, session is not ready.
|
||||||
|
network_disconnected=The network connection terminated abruptly.
|
||||||
|
connection_error_see_console_notification=Call failed; see console for details.
|
||||||
|
no_media_failure_message=No camera or microphone found.
|
||||||
|
ice_failure_message=Connection failed. Your firewall may be blocking calls.
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
|
||||||
|
## parts between {{..}} because these will be replaced with links with the labels
|
||||||
|
## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
|
||||||
|
## by the brand name.
|
||||||
|
legal_text_and_links3=By using {{clientShortname}} you agree to the {{terms_of_use}} and {{privacy_notice}}.
|
||||||
|
legal_text_tos=Terms of Use
|
||||||
|
legal_text_privacy=Privacy Notice
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
|
||||||
|
## These 2 strings are displayed before and after a 'Telefonica'
|
||||||
|
## logo.
|
||||||
|
powered_by_beforeLogo=Powered by
|
||||||
|
powered_by_afterLogo=
|
||||||
|
|
||||||
|
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
|
||||||
|
## a signed-in to signed-in user call.
|
||||||
|
feedback_rejoin_button=Rejoin
|
||||||
|
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
|
||||||
|
## an abusive user.
|
||||||
|
feedback_report_user_button=Report User
|
||||||
|
feedback_window_heading=How was your conversation?
|
||||||
|
feedback_request_button=Leave Feedback
|
||||||
|
|
||||||
|
tour_label=Tour
|
||||||
|
|
||||||
|
rooms_list_recently_browsed2=Recently browsed
|
||||||
|
rooms_list_currently_browsing2=Currently browsing
|
||||||
|
rooms_signout_alert=Open conversations will be closed
|
||||||
|
room_name_untitled_page=Untitled Page
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
|
||||||
|
door_hanger_return=See you later! You can return to this shared session at any time through the Hello panel.
|
||||||
|
door_hanger_prompt_name=Would you like to give it a name that's easier to remember? Current name:
|
||||||
|
door_hanger_button=OK
|
||||||
|
|
||||||
|
# Infobar strings
|
||||||
|
|
||||||
|
infobar_screenshare_browser_message2=You are sharing your tabs. Any tab you click on can be seen by your friends
|
||||||
|
infobar_screenshare_paused_browser_message=Tab sharing is paused
|
||||||
|
infobar_button_gotit_label=Got it!
|
||||||
|
infobar_button_gotit_accesskey=G
|
||||||
|
infobar_button_pause_label=Pause
|
||||||
|
infobar_button_pause_accesskey=P
|
||||||
|
infobar_button_restart_label=Restart
|
||||||
|
infobar_button_restart_accesskey=e
|
||||||
|
infobar_button_resume_label=Resume
|
||||||
|
infobar_button_resume_accesskey=R
|
||||||
|
infobar_button_stop_label=Stop
|
||||||
|
infobar_button_stop_accesskey=S
|
||||||
|
infobar_menuitem_dontshowagain_label=Don't show this again
|
||||||
|
infobar_menuitem_dontshowagain_accesskey=D
|
||||||
|
|
||||||
|
# E10s not supported strings
|
||||||
|
|
||||||
|
e10s_not_supported_button_label=Launch New Window
|
||||||
|
e10s_not_supported_subheading={{brandShortname}} doesn't work in a multi-process window.
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
|
||||||
|
|
||||||
|
# Text chat strings
|
||||||
|
chat_textbox_placeholder=Type here…
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
|
||||||
|
## should remain "Firefox Hello" for all locales.
|
||||||
|
clientShortname2=Firefox Hello
|
||||||
|
|
||||||
|
conversation_has_ended=Your conversation has ended.
|
||||||
|
generic_failure_message=We're having technical difficulties…
|
||||||
|
|
||||||
|
generic_failure_no_reason2=Would you like to try again?
|
||||||
|
|
||||||
|
help_label=Help
|
||||||
|
|
||||||
|
mute_local_audio_button_title=Mute your audio
|
||||||
|
unmute_local_audio_button_title=Unmute your audio
|
||||||
|
mute_local_video_button_title2=Disable video
|
||||||
|
unmute_local_video_button_title2=Enable video
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (retry_call_button):
|
||||||
|
## This button is displayed when a call has failed.
|
||||||
|
retry_call_button=Retry
|
||||||
|
|
||||||
|
rooms_leave_button_label=Leave
|
||||||
|
|
||||||
|
rooms_panel_title=Choose a conversation or start a new one
|
||||||
|
|
||||||
|
rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
|
||||||
|
rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start your own
|
||||||
|
rooms_room_full_label=There are already two people in this conversation.
|
||||||
|
rooms_room_join_label=Join the conversation
|
||||||
|
rooms_room_joined_label=Someone has joined the conversation!
|
||||||
|
|
||||||
|
self_view_hidden_message=Self-view hidden but still being sent; resize window to show
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
|
||||||
|
## as this will be replaced by clientShortname2.
|
||||||
|
tos_failure_message={{clientShortname}} is not available in your country.
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../.eslintrc-gecko",
|
|
||||||
"globals": {
|
"globals": {
|
||||||
// General test items.
|
// General test items.
|
||||||
"add_task": false,
|
"add_task": false,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ support-files =
|
|||||||
head.js
|
head.js
|
||||||
loop_fxa.sjs
|
loop_fxa.sjs
|
||||||
test_loopLinkClicker_channel.html
|
test_loopLinkClicker_channel.html
|
||||||
../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
|
../../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
|
||||||
|
|
||||||
[browser_fxa_login.js]
|
[browser_fxa_login.js]
|
||||||
[browser_loop_fxa_server.js]
|
[browser_loop_fxa_server.js]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ var { WebChannel } = Cu.import("resource://gre/modules/WebChannel.jsm", {});
|
|||||||
var { Chat } = Cu.import("resource:///modules/Chat.jsm", {});
|
var { Chat } = Cu.import("resource:///modules/Chat.jsm", {});
|
||||||
|
|
||||||
const TEST_URI =
|
const TEST_URI =
|
||||||
"example.com/browser/browser/extensions/loop/test/mochitest/test_loopLinkClicker_channel.html";
|
"example.com/browser/browser/extensions/loop/chrome/test/mochitest/test_loopLinkClicker_channel.html";
|
||||||
const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URI, null, null);
|
const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URI, null, null);
|
||||||
const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URI, null, null);
|
const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URI, null, null);
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ var gBadBackChannel;
|
|||||||
// Loads the specified URI in a new tab and waits for it to send us data on our
|
// Loads the specified URI in a new tab and waits for it to send us data on our
|
||||||
// test web-channel and resolves with that data.
|
// test web-channel and resolves with that data.
|
||||||
function promiseNewChannelResponse(uri, channel, hash) {
|
function promiseNewChannelResponse(uri, channel, hash) {
|
||||||
let waitForChannelPromise = new Promise((resolve, reject) => {
|
let waitForChannelPromise = new Promise((resolve) => {
|
||||||
if (channel.receivedData) {
|
if (channel.receivedData) {
|
||||||
let data = channel.receivedData;
|
let data = channel.receivedData;
|
||||||
channel.receivedData = null;
|
channel.receivedData = null;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ add_task(function* setup() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
registerCleanupFunction(function* () {
|
registerCleanupFunction(function* () {
|
||||||
info("cleanup time");
|
info("cleanup specific to setup test");
|
||||||
yield promiseDeletedOAuthParams(BASE_URL);
|
yield promiseDeletedOAuthParams(BASE_URL);
|
||||||
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
|
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
|
||||||
MozLoopServiceInternal.mocks.pushHandler = undefined;
|
MozLoopServiceInternal.mocks.pushHandler = undefined;
|
||||||
@@ -218,7 +218,7 @@ add_task(function* registrationWithInvalidState() {
|
|||||||
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
|
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
|
||||||
|
|
||||||
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
|
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
|
||||||
yield tokenPromise.then(body => {
|
yield tokenPromise.then(() => {
|
||||||
ok(false, "Promise should have rejected");
|
ok(false, "Promise should have rejected");
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@@ -241,7 +241,7 @@ add_task(function* registrationWith401() {
|
|||||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||||
|
|
||||||
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
|
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
|
||||||
yield tokenPromise.then(body => {
|
yield tokenPromise.then(() => {
|
||||||
ok(false, "Promise should have rejected");
|
ok(false, "Promise should have rejected");
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@@ -357,7 +357,7 @@ add_task(function* loginWithParams401() {
|
|||||||
yield MozLoopService.promiseRegisteredWithServers();
|
yield MozLoopService.promiseRegisteredWithServers();
|
||||||
|
|
||||||
let loginPromise = MozLoopService.logInToFxA();
|
let loginPromise = MozLoopService.logInToFxA();
|
||||||
yield loginPromise.then(tokenData => {
|
yield loginPromise.then(() => {
|
||||||
ok(false, "Promise should have rejected");
|
ok(false, "Promise should have rejected");
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@@ -379,7 +379,7 @@ add_task(function* logoutWithIncorrectPushURL() {
|
|||||||
is(registrationResponse.response.simplePushURLs.rooms, pushURL, "Check registered push URL");
|
is(registrationResponse.response.simplePushURLs.rooms, pushURL, "Check registered push URL");
|
||||||
MozLoopServiceInternal.pushURLs.get(LOOP_SESSION_TYPE.FXA).rooms = "http://www.example.com/invalid";
|
MozLoopServiceInternal.pushURLs.get(LOOP_SESSION_TYPE.FXA).rooms = "http://www.example.com/invalid";
|
||||||
let caught = false;
|
let caught = false;
|
||||||
yield MozLoopService.logOutFromFxA().catch((error) => {
|
yield MozLoopService.logOutFromFxA().catch(() => {
|
||||||
caught = true;
|
caught = true;
|
||||||
});
|
});
|
||||||
ok(caught, "Should have caught an error logging out with a mismatched push URL");
|
ok(caught, "Should have caught an error logging out with a mismatched push URL");
|
||||||
@@ -418,7 +418,7 @@ add_task(function* loginWithRegistration401() {
|
|||||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||||
|
|
||||||
let loginPromise = MozLoopService.logInToFxA();
|
let loginPromise = MozLoopService.logInToFxA();
|
||||||
yield loginPromise.then(tokenData => {
|
yield loginPromise.then(() => {
|
||||||
ok(false, "Promise should have rejected");
|
ok(false, "Promise should have rejected");
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@@ -437,6 +437,17 @@ add_task(function* openFxASettings() {
|
|||||||
// blank tab.
|
// blank tab.
|
||||||
gBrowser.selectedTab = gBrowser.addTab(BASE_URL);
|
gBrowser.selectedTab = gBrowser.addTab(BASE_URL);
|
||||||
|
|
||||||
|
let fxASampleToken = {
|
||||||
|
token_type: "bearer",
|
||||||
|
access_token: "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
|
||||||
|
scope: "profile"
|
||||||
|
};
|
||||||
|
|
||||||
|
let fxASampleProfile = {
|
||||||
|
email: "test@example.com",
|
||||||
|
uid: "abcd1234"
|
||||||
|
};
|
||||||
|
|
||||||
let params = {
|
let params = {
|
||||||
client_id: "client_id",
|
client_id: "client_id",
|
||||||
content_uri: BASE_URL + "/content",
|
content_uri: BASE_URL + "/content",
|
||||||
@@ -445,9 +456,20 @@ add_task(function* openFxASettings() {
|
|||||||
state: "state",
|
state: "state",
|
||||||
test_error: "token_401"
|
test_error: "token_401"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Services.prefs.setCharPref("loop.fxa_oauth.profile", JSON.stringify(fxASampleProfile));
|
||||||
|
Services.prefs.setCharPref("loop.fxa_oauth.tokendata", JSON.stringify(fxASampleToken));
|
||||||
|
|
||||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||||
|
|
||||||
yield new Promise((resolve, reject) => {
|
registerCleanupFunction(function* () {
|
||||||
|
info("cleanup specific to openFxASettings test");
|
||||||
|
yield promiseDeletedOAuthParams(BASE_URL);
|
||||||
|
Services.prefs.clearUserPref("loop.fxa_oauth.profile");
|
||||||
|
Services.prefs.clearUserPref("loop.fxa_oauth.tokendata");
|
||||||
|
});
|
||||||
|
|
||||||
|
yield new Promise((resolve) => {
|
||||||
let progressListener = {
|
let progressListener = {
|
||||||
onLocationChange: function onLocationChange(aBrowser) {
|
onLocationChange: function onLocationChange(aBrowser) {
|
||||||
if (aBrowser.currentURI.spec == BASE_URL) {
|
if (aBrowser.currentURI.spec == BASE_URL) {
|
||||||
@@ -456,7 +478,7 @@ add_task(function* openFxASettings() {
|
|||||||
}
|
}
|
||||||
gBrowser.removeTabsProgressListener(progressListener);
|
gBrowser.removeTabsProgressListener(progressListener);
|
||||||
let contentURI = Services.io.newURI(params.content_uri, null, null);
|
let contentURI = Services.io.newURI(params.content_uri, null, null);
|
||||||
is(aBrowser.currentURI.spec, Services.io.newURI("/settings", null, contentURI).spec,
|
is(aBrowser.currentURI.spec, Services.io.newURI("/settings?uid=abcd1234", null, contentURI).spec,
|
||||||
"Check settings tab URL");
|
"Check settings tab URL");
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/loop/test/mochitest/loop_fxa.sjs?";
|
const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/loop/chrome/test/mochitest/loop_fxa.sjs?";
|
||||||
|
|
||||||
registerCleanupFunction(function* () {
|
registerCleanupFunction(function* () {
|
||||||
yield promiseDeletedOAuthParams(BASE_URL);
|
yield promiseDeletedOAuthParams(BASE_URL);
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ add_task(function* test_multipleListener() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_infoBar() {
|
add_task(function* test_infoBar() {
|
||||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|
||||||
const kBrowserSharingNotificationId = "loop-sharing-notification";
|
const kBrowserSharingNotificationId = "loop-sharing-notification";
|
||||||
const kPrefBrowserSharingInfoBar = "loop.browserSharing.showInfoBar";
|
const kPrefBrowserSharingInfoBar = "loop.browserSharing.showInfoBar";
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
||||||
const { LoopRoomsInternal } = Components.utils.import("chrome://loop/content/modules/LoopRooms.jsm", {});
|
|
||||||
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 1);
|
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 1);
|
||||||
|
|
||||||
const fxASampleToken = {
|
const fxASampleToken = {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported HAWK_TOKEN_LENGTH, LoopRooms, promiseWaitForCondition,
|
||||||
|
loadLoopPanel, promiseOAuthParamsSetup, resetFxA, checkLoggedOutState,
|
||||||
|
promiseDeletedOAuthParams, promiseOAuthGetRegistration,
|
||||||
|
getLoopString, mockPushHandler, channelID, mockDb, LoopAPI */
|
||||||
|
|
||||||
const HAWK_TOKEN_LENGTH = 64;
|
const HAWK_TOKEN_LENGTH = 64;
|
||||||
const {
|
const {
|
||||||
LOOP_SESSION_TYPE,
|
LOOP_SESSION_TYPE,
|
||||||
@@ -18,7 +23,7 @@ const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {
|
|||||||
const WAS_OFFLINE = Services.io.offline;
|
const WAS_OFFLINE = Services.io.offline;
|
||||||
|
|
||||||
function promisePanelLoaded() {
|
function promisePanelLoaded() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
let loopPanel = document.getElementById("loop-notification-panel");
|
let loopPanel = document.getElementById("loop-notification-panel");
|
||||||
let btn = document.getElementById("loop-button");
|
let btn = document.getElementById("loop-button");
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ function promisePanelLoaded() {
|
|||||||
iframe.contentDocument.readyState == "complete") {
|
iframe.contentDocument.readyState == "complete") {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
iframe.addEventListener("load", function panelOnLoad(e) {
|
iframe.addEventListener("load", function panelOnLoad() {
|
||||||
iframe.removeEventListener("load", panelOnLoad, true);
|
iframe.removeEventListener("load", panelOnLoad, true);
|
||||||
// We do this in an execute soon to allow any other event listeners to
|
// We do this in an execute soon to allow any other event listeners to
|
||||||
// be handled, just in case.
|
// be handled, just in case.
|
||||||
@@ -89,7 +94,7 @@ function waitForCondition(condition, nextTest, errorMsg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function promiseWaitForCondition(aConditionFn) {
|
function promiseWaitForCondition(aConditionFn) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
waitForCondition(aConditionFn, resolve, "Condition didn't pass.");
|
waitForCondition(aConditionFn, resolve, "Condition didn't pass.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -175,7 +180,7 @@ function promiseDeletedOAuthParams(baseURL) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function promiseObserverNotified(aTopic, aExpectedData = null) {
|
function promiseObserverNotified(aTopic, aExpectedData = null) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
Services.obs.addObserver(function onNotification(aSubject, topic, aData) {
|
Services.obs.addObserver(function onNotification(aSubject, topic, aData) {
|
||||||
Services.obs.removeObserver(onNotification, topic);
|
Services.obs.removeObserver(onNotification, topic);
|
||||||
is(aData, aExpectedData, "observer data should match expected data");
|
is(aData, aExpectedData, "observer data should match expected data");
|
||||||
@@ -235,7 +240,7 @@ var mockPushHandler = {
|
|||||||
setTimeout(registerCallback(this.registrationResult, this.registeredChannels[channelId], channelId), 0);
|
setTimeout(registerCallback(this.registrationResult, this.registeredChannels[channelId], channelId), 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
unregister: function(channelID) {
|
unregister: function() {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../.eslintrc-gecko",
|
|
||||||
"globals": {
|
"globals": {
|
||||||
// General xpcshell-test functions
|
// General xpcshell-test functions
|
||||||
"HttpServer": false,
|
"HttpServer": false,
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported Cr, LoopRoomsInternal, timerHandlers, kMockWebSocketChannelName,
|
||||||
|
kWebSocketChannelContractID, kServerPushUrl, kLoopServerUrl,
|
||||||
|
setupFakeLoopServer, setupFakeFxAUserProfile, waitForCondition,
|
||||||
|
getLoopString, extend */
|
||||||
|
|
||||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||||
|
|
||||||
// Initialize this before the imports, as some of them need it.
|
// Initialize this before the imports, as some of them need it.
|
||||||
@@ -159,7 +164,7 @@ var mockPushHandler = {
|
|||||||
registerCallback(this.registrationResult, this.registrationPushURL, channelId);
|
registerCallback(this.registrationResult, this.registrationPushURL, channelId);
|
||||||
},
|
},
|
||||||
|
|
||||||
unregister: function(channelID) {
|
unregister: function() {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -183,7 +188,7 @@ MockWebSocketChannel.prototype = {
|
|||||||
|
|
||||||
initRegStatus: 0,
|
initRegStatus: 0,
|
||||||
|
|
||||||
defaultMsgHandler: function(msg) {
|
defaultMsgHandler: function() {
|
||||||
// Treat as a ping
|
// Treat as a ping
|
||||||
this.listener.onMessageAvailable(this.context,
|
this.listener.onMessageAvailable(this.context,
|
||||||
JSON.stringify({}));
|
JSON.stringify({}));
|
||||||
@@ -231,7 +236,7 @@ MockWebSocketChannel.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function(aCode, aReason) {
|
close: function(aCode) {
|
||||||
this.stop(aCode);
|
this.stop(aCode);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
|
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
|
||||||
const [LoopAPIInternal] = LoopAPI.inspect();
|
const [LoopAPIInternal] = LoopAPI.inspect();
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
var dummyCallback = () => {};
|
var dummyCallback = () => {};
|
||||||
var mockWebSocket = new MockWebSocketChannel();
|
var mockWebSocket = new MockWebSocketChannel();
|
||||||
var pushServerRequestCount = 0;
|
var pushServerRequestCount = 0;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
|
Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
|
||||||
Cu.import("resource:///modules/Chat.jsm");
|
Cu.import("resource:///modules/Chat.jsm");
|
||||||
@@ -169,12 +171,6 @@ const kCreateRoomProps = {
|
|||||||
maxSize: 2
|
maxSize: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
const kCreateRoomUnencryptedProps = {
|
|
||||||
roomName: "UX Discussion",
|
|
||||||
roomOwner: "Alexis",
|
|
||||||
maxSize: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const kCreateRoomData = {
|
const kCreateRoomData = {
|
||||||
roomToken: "_nxD4V4FflQ",
|
roomToken: "_nxD4V4FflQ",
|
||||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||||
@@ -261,7 +257,7 @@ const onRoomLeft = function(e, room, participant) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRefresh = function(e) {
|
const onRefresh = function() {
|
||||||
Assert.ok(gExpectedRefresh, "A refresh event should've been expected");
|
Assert.ok(gExpectedRefresh, "A refresh event should've been expected");
|
||||||
gExpectedRefresh = false;
|
gExpectedRefresh = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
timerHandlers.startTimer = callback => callback();
|
timerHandlers.startTimer = callback => callback();
|
||||||
|
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
@@ -184,7 +186,7 @@ add_task(function* setup_server() {
|
|||||||
|
|
||||||
// Test if getting rooms saves unknown keys correctly.
|
// Test if getting rooms saves unknown keys correctly.
|
||||||
add_task(function* test_get_rooms_saves_unknown_keys() {
|
add_task(function* test_get_rooms_saves_unknown_keys() {
|
||||||
let rooms = yield LoopRooms.promise("getAll");
|
yield LoopRooms.promise("getAll");
|
||||||
|
|
||||||
// Check that we've saved the encryption keys correctly.
|
// Check that we've saved the encryption keys correctly.
|
||||||
let roomsCache = yield readRoomsCache();
|
let roomsCache = yield readRoomsCache();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
|
Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
|
||||||
Cu.import("resource:///modules/Chat.jsm");
|
Cu.import("resource:///modules/Chat.jsm");
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
Cu.import("resource:///modules/Chat.jsm");
|
Cu.import("resource:///modules/Chat.jsm");
|
||||||
Cu.import("resource://gre/modules/Promise.jsm");
|
Cu.import("resource://gre/modules/Promise.jsm");
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
|
|
||||||
const loopCrypto = Cu.import("chrome://loop/content/shared/js/crypto.js", {}).LoopCrypto;
|
const loopCrypto = Cu.import("chrome://loop/content/shared/js/crypto.js", {}).LoopCrypto;
|
||||||
const { LOOP_ROOMS_CACHE_FILENAME } = Cu.import("chrome://loop/content/modules/LoopRoomsCache.jsm", {});
|
|
||||||
|
|
||||||
var gTimerArgs = [];
|
var gTimerArgs = [];
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
|
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
|
||||||
"resource:///modules/Chat.jsm");
|
"resource:///modules/Chat.jsm");
|
||||||
var openChatOrig = Chat.open;
|
var openChatOrig = Chat.open;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
const { INVALID_AUTH_TOKEN } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm");
|
const { INVALID_AUTH_TOKEN } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +63,7 @@ add_task(function* guest_401() {
|
|||||||
Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
|
Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
|
||||||
yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/401", "POST").then(
|
yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/401", "POST").then(
|
||||||
() => Assert.ok(false, "Should have rejected"),
|
() => Assert.ok(false, "Should have rejected"),
|
||||||
(error) => {
|
() => {
|
||||||
Assert.strictEqual(Services.prefs.getPrefType("loop.hawk-session-token"),
|
Assert.strictEqual(Services.prefs.getPrefType("loop.hawk-session-token"),
|
||||||
Services.prefs.PREF_INVALID,
|
Services.prefs.PREF_INVALID,
|
||||||
"Guest session token should have been cleared");
|
"Guest session token should have been cleared");
|
||||||
@@ -85,7 +87,7 @@ add_task(function* fxa_401() {
|
|||||||
Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
|
Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
|
||||||
yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.FXA, "/401", "POST").then(
|
yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.FXA, "/401", "POST").then(
|
||||||
() => Assert.ok(false, "Should have rejected"),
|
() => Assert.ok(false, "Should have rejected"),
|
||||||
(error) => {
|
() => {
|
||||||
Assert.strictEqual(Services.prefs.getCharPref("loop.hawk-session-token"),
|
Assert.strictEqual(Services.prefs.getCharPref("loop.hawk-session-token"),
|
||||||
"guest",
|
"guest",
|
||||||
"Guest session token should NOT have been cleared");
|
"Guest session token should NOT have been cleared");
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
|
|
||||||
add_task(function* request_with_unicode() {
|
add_task(function* request_with_unicode() {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
var startTimerCalled = false;
|
var startTimerCalled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
function test_locale() {
|
function test_locale() {
|
||||||
// Set the pref to something controlled.
|
// Set the pref to something controlled.
|
||||||
Services.prefs.setCharPref("general.useragent.locale", "ab-CD");
|
Services.prefs.setCharPref("general.useragent.locale", "ab-CD");
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
var fakeCharPrefName = "color";
|
var fakeCharPrefName = "color";
|
||||||
var fakeBoolPrefName = "boolean";
|
var fakeBoolPrefName = "boolean";
|
||||||
var fakePrefValue = "green";
|
var fakePrefValue = "green";
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Task.jsm");
|
Cu.import("resource://gre/modules/Task.jsm");
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
const FAKE_FXA_TOKEN_DATA = JSON.stringify({
|
const FAKE_FXA_TOKEN_DATA = JSON.stringify({
|
||||||
"token_type": "bearer",
|
"token_type": "bearer",
|
||||||
"access_token": "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
|
"access_token": "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
const LOOP_HAWK_PREF = "loop.hawk-session-token";
|
const LOOP_HAWK_PREF = "loop.hawk-session-token";
|
||||||
const fakeSessionToken1 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1751";
|
const fakeSessionToken1 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1751";
|
||||||
const fakeSessionToken2 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750";
|
const fakeSessionToken2 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750";
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that things behave reasonably when a reasonable Hawk-Session-Token
|
* Test that things behave reasonably when a reasonable Hawk-Session-Token
|
||||||
* header is returned with the registration response.
|
* header is returned with the registration response.
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
add_test(function test_registration_uses_hawk_session_token() {
|
add_test(function test_registration_uses_hawk_session_token() {
|
||||||
Services.prefs.setCharPref("loop.hawk-session-token",
|
Services.prefs.setCharPref("loop.hawk-session-token",
|
||||||
"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750");
|
"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750");
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* exported run_test */
|
||||||
|
|
||||||
// XXX should report error if Hawk-Session-Token is lexically invalid
|
// XXX should report error if Hawk-Session-Token is lexically invalid
|
||||||
// (not a string of 64 hex digits) to help resist other possible injection
|
// (not a string of 64 hex digits) to help resist other possible injection
|
||||||
// attacks. For now, however, we're just checking if it's the right length.
|
// attacks. For now, however, we're just checking if it's the right length.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</em:targetApplication>
|
</em:targetApplication>
|
||||||
|
|
||||||
<!-- Front End MetaData -->
|
<!-- Front End MetaData -->
|
||||||
<em:name>Firefox Hello</em:name>
|
<em:name>Firefox Hello Beta</em:name>
|
||||||
<em:description>Web sharing for Firefox</em:description>
|
<em:description>Web sharing for Firefox</em:description>
|
||||||
<em:creator>Mozilla</em:creator>
|
<em:creator>Mozilla</em:creator>
|
||||||
</Description>
|
</Description>
|
||||||
|
|||||||
@@ -26,139 +26,20 @@
|
|||||||
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-aero@2x.png os=WINNT osversion=6.1
|
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-aero@2x.png os=WINNT osversion=6.1
|
||||||
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.2
|
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.2
|
||||||
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.3
|
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.3
|
||||||
skin/ (skin/*)
|
skin/ (chrome/skin/*)
|
||||||
content/modules/ (content/modules/*)
|
content/modules/ (chrome/content/modules/*)
|
||||||
* content/preferences/prefs.js (content/preferences/prefs.js)
|
# We don't package the test/ directory for panels, so do these separately.
|
||||||
|
content/panels/ (chrome/content/panels/*.html)
|
||||||
# Desktop html files
|
content/panels/css/ (chrome/content/panels/css/*)
|
||||||
content/panels/conversation.html (content/panels/conversation.html)
|
content/panels/js/ (chrome/content/panels/js/*)
|
||||||
content/panels/panel.html (content/panels/panel.html)
|
content/panels/vendor/ (chrome/content/panels/vendor/*)
|
||||||
|
* content/preferences/prefs.js (chrome/content/preferences/prefs.js)
|
||||||
# Desktop vendor (see bottom of this file for TokBox sdk assets)
|
# We don't package the test/ directory for shared, so do these separately.
|
||||||
content/panels/vendor/l10n.js (content/panels/vendor/l10n.js)
|
content/shared/css/ (chrome/content/shared/css/*)
|
||||||
|
content/shared/img/ (chrome/content/shared/img/*)
|
||||||
# Desktop script
|
content/shared/js/ (chrome/content/shared/js/*)
|
||||||
content/panels/js/conversation.js (content/panels/js/conversation.js)
|
content/shared/sounds/ (chrome/content/shared/sounds/*)
|
||||||
content/panels/js/conversationAppStore.js (content/panels/js/conversationAppStore.js)
|
content/shared/vendor/ (chrome/content/shared/vendor/*)
|
||||||
content/panels/js/otconfig.js (content/panels/js/otconfig.js)
|
#ifndef DEBUG
|
||||||
content/panels/js/panel.js (content/panels/js/panel.js)
|
+ content/shared/vendor/react.js (chrome/content/shared/vendor/react-prod.js)
|
||||||
content/panels/js/roomStore.js (content/panels/js/roomStore.js)
|
|
||||||
content/panels/js/roomViews.js (content/panels/js/roomViews.js)
|
|
||||||
content/panels/js/feedbackViews.js (content/panels/js/feedbackViews.js)
|
|
||||||
|
|
||||||
# Desktop styles
|
|
||||||
content/panels/css/panel.css (content/panels/css/panel.css)
|
|
||||||
|
|
||||||
# Shared styles
|
|
||||||
content/shared/css/reset.css (content/shared/css/reset.css)
|
|
||||||
content/shared/css/common.css (content/shared/css/common.css)
|
|
||||||
content/shared/css/conversation.css (content/shared/css/conversation.css)
|
|
||||||
|
|
||||||
# Shared images
|
|
||||||
content/shared/img/helloicon.svg (content/shared/img/helloicon.svg)
|
|
||||||
content/shared/img/icon_32.png (content/shared/img/icon_32.png)
|
|
||||||
content/shared/img/icon_64.png (content/shared/img/icon_64.png)
|
|
||||||
content/shared/img/spinner.svg (content/shared/img/spinner.svg)
|
|
||||||
# XXX could get rid of the png spinner usages and replace them with the svg
|
|
||||||
# one?
|
|
||||||
content/shared/img/spinner.png (content/shared/img/spinner.png)
|
|
||||||
content/shared/img/spinner@2x.png (content/shared/img/spinner@2x.png)
|
|
||||||
content/shared/img/sad_hello_icon_64x64.svg (content/shared/img/sad_hello_icon_64x64.svg)
|
|
||||||
content/shared/img/chatbubble-arrow-left.svg (content/shared/img/chatbubble-arrow-left.svg)
|
|
||||||
content/shared/img/chatbubble-arrow-right.svg (content/shared/img/chatbubble-arrow-right.svg)
|
|
||||||
content/shared/img/facemute-14x14.png (content/shared/img/facemute-14x14.png)
|
|
||||||
content/shared/img/facemute-14x14@2x.png (content/shared/img/facemute-14x14@2x.png)
|
|
||||||
content/shared/img/hangup-inverse-14x14.png (content/shared/img/hangup-inverse-14x14.png)
|
|
||||||
content/shared/img/hangup-inverse-14x14@2x.png (content/shared/img/hangup-inverse-14x14@2x.png)
|
|
||||||
content/shared/img/mute-inverse-14x14.png (content/shared/img/mute-inverse-14x14.png)
|
|
||||||
content/shared/img/mute-inverse-14x14@2x.png (content/shared/img/mute-inverse-14x14@2x.png)
|
|
||||||
content/shared/img/glyph-email-16x16.svg (content/shared/img/glyph-email-16x16.svg)
|
|
||||||
content/shared/img/glyph-facebook-16x16.svg (content/shared/img/glyph-facebook-16x16.svg)
|
|
||||||
content/shared/img/glyph-help-16x16.svg (content/shared/img/glyph-help-16x16.svg)
|
|
||||||
content/shared/img/glyph-link-16x16.svg (content/shared/img/glyph-link-16x16.svg)
|
|
||||||
content/shared/img/glyph-user-16x16.svg (content/shared/img/glyph-user-16x16.svg)
|
|
||||||
content/shared/img/exit.svg (content/shared/img/exit.svg)
|
|
||||||
content/shared/img/audio.svg (content/shared/img/audio.svg)
|
|
||||||
content/shared/img/audio-hover.svg (content/shared/img/audio-hover.svg)
|
|
||||||
content/shared/img/audio-mute.svg (content/shared/img/audio-mute.svg)
|
|
||||||
content/shared/img/audio-mute-hover.svg (content/shared/img/audio-mute-hover.svg)
|
|
||||||
content/shared/img/video.svg (content/shared/img/video.svg)
|
|
||||||
content/shared/img/video-hover.svg (content/shared/img/video-hover.svg)
|
|
||||||
content/shared/img/video-mute.svg (content/shared/img/video-mute.svg)
|
|
||||||
content/shared/img/video-mute-hover.svg (content/shared/img/video-mute-hover.svg)
|
|
||||||
content/shared/img/settings.svg (content/shared/img/settings.svg)
|
|
||||||
content/shared/img/settings-hover.svg (content/shared/img/settings-hover.svg)
|
|
||||||
content/shared/img/sharing.svg (content/shared/img/sharing.svg)
|
|
||||||
content/shared/img/sharing-active.svg (content/shared/img/sharing-active.svg)
|
|
||||||
content/shared/img/sharing-pending.svg (content/shared/img/sharing-pending.svg)
|
|
||||||
content/shared/img/sharing-hover.svg (content/shared/img/sharing-hover.svg)
|
|
||||||
content/shared/img/media-group.svg (content/shared/img/media-group.svg)
|
|
||||||
content/shared/img/media-group-left-hover.svg (content/shared/img/media-group-left-hover.svg)
|
|
||||||
content/shared/img/media-group-right-hover.svg (content/shared/img/media-group-right-hover.svg)
|
|
||||||
content/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
|
|
||||||
content/shared/img/beta-ribbon.svg (content/shared/img/beta-ribbon.svg)
|
|
||||||
content/shared/img/check.svg (content/shared/img/check.svg)
|
|
||||||
content/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg)
|
|
||||||
content/shared/img/icons-14x14.svg (content/shared/img/icons-14x14.svg)
|
|
||||||
content/shared/img/icons-16x16.svg (content/shared/img/icons-16x16.svg)
|
|
||||||
content/shared/img/movistar.png (content/shared/img/movistar.png)
|
|
||||||
content/shared/img/movistar@2x.png (content/shared/img/movistar@2x.png)
|
|
||||||
content/shared/img/vivo.png (content/shared/img/vivo.png)
|
|
||||||
content/shared/img/vivo@2x.png (content/shared/img/vivo@2x.png)
|
|
||||||
content/shared/img/02.png (content/shared/img/02.png)
|
|
||||||
content/shared/img/02@2x.png (content/shared/img/02@2x.png)
|
|
||||||
content/shared/img/telefonica-logo.svg (content/shared/img/telefonica-logo.svg)
|
|
||||||
content/shared/img/hello_logo.svg (content/shared/img/hello_logo.svg)
|
|
||||||
content/shared/img/hello-web-share.svg (content/shared/img/hello-web-share.svg)
|
|
||||||
content/shared/img/ellipsis-v.svg (content/shared/img/ellipsis-v.svg)
|
|
||||||
content/shared/img/empty_conversations.svg (content/shared/img/empty_conversations.svg)
|
|
||||||
content/shared/img/empty_search.svg (content/shared/img/empty_search.svg)
|
|
||||||
content/shared/img/animated-spinner.svg (content/shared/img/animated-spinner.svg)
|
|
||||||
content/shared/img/avatars.svg (content/shared/img/avatars.svg)
|
|
||||||
content/shared/img/firefox-avatar.svg (content/shared/img/firefox-avatar.svg)
|
|
||||||
content/shared/img/pause-12x12.svg (content/shared/img/pause-12x12.svg)
|
|
||||||
content/shared/img/play-12x12.svg (content/shared/img/play-12x12.svg)
|
|
||||||
content/shared/img/stop-12x12.svg (content/shared/img/stop-12x12.svg)
|
|
||||||
|
|
||||||
# Shared scripts
|
|
||||||
content/shared/js/actions.js (content/shared/js/actions.js)
|
|
||||||
content/shared/js/crypto.js (content/shared/js/crypto.js)
|
|
||||||
content/shared/js/store.js (content/shared/js/store.js)
|
|
||||||
content/shared/js/activeRoomStore.js (content/shared/js/activeRoomStore.js)
|
|
||||||
content/shared/js/dispatcher.js (content/shared/js/dispatcher.js)
|
|
||||||
content/shared/js/linkifiedTextView.js (content/shared/js/linkifiedTextView.js)
|
|
||||||
content/shared/js/loopapi-client.js (content/shared/js/loopapi-client.js)
|
|
||||||
content/shared/js/models.js (content/shared/js/models.js)
|
|
||||||
content/shared/js/mixins.js (content/shared/js/mixins.js)
|
|
||||||
content/shared/js/otSdkDriver.js (content/shared/js/otSdkDriver.js)
|
|
||||||
content/shared/js/views.js (content/shared/js/views.js)
|
|
||||||
content/shared/js/textChatStore.js (content/shared/js/textChatStore.js)
|
|
||||||
content/shared/js/textChatView.js (content/shared/js/textChatView.js)
|
|
||||||
content/shared/js/urlRegExps.js (content/shared/js/urlRegExps.js)
|
|
||||||
content/shared/js/utils.js (content/shared/js/utils.js)
|
|
||||||
content/shared/js/validate.js (content/shared/js/validate.js)
|
|
||||||
|
|
||||||
# Shared libs
|
|
||||||
#ifdef DEBUG
|
|
||||||
content/shared/vendor/react-0.13.3.js (content/shared/vendor/react-0.13.3.js)
|
|
||||||
#else
|
|
||||||
content/shared/vendor/react-0.13.3.js (content/shared/vendor/react-0.13.3-prod.js)
|
|
||||||
#endif
|
#endif
|
||||||
content/shared/vendor/lodash-3.9.3.js (content/shared/vendor/lodash-3.9.3.js)
|
|
||||||
content/shared/vendor/backbone-1.2.1.js (content/shared/vendor/backbone-1.2.1.js)
|
|
||||||
content/shared/vendor/classnames-2.2.0.js (content/shared/vendor/classnames-2.2.0.js)
|
|
||||||
|
|
||||||
# Shared sounds
|
|
||||||
content/shared/sounds/ringtone.ogg (content/shared/sounds/ringtone.ogg)
|
|
||||||
content/shared/sounds/connecting.ogg (content/shared/sounds/connecting.ogg)
|
|
||||||
content/shared/sounds/connected.ogg (content/shared/sounds/connected.ogg)
|
|
||||||
content/shared/sounds/terminated.ogg (content/shared/sounds/terminated.ogg)
|
|
||||||
content/shared/sounds/room-joined.ogg (content/shared/sounds/room-joined.ogg)
|
|
||||||
content/shared/sounds/room-joined-in.ogg (content/shared/sounds/room-joined-in.ogg)
|
|
||||||
content/shared/sounds/room-left.ogg (content/shared/sounds/room-left.ogg)
|
|
||||||
content/shared/sounds/failure.ogg (content/shared/sounds/failure.ogg)
|
|
||||||
content/shared/sounds/message.ogg (content/shared/sounds/message.ogg)
|
|
||||||
|
|
||||||
# Partner SDK assets
|
|
||||||
content/shared/vendor/sdk.js (content/shared/vendor/sdk.js)
|
|
||||||
content/sdk-content/js/dynamic_config.min.js (content/shared/vendor/sdk-content/js/dynamic_config.min.js)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user