Bug 1065201: introduce new sounds for Hello standalone and desktop. r=mikedeboer

This commit is contained in:
Romain Gauthier 2014-11-06 14:51:50 +01:00
Родитель 61eeb953f5
Коммит 002e180974
18 изменённых файлов: 317 добавлений и 60 удалений

Просмотреть файл

@ -1650,7 +1650,7 @@ pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
pref("loop.legal.ToS_url", "https://hello.firefox.com/legal/terms/");
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/");
pref("loop.do_not_disturb", false);
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/Firefox-Long.ogg");
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
pref("loop.retry_delay.start", 60000);
pref("loop.retry_delay.limit", 300000);
pref("loop.feedback.baseUrl", "https://input.mozilla.org/api/v1/feedback");
@ -1660,9 +1660,9 @@ pref("loop.debug.dispatcher", false);
pref("loop.debug.websocket", false);
pref("loop.debug.sdk", false);
#ifdef DEBUG
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*");
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
#else
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net");
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
#endif
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");

Просмотреть файл

@ -13,6 +13,7 @@ Cu.import("resource:///modules/loop/LoopCalls.jsm");
Cu.import("resource:///modules/loop/MozLoopService.jsm");
Cu.import("resource:///modules/loop/LoopRooms.jsm");
Cu.import("resource:///modules/loop/LoopContacts.jsm");
Cu.importGlobalProperties(["Blob"]);
XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
"resource:///modules/loop/LoopContacts.jsm");
@ -685,6 +686,31 @@ function injectLoopAPI(targetWindow) {
return MozLoopService.generateUUID();
}
},
getAudioBlob: {
enumerable: true,
writable: true,
value: function(name, callback) {
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
let url = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
request.open("GET", url, true);
request.responseType = "arraybuffer";
request.onload = () => {
if (request.status < 200 || request.status >= 300) {
let error = new Error(request.status + " " + request.statusText);
callback(cloneValueInto(error, targetWindow));
return;
}
let blob = new Blob([request.response], {type: "audio/ogg"});
callback(null, cloneValueInto(blob, targetWindow));
};
request.send();
}
}
};
function onStatusChanged(aSubject, aTopic, aData) {

Просмотреть файл

@ -21,7 +21,7 @@ loop.conversation = (function(mozL10n) {
var DesktopRoomView = loop.roomViews.DesktopRoomView;
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
mixins: [sharedMixins.DropdownMenuMixin],
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
propTypes: {
model: React.PropTypes.object.isRequired,
@ -185,10 +185,16 @@ loop.conversation = (function(mozL10n) {
* incoming call views (bug 1088672).
*/
var GenericFailureView = React.createClass({displayName: 'GenericFailureView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
cancelCall: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("failure");
},
render: function() {
document.title = mozL10n.get("generic_failure_title");

Просмотреть файл

@ -21,7 +21,7 @@ loop.conversation = (function(mozL10n) {
var DesktopRoomView = loop.roomViews.DesktopRoomView;
var IncomingCallView = React.createClass({
mixins: [sharedMixins.DropdownMenuMixin],
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
propTypes: {
model: React.PropTypes.object.isRequired,
@ -185,10 +185,16 @@ loop.conversation = (function(mozL10n) {
* incoming call views (bug 1088672).
*/
var GenericFailureView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
cancelCall: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("failure");
},
render: function() {
document.title = mozL10n.get("generic_failure_title");

Просмотреть файл

@ -14,6 +14,7 @@ loop.conversationViews = (function(mozL10n) {
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
@ -133,6 +134,8 @@ loop.conversationViews = (function(mozL10n) {
* pending/ringing strings.
*/
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
callState: React.PropTypes.string,
@ -146,6 +149,10 @@ loop.conversationViews = (function(mozL10n) {
};
},
componentDidMount: function() {
this.play("ringtone", {loop: true});
},
cancelCall: function() {
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
},
@ -186,7 +193,7 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails.
*/
var CallFailedView = React.createClass({displayName: 'CallFailedView',
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@ -205,6 +212,7 @@ loop.conversationViews = (function(mozL10n) {
},
componentDidMount: function() {
this.play("failure");
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
this.listenTo(this.props.store, "error:emailLink",

Просмотреть файл

@ -14,6 +14,7 @@ loop.conversationViews = (function(mozL10n) {
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
@ -133,6 +134,8 @@ loop.conversationViews = (function(mozL10n) {
* pending/ringing strings.
*/
var PendingConversationView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
callState: React.PropTypes.string,
@ -146,6 +149,10 @@ loop.conversationViews = (function(mozL10n) {
};
},
componentDidMount: function() {
this.play("ringtone", {loop: true});
},
cancelCall: function() {
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
},
@ -186,7 +193,7 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails.
*/
var CallFailedView = React.createClass({
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@ -205,6 +212,7 @@ loop.conversationViews = (function(mozL10n) {
},
componentDidMount: function() {
this.play("failure");
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
this.listenTo(this.props.store, "error:emailLink",

Просмотреть файл

@ -141,6 +141,7 @@ loop.shared.mixins = (function() {
*/
var AudioMixin = {
audio: null,
_audioRequest: null,
_isLoopDesktop: function() {
return typeof rootObject.navigator.mozLoop === "object";
@ -149,27 +150,62 @@ loop.shared.mixins = (function() {
/**
* Starts playing an audio file, stopping any audio that is already in progress.
*
* @param {String} filename The filename to play (excluding the extension).
* @param {String} name The filename to play (excluding the extension).
*/
play: function(filename, options) {
if (this._isLoopDesktop()) {
// XXX: We need navigator.mozLoop.playSound(name), see Bug 1089585.
return;
}
play: function(name, options) {
options = options || {};
options.loop = options.loop || false;
this._ensureAudioStopped();
this.audio = new Audio('shared/sounds/' + filename + ".ogg");
this.audio.loop = options.loop;
this.audio.play();
this._getAudioBlob(name, function(error, blob) {
if (error) {
console.error(error);
return;
}
var url = URL.createObjectURL(blob);
this.audio = new Audio(url);
this.audio.loop = options.loop;
this.audio.play();
}.bind(this));
},
_getAudioBlob: function(name, callback) {
if (this._isLoopDesktop()) {
rootObject.navigator.mozLoop.getAudioBlob(name, callback);
return;
}
var url = "shared/sounds/" + name + ".ogg";
this._audioRequest = new XMLHttpRequest();
this._audioRequest.open("GET", url, true);
this._audioRequest.responseType = "arraybuffer";
this._audioRequest.onload = function() {
var request = this._audioRequest;
var error;
if (request.status < 200 || request.status >= 300) {
error = new Error(request.status + " " + request.statusText);
callback(error);
return;
}
var type = request.getResponseHeader("Content-Type");
var blob = new Blob([request.response], {type: type});
callback(null, blob);
}.bind(this);
this._audioRequest.send(null);
},
/**
* Ensures audio is stopped playing, and removes the object from memory.
*/
_ensureAudioStopped: function() {
if (this._audioRequest) {
this._audioRequest.abort();
delete this._audioRequest;
}
if (this.audio) {
this.audio.pause();
this.audio.removeAttribute("src");

Просмотреть файл

@ -540,6 +540,8 @@ loop.shared.views = (function(_, OT, l10n) {
* Feedback view.
*/
var FeedbackView = React.createClass({displayName: 'FeedbackView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
// A loop.FeedbackAPIClient instance
feedbackApiClient: React.PropTypes.object.isRequired,
@ -556,6 +558,10 @@ loop.shared.views = (function(_, OT, l10n) {
return {step: "start"};
},
componentDidMount: function() {
this.play("terminated");
},
reset: function() {
this.setState(this.getInitialState());
},

Просмотреть файл

@ -540,6 +540,8 @@ loop.shared.views = (function(_, OT, l10n) {
* Feedback view.
*/
var FeedbackView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
// A loop.FeedbackAPIClient instance
feedbackApiClient: React.PropTypes.object.isRequired,
@ -556,6 +558,10 @@ loop.shared.views = (function(_, OT, l10n) {
return {step: "start"};
},
componentDidMount: function() {
this.play("terminated");
},
reset: function() {
this.setState(this.getInitialState());
},

Двоичный файл не отображается.

Просмотреть файл

@ -81,7 +81,11 @@ browser.jar:
content/browser/loop/shared/libs/backbone-1.1.2.js (content/shared/libs/backbone-1.1.2.js)
# Shared sounds
content/browser/loop/shared/sounds/Firefox-Long.ogg (content/shared/sounds/Firefox-Long.ogg)
content/browser/loop/shared/sounds/ringtone.ogg (content/shared/sounds/ringtone.ogg)
content/browser/loop/shared/sounds/connecting.ogg (content/shared/sounds/connecting.ogg)
content/browser/loop/shared/sounds/connected.ogg (content/shared/sounds/connected.ogg)
content/browser/loop/shared/sounds/terminated.ogg (content/shared/sounds/terminated.ogg)
content/browser/loop/shared/sounds/failure.ogg (content/shared/sounds/failure.ogg)
# Partner SDK assets
content/browser/loop/libs/sdk.js (content/shared/libs/sdk.js)

Просмотреть файл

@ -286,7 +286,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
_handleRingingProgress: function() {
this.play("ringing", {loop: true});
this.play("ringtone", {loop: true});
this.setState({callState: "ringing"});
},
@ -534,8 +534,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
* Ended conversation view.
*/
var EndedConversationView = React.createClass({displayName: 'EndedConversationView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
@ -544,10 +542,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
onAfterFeedbackReceived: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("terminated");
},
render: function() {
document.title = mozL10n.get("standalone_title_with_status",
{clientShortname: mozL10n.get("clientShortname2"),

Просмотреть файл

@ -286,7 +286,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
_handleRingingProgress: function() {
this.play("ringing", {loop: true});
this.play("ringtone", {loop: true});
this.setState({callState: "ringing"});
},
@ -534,8 +534,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
* Ended conversation view.
*/
var EndedConversationView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
@ -544,10 +542,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
onAfterFeedbackReceived: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("terminated");
},
render: function() {
document.title = mozL10n.get("standalone_title_with_status",
{clientShortname: mozL10n.get("clientShortname2"),

Просмотреть файл

@ -7,7 +7,7 @@ describe("loop.conversationViews", function () {
"use strict";
var sharedUtils = loop.shared.utils;
var sandbox, oldTitle, view, dispatcher, contact;
var sandbox, oldTitle, view, dispatcher, contact, fakeAudioXHR;
var CALL_STATES = loop.store.CALL_STATES;
@ -30,11 +30,39 @@ describe("loop.conversationViews", function () {
pref: true
}]
};
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
};
navigator.mozLoop = {
getLoopCharPref: sinon.stub().returns("http://fakeurl"),
composeEmail: sinon.spy(),
get appVersionInfo() {
return {
version: "42",
channel: "test",
platform: "test"
};
},
getAudioBlob: sinon.spy(function(name, callback) {
callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
})
};
});
afterEach(function() {
document.title = oldTitle;
view = undefined;
delete navigator.mozLoop;
sandbox.restore();
});
@ -202,7 +230,7 @@ describe("loop.conversationViews", function () {
});
describe("CallFailedView", function() {
var store;
var store, fakeAudio;
function mountTestComponent(props) {
return TestUtils.renderIntoDocument(
@ -219,6 +247,12 @@ describe("loop.conversationViews", function () {
client: {},
sdkDriver: {}
});
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
});
it("should dispatch a retryCall action when the retry button is pressed",
@ -306,6 +340,16 @@ describe("loop.conversationViews", function () {
expect(view.getDOMNode().querySelector(".btn-email").disabled).eql(false);
});
it("should play a failure sound, once", function() {
view = mountTestComponent();
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
"failure", sinon.match.func);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);
});
});
describe("OngoingConversationView", function() {
@ -412,11 +456,6 @@ describe("loop.conversationViews", function () {
}
beforeEach(function() {
navigator.mozLoop = {
getLoopCharPref: function() { return "fake"; },
appVersionInfo: sinon.spy()
};
store = new loop.store.ConversationStore({}, {
dispatcher: dispatcher,
client: {},
@ -424,10 +463,6 @@ describe("loop.conversationViews", function () {
});
});
afterEach(function() {
delete navigator.mozLoop;
});
it("should render the CallFailedView when the call state is 'terminated'",
function() {
store.set({callState: CALL_STATES.TERMINATED});

Просмотреть файл

@ -57,7 +57,10 @@ describe("loop.conversation", function() {
channel: "test",
platform: "test"
};
}
},
getAudioBlob: sinon.spy(function(name, callback) {
callback(null, new Blob([new ArrayBuffer(10)], {type: 'audio/ogg'}));
})
};
// XXX These stubs should be hoisted in a common file
@ -690,8 +693,8 @@ describe("loop.conversation", function() {
function() {
conversation.trigger("session:network-disconnected");
TestUtils.findRenderedComponentWithType(icView,
loop.conversation.GenericFailureView);
TestUtils.findRenderedComponentWithType(icView,
loop.conversation.GenericFailureView);
});
it("should update the conversation window toolbar title",
@ -747,7 +750,7 @@ describe("loop.conversation", function() {
});
describe("IncomingCallView", function() {
var view, model;
var view, model, fakeAudio;
beforeEach(function() {
var Model = Backbone.Model.extend({
@ -757,6 +760,13 @@ describe("loop.conversation", function() {
sandbox.spy(model, "trigger");
sandbox.stub(model, "set");
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
model: model,
video: true
@ -896,4 +906,32 @@ describe("loop.conversation", function() {
});
});
});
describe("GenericFailureView", function() {
var view, fakeAudio;
beforeEach(function() {
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
view = TestUtils.renderIntoDocument(
loop.conversation.GenericFailureView({
cancelCall: function() {}
})
);
});
it("should play a failure sound, once", function() {
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
"failure", sinon.match.func);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);
});
});
});

Просмотреть файл

@ -15,7 +15,7 @@ describe("loop.shared.views", function() {
var sharedModels = loop.shared.models,
sharedViews = loop.shared.views,
getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass,
sandbox;
sandbox, fakeAudioXHR;
beforeEach(function() {
sandbox = sinon.sandbox.create();
@ -23,6 +23,18 @@ describe("loop.shared.views", function() {
sandbox.stub(l10n, "get", function(x) {
return "translated:" + x;
});
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
};
});
afterEach(function() {
@ -368,16 +380,55 @@ describe("loop.shared.views", function() {
it("should play a connected sound, once, on session:connected",
function() {
var url = "shared/sounds/connected.ogg";
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
model.trigger("session:connected");
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(
window.Audio, "shared/sounds/connected.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(fakeAudioXHR.open, "GET", url, true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.not.equal(true);
});
});
describe("for desktop", function() {
var origMozLoop;
beforeEach(function() {
origMozLoop = navigator.mozLoop;
navigator.mozLoop = {
getAudioBlob: sinon.spy(function(name, callback) {
var data = new ArrayBuffer(10);
callback(null, new Blob([data], {type: "audio/ogg"}));
})
};
});
afterEach(function() {
navigator.mozLoop = origMozLoop;
});
it("should play a connected sound, once, on session:connected",
function() {
var url = "chrome://browser/content/loop/shared/sounds/connected.ogg";
model.trigger("session:connected");
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
"connected", sinon.match.func);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.not.equal(true);
});
});
describe("for both (standalone and desktop)", function() {
beforeEach(function() {
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
});
it("should start streaming on session:connected", function() {
model.trigger("session:connected");
@ -458,6 +509,7 @@ describe("loop.shared.views", function() {
beforeEach(function() {
fakeFeedbackApiClient = {send: sandbox.stub()};
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({
feedbackApiClient: fakeFeedbackApiClient
}));

Просмотреть файл

@ -18,7 +18,8 @@ describe("loop.webapp", function() {
sandbox,
notifications,
feedbackApiClient,
stubGetPermsAndCacheMedia;
stubGetPermsAndCacheMedia,
fakeAudioXHR;
beforeEach(function() {
sandbox = sinon.sandbox.create();
@ -29,6 +30,19 @@ describe("loop.webapp", function() {
stubGetPermsAndCacheMedia = sandbox.stub(
loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
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
};
});
afterEach(function() {
@ -219,6 +233,7 @@ describe("loop.webapp", function() {
describe("state: terminate, reason: reject", function() {
beforeEach(function() {
sandbox.stub(notifications, "errorL10n");
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
});
it("should display the FailedConversationView", function() {
@ -307,6 +322,7 @@ describe("loop.webapp", function() {
promiseConnectStub =
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect");
promiseConnectStub.returns(new Promise(function(resolve, reject) {}));
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
});
describe("call:outgoing", function() {
@ -526,6 +542,8 @@ describe("loop.webapp", function() {
var view, conversation, client, fakeAudio;
beforeEach(function() {
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
@ -541,6 +559,7 @@ describe("loop.webapp", function() {
});
conversation.set("loopToken", "fakeToken");
sandbox.stub(client, "requestCallUrlInfo");
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.FailedConversationView({
conversation: conversation,
@ -550,9 +569,12 @@ describe("loop.webapp", function() {
});
it("should play a failure sound, once", function() {
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(window.Audio,
"shared/sounds/failure.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/failure.ogg", true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);
});
});
@ -678,6 +700,7 @@ describe("loop.webapp", function() {
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.PendingConversationView({
@ -689,8 +712,12 @@ describe("loop.webapp", function() {
describe("#componentDidMount", function() {
it("should play a looped connecting sound", function() {
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/connecting.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/connecting.ogg", true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(true);
});
@ -727,8 +754,13 @@ describe("loop.webapp", function() {
it("should play a looped ringing sound", function() {
websocket.trigger("progress:alerting");
fakeAudioXHR.onload();
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/ringing.ogg");
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/ringtone.ogg", true);
sinon.assert.called(fakeAudio.play);
expect(fakeAudio.loop).to.equal(true);
});
});
@ -997,6 +1029,7 @@ describe("loop.webapp", function() {
conversation = new sharedModels.ConversationModel({}, {
sdk: {}
});
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.EndedConversationView({
conversation: conversation,
@ -1018,8 +1051,13 @@ describe("loop.webapp", function() {
describe("#componentDidMount", function() {
it("should play a terminating sound, once", function() {
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/terminated.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/terminated.ogg", true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.not.equal(true);
});