Bug 1088672 - Part 4. Rewrite Loop's incoming call handling in the flux style. Put back alerts and make window unload be handled correctly. r=mikedeboer
This commit is contained in:
@@ -35,7 +35,8 @@ loop.conversation = (function(mozL10n) {
|
|||||||
|
|
||||||
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),
|
||||||
|
mozLoop: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@@ -48,7 +49,8 @@ loop.conversation = (function(mozL10n) {
|
|||||||
case "incoming":
|
case "incoming":
|
||||||
case "outgoing": {
|
case "outgoing": {
|
||||||
return (React.createElement(CallControllerView, {
|
return (React.createElement(CallControllerView, {
|
||||||
dispatcher: this.props.dispatcher}
|
dispatcher: this.props.dispatcher,
|
||||||
|
mozLoop: this.props.mozLoop}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
case "room": {
|
case "room": {
|
||||||
@@ -152,17 +154,13 @@ loop.conversation = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("unload", function(event) {
|
window.addEventListener("unload", function(event) {
|
||||||
// Handle direct close of dialog box via [x] control.
|
|
||||||
// XXX Move to the conversation models, when we transition
|
|
||||||
// incoming calls to flux (bug 1088672).
|
|
||||||
navigator.mozLoop.calls.clearCallInProgress(windowId);
|
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.WindowUnload());
|
dispatcher.dispatch(new sharedActions.WindowUnload());
|
||||||
});
|
});
|
||||||
|
|
||||||
React.render(React.createElement(AppControllerView, {
|
React.render(React.createElement(AppControllerView, {
|
||||||
roomStore: roomStore,
|
roomStore: roomStore,
|
||||||
dispatcher: dispatcher}
|
dispatcher: dispatcher,
|
||||||
|
mozLoop: navigator.mozLoop}
|
||||||
), document.querySelector('#main'));
|
), document.querySelector('#main'));
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ loop.conversation = (function(mozL10n) {
|
|||||||
|
|
||||||
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),
|
||||||
|
mozLoop: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@@ -49,6 +50,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
case "outgoing": {
|
case "outgoing": {
|
||||||
return (<CallControllerView
|
return (<CallControllerView
|
||||||
dispatcher={this.props.dispatcher}
|
dispatcher={this.props.dispatcher}
|
||||||
|
mozLoop={this.props.mozLoop}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
case "room": {
|
case "room": {
|
||||||
@@ -152,17 +154,13 @@ loop.conversation = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("unload", function(event) {
|
window.addEventListener("unload", function(event) {
|
||||||
// Handle direct close of dialog box via [x] control.
|
|
||||||
// XXX Move to the conversation models, when we transition
|
|
||||||
// incoming calls to flux (bug 1088672).
|
|
||||||
navigator.mozLoop.calls.clearCallInProgress(windowId);
|
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.WindowUnload());
|
dispatcher.dispatch(new sharedActions.WindowUnload());
|
||||||
});
|
});
|
||||||
|
|
||||||
React.render(<AppControllerView
|
React.render(<AppControllerView
|
||||||
roomStore={roomStore}
|
roomStore={roomStore}
|
||||||
dispatcher={dispatcher}
|
dispatcher={dispatcher}
|
||||||
|
mozLoop={navigator.mozLoop}
|
||||||
/>, document.querySelector('#main'));
|
/>, document.querySelector('#main'));
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
callType: React.PropTypes.string.isRequired,
|
callType: React.PropTypes.string.isRequired,
|
||||||
callerId: React.PropTypes.string.isRequired,
|
callerId: React.PropTypes.string.isRequired,
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
mozLoop: React.PropTypes.object.isRequired,
|
||||||
// Only for use by the ui-showcase
|
// Only for use by the ui-showcase
|
||||||
showMenu: React.PropTypes.bool
|
showMenu: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
@@ -157,6 +158,14 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.mozLoop.startAlerting();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.props.mozLoop.stopAlerting();
|
||||||
|
},
|
||||||
|
|
||||||
clickHandler: function(e) {
|
clickHandler: function(e) {
|
||||||
var target = e.target;
|
var target = e.target;
|
||||||
if (!target.classList.contains('btn-chevron')) {
|
if (!target.classList.contains('btn-chevron')) {
|
||||||
@@ -939,7 +948,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
],
|
],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
mozLoop: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@@ -989,7 +999,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
return (React.createElement(AcceptCallView, {
|
return (React.createElement(AcceptCallView, {
|
||||||
callType: this.state.callType,
|
callType: this.state.callType,
|
||||||
callerId: this.state.callerId,
|
callerId: this.state.callerId,
|
||||||
dispatcher: this.props.dispatcher}
|
dispatcher: this.props.dispatcher,
|
||||||
|
mozLoop: this.props.mozLoop}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
callType: React.PropTypes.string.isRequired,
|
callType: React.PropTypes.string.isRequired,
|
||||||
callerId: React.PropTypes.string.isRequired,
|
callerId: React.PropTypes.string.isRequired,
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
mozLoop: React.PropTypes.object.isRequired,
|
||||||
// Only for use by the ui-showcase
|
// Only for use by the ui-showcase
|
||||||
showMenu: React.PropTypes.bool
|
showMenu: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
@@ -157,6 +158,14 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.mozLoop.startAlerting();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.props.mozLoop.stopAlerting();
|
||||||
|
},
|
||||||
|
|
||||||
clickHandler: function(e) {
|
clickHandler: function(e) {
|
||||||
var target = e.target;
|
var target = e.target;
|
||||||
if (!target.classList.contains('btn-chevron')) {
|
if (!target.classList.contains('btn-chevron')) {
|
||||||
@@ -939,7 +948,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
],
|
],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
mozLoop: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@@ -990,6 +1000,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
callType={this.state.callType}
|
callType={this.state.callType}
|
||||||
callerId={this.state.callerId}
|
callerId={this.state.callerId}
|
||||||
dispatcher={this.props.dispatcher}
|
dispatcher={this.props.dispatcher}
|
||||||
|
mozLoop={this.props.mozLoop}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -425,6 +425,12 @@ loop.store = loop.store || {};
|
|||||||
* as shutting down the call cleanly and adding any relevant telemetry data.
|
* as shutting down the call cleanly and adding any relevant telemetry data.
|
||||||
*/
|
*/
|
||||||
windowUnload: function() {
|
windowUnload: function() {
|
||||||
|
if (!this.getStoreState("outgoing") &&
|
||||||
|
this.getStoreState("callState") === CALL_STATES.ALERTING &&
|
||||||
|
this._websocket) {
|
||||||
|
this._websocket.decline();
|
||||||
|
}
|
||||||
|
|
||||||
this._endSession();
|
this._endSession();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -586,6 +586,7 @@ describe("loop.conversationViews", function () {
|
|||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
React.createElement(loop.conversationViews.CallControllerView, {
|
React.createElement(loop.conversationViews.CallControllerView, {
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher,
|
||||||
|
mozLoop: fakeMozLoop
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1224,7 +1225,8 @@ describe("loop.conversationViews", function () {
|
|||||||
describe("AcceptCallView", function() {
|
describe("AcceptCallView", function() {
|
||||||
var view;
|
var view;
|
||||||
|
|
||||||
function mountTestComponent(props) {
|
function mountTestComponent(extraProps) {
|
||||||
|
var props = _.extend({dispatcher: dispatcher, mozLoop: fakeMozLoop}, extraProps);
|
||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
React.createElement(loop.conversationViews.AcceptCallView, props));
|
React.createElement(loop.conversationViews.AcceptCallView, props));
|
||||||
}
|
}
|
||||||
@@ -1233,12 +1235,31 @@ describe("loop.conversationViews", function () {
|
|||||||
view = null;
|
view = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should start alerting on display", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
|
callerId: "fake@invalid.com"
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeMozLoop.startAlerting);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should stop alerting when removed from the display", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
|
callerId: "fake@invalid.com"
|
||||||
|
});
|
||||||
|
|
||||||
|
view.componentWillUnmount();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeMozLoop.stopAlerting);
|
||||||
|
});
|
||||||
|
|
||||||
describe("default answer mode", function() {
|
describe("default answer mode", function() {
|
||||||
it("should display video as primary answer mode", function() {
|
it("should display video as primary answer mode", function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_VIDEO,
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var primaryBtn = view.getDOMNode()
|
var primaryBtn = view.getDOMNode()
|
||||||
@@ -1250,8 +1271,7 @@ describe("loop.conversationViews", function () {
|
|||||||
it("should display audio as primary answer mode", function() {
|
it("should display audio as primary answer mode", function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_ONLY,
|
callType: CALL_TYPES.AUDIO_ONLY,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var primaryBtn = view.getDOMNode()
|
var primaryBtn = view.getDOMNode()
|
||||||
@@ -1263,8 +1283,7 @@ describe("loop.conversationViews", function () {
|
|||||||
it("should accept call with video", function() {
|
it("should accept call with video", function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_VIDEO,
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var primaryBtn = view.getDOMNode()
|
var primaryBtn = view.getDOMNode()
|
||||||
@@ -1282,8 +1301,7 @@ describe("loop.conversationViews", function () {
|
|||||||
it("should accept call with audio", function() {
|
it("should accept call with audio", function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_ONLY,
|
callType: CALL_TYPES.AUDIO_ONLY,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var primaryBtn = view.getDOMNode()
|
var primaryBtn = view.getDOMNode()
|
||||||
@@ -1302,8 +1320,7 @@ describe("loop.conversationViews", function () {
|
|||||||
function() {
|
function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_ONLY,
|
callType: CALL_TYPES.AUDIO_ONLY,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var secondaryBtn = view.getDOMNode()
|
var secondaryBtn = view.getDOMNode()
|
||||||
@@ -1322,8 +1339,7 @@ describe("loop.conversationViews", function () {
|
|||||||
function() {
|
function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_VIDEO,
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var secondaryBtn = view.getDOMNode()
|
var secondaryBtn = view.getDOMNode()
|
||||||
@@ -1343,8 +1359,7 @@ describe("loop.conversationViews", function () {
|
|||||||
it("should dispatch a DeclineCall action", function() {
|
it("should dispatch a DeclineCall action", function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_VIDEO,
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var buttonDecline = view.getDOMNode().querySelector(".btn-decline");
|
var buttonDecline = view.getDOMNode().querySelector(".btn-decline");
|
||||||
@@ -1361,8 +1376,7 @@ describe("loop.conversationViews", function () {
|
|||||||
it("should dispatch a DeclineCall action with blockCaller true", function() {
|
it("should dispatch a DeclineCall action with blockCaller true", function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callType: CALL_TYPES.AUDIO_VIDEO,
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
callerId: "fake@invalid.com",
|
callerId: "fake@invalid.com"
|
||||||
dispatcher: dispatcher
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var buttonBlock = view.getDOMNode().querySelector(".btn-block");
|
var buttonBlock = view.getDOMNode().querySelector(".btn-block");
|
||||||
|
|||||||
@@ -915,11 +915,46 @@ describe("loop.store.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#windowUnload", function() {
|
describe("#windowUnload", function() {
|
||||||
it("should disconnect from the servers via the sdk", function() {
|
var fakeWebsocket;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
fakeWebsocket = store._websocket = {
|
||||||
|
close: sinon.stub(),
|
||||||
|
decline: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
store.setStoreState({windowId: 42});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should decline the connection on the websocket for incoming calls if the state is alerting", function() {
|
||||||
|
store.setStoreState({
|
||||||
|
callState: CALL_STATES.ALERTING,
|
||||||
|
outgoing: false
|
||||||
|
});
|
||||||
|
|
||||||
|
store.windowUnload();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeWebsocket.decline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disconnect the sdk session", function() {
|
||||||
store.windowUnload();
|
store.windowUnload();
|
||||||
|
|
||||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should close the websocket", function() {
|
||||||
|
store.windowUnload();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeWebsocket.close);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clear the call in progress for the backend", function() {
|
||||||
|
store.windowUnload();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
||||||
|
sinon.assert.calledWithExactly(fakeMozLoop.calls.clearCallInProgress, 42);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Events", function() {
|
describe("Events", function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user