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:
Mark Banner 2015-03-12 14:01:38 +00:00
Родитель d5f776b7d0
Коммит 0984661eea
7 изменённых файлов: 108 добавлений и 35 удалений

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

@ -35,7 +35,8 @@ loop.conversation = (function(mozL10n) {
propTypes: {
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() {
@ -48,7 +49,8 @@ loop.conversation = (function(mozL10n) {
case "incoming":
case "outgoing": {
return (React.createElement(CallControllerView, {
dispatcher: this.props.dispatcher}
dispatcher: this.props.dispatcher,
mozLoop: this.props.mozLoop}
));
}
case "room": {
@ -152,17 +154,13 @@ loop.conversation = (function(mozL10n) {
}
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());
});
React.render(React.createElement(AppControllerView, {
roomStore: roomStore,
dispatcher: dispatcher}
dispatcher: dispatcher,
mozLoop: navigator.mozLoop}
), document.querySelector('#main'));
dispatcher.dispatch(new sharedActions.GetWindowData({

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

@ -35,7 +35,8 @@ loop.conversation = (function(mozL10n) {
propTypes: {
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() {
@ -49,6 +50,7 @@ loop.conversation = (function(mozL10n) {
case "outgoing": {
return (<CallControllerView
dispatcher={this.props.dispatcher}
mozLoop={this.props.mozLoop}
/>);
}
case "room": {
@ -152,17 +154,13 @@ loop.conversation = (function(mozL10n) {
}
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());
});
React.render(<AppControllerView
roomStore={roomStore}
dispatcher={dispatcher}
mozLoop={navigator.mozLoop}
/>, document.querySelector('#main'));
dispatcher.dispatch(new sharedActions.GetWindowData({

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

@ -147,6 +147,7 @@ loop.conversationViews = (function(mozL10n) {
callType: React.PropTypes.string.isRequired,
callerId: React.PropTypes.string.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired,
// Only for use by the ui-showcase
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) {
var target = e.target;
if (!target.classList.contains('btn-chevron')) {
@ -939,7 +948,8 @@ loop.conversationViews = (function(mozL10n) {
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired
},
getInitialState: function() {
@ -989,7 +999,8 @@ loop.conversationViews = (function(mozL10n) {
return (React.createElement(AcceptCallView, {
callType: this.state.callType,
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,
callerId: React.PropTypes.string.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired,
// Only for use by the ui-showcase
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) {
var target = e.target;
if (!target.classList.contains('btn-chevron')) {
@ -939,7 +948,8 @@ loop.conversationViews = (function(mozL10n) {
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired
},
getInitialState: function() {
@ -990,6 +1000,7 @@ loop.conversationViews = (function(mozL10n) {
callType={this.state.callType}
callerId={this.state.callerId}
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.
*/
windowUnload: function() {
if (!this.getStoreState("outgoing") &&
this.getStoreState("callState") === CALL_STATES.ALERTING &&
this._websocket) {
this._websocket.decline();
}
this._endSession();
},

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

@ -586,6 +586,7 @@ describe("loop.conversationViews", function () {
return TestUtils.renderIntoDocument(
React.createElement(loop.conversationViews.CallControllerView, {
dispatcher: dispatcher,
mozLoop: fakeMozLoop
}));
}
@ -1224,7 +1225,8 @@ describe("loop.conversationViews", function () {
describe("AcceptCallView", function() {
var view;
function mountTestComponent(props) {
function mountTestComponent(extraProps) {
var props = _.extend({dispatcher: dispatcher, mozLoop: fakeMozLoop}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(loop.conversationViews.AcceptCallView, props));
}
@ -1233,12 +1235,31 @@ describe("loop.conversationViews", function () {
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() {
it("should display video as primary answer mode", function() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_VIDEO,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
var primaryBtn = view.getDOMNode()
@ -1250,8 +1271,7 @@ describe("loop.conversationViews", function () {
it("should display audio as primary answer mode", function() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_ONLY,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
var primaryBtn = view.getDOMNode()
@ -1263,8 +1283,7 @@ describe("loop.conversationViews", function () {
it("should accept call with video", function() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_VIDEO,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
var primaryBtn = view.getDOMNode()
@ -1282,8 +1301,7 @@ describe("loop.conversationViews", function () {
it("should accept call with audio", function() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_ONLY,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
var primaryBtn = view.getDOMNode()
@ -1302,8 +1320,7 @@ describe("loop.conversationViews", function () {
function() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_ONLY,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
var secondaryBtn = view.getDOMNode()
@ -1322,8 +1339,7 @@ describe("loop.conversationViews", function () {
function() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_VIDEO,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
var secondaryBtn = view.getDOMNode()
@ -1343,8 +1359,7 @@ describe("loop.conversationViews", function () {
it("should dispatch a DeclineCall action", function() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_VIDEO,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
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() {
view = mountTestComponent({
callType: CALL_TYPES.AUDIO_VIDEO,
callerId: "fake@invalid.com",
dispatcher: dispatcher
callerId: "fake@invalid.com"
});
var buttonBlock = view.getDOMNode().querySelector(".btn-block");

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

@ -915,11 +915,46 @@ describe("loop.store.ConversationStore", 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();
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() {