Back out csets 56ba52f28300, f6016481c7f2, 94b064ee68b2 (bug 1033841 and bug 1033965) for xpcshell test failures

This commit is contained in:
Benoit Jacob 2014-07-04 13:34:25 -04:00
parent d0a7c92c94
commit 91957db779
17 changed files with 448 additions and 19592 deletions

View File

@ -6,16 +6,3 @@ The standalone client is a set of web pages intended to be hosted on a standalon
The standalone client exists in standalone/ but shares items (from content/shared/) with the desktop implementation. See the README.md file in the standalone/ directory for how to run the server locally.
Working with JSX
================
You need to install the JSX compiler in order to compile the .jsx files into regular .js ones.
The JSX compiler is installable using npm:
npm install -g react-tools
Once installed, run it with the --watch option, eg.:
jsx --watch --x jsx browser/components/loop/content/js/src \
browser/components/loop/content/js

View File

@ -2,7 +2,6 @@
* 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/. */
/* jshint esnext:true */
/* global loop:true, hawk, deriveHawkCredentials */
var loop = loop || {};

View File

@ -2,7 +2,6 @@
* 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/. */
/* jshint esnext:true */
/* global loop:true */
var loop = loop || {};

View File

@ -2,7 +2,6 @@
* 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/. */
/* jshint esnext:true */
/* global loop:true */
var loop = loop || {};

View File

@ -1,11 +1,8 @@
/** @jsx React.DOM */
/* 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/. */
/*jshint newcap:false*/
/*global loop:true, React */
/* global loop:true */
var loop = loop || {};
loop.panel = (function(_, mozL10n) {
@ -24,188 +21,162 @@ loop.panel = (function(_, mozL10n) {
/**
* Do not disturb panel subview.
*/
var DoNotDisturb = React.createClass({displayName: 'DoNotDisturb',
getInitialState: function() {
return {doNotDisturb: navigator.mozLoop.doNotDisturb};
var DoNotDisturbView = sharedViews.BaseView.extend({
template: _.template([
'<label>',
' <input type="checkbox" <%- checked %>>',
' <span data-l10n-id="do_not_disturb"></span>',
'</label>',
].join('')),
events: {
"click input[type=checkbox]": "toggle"
},
handleCheckboxChange: function() {
// Note: side effect!
/**
* Toggles mozLoop activation status.
*/
toggle: function() {
navigator.mozLoop.doNotDisturb = !navigator.mozLoop.doNotDisturb;
this.setState({doNotDisturb: navigator.mozLoop.doNotDisturb});
this.render();
},
render: function() {
// XXX https://github.com/facebook/react/issues/310 for === htmlFor
return (
React.DOM.p( {className:"dnd"},
React.DOM.input( {type:"checkbox", checked:this.state.doNotDisturb,
id:"dnd-component", onChange:this.handleCheckboxChange} ),
React.DOM.label( {htmlFor:"dnd-component"}, __("do_not_disturb"))
)
);
this.$el.html(this.template({
checked: navigator.mozLoop.doNotDisturb ? "checked" : ""
}));
return this;
}
});
var ToSView = React.createClass({displayName: 'ToSView',
getInitialState: function() {
return {seenToS: navigator.mozLoop.getLoopCharPref('seenToS')};
},
var ToSView = sharedViews.BaseView.extend({
template: _.template([
'<p data-l10n-id="legal_text_and_links"',
' data-l10n-args=\'',
' {"terms_of_use_url": "https://accounts.firefox.com/legal/terms",',
' "privacy_notice_url": "www.mozilla.org/privacy/"',
' }\'></p>'
].join('')),
render: function() {
var tosHTML = __("legal_text_and_links", {
"terms_of_use_url": "https://accounts.firefox.com/legal/terms",
"privacy_notice_url": "www.mozilla.org/privacy/"
});
if (!this.state.seenToS) {
if (navigator.mozLoop.getLoopCharPref('seenToS') === null) {
this.$el.html(this.template());
navigator.mozLoop.setLoopCharPref('seenToS', 'seen');
return React.DOM.p( {className:"tos",
dangerouslySetInnerHTML:{__html: tosHTML}});
} else {
return React.DOM.div(null );
}
}
});
var PanelLayout = React.createClass({displayName: 'PanelLayout',
propTypes: {
summary: React.PropTypes.string.isRequired
},
render: function() {
return (
React.DOM.div( {className:"share generate-url"},
React.DOM.div( {className:"description"},
React.DOM.p(null, this.props.summary)
),
React.DOM.div( {className:"action"},
this.props.children
)
)
);
}
});
var CallUrlResult = React.createClass({displayName: 'CallUrlResult',
propTypes: {
callUrl: React.PropTypes.string.isRequired,
retry: React.PropTypes.func.isRequired
},
handleButtonClick: function() {
this.props.retry();
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
return (
PanelLayout( {summary:__("share_link_url")},
React.DOM.div( {className:"invite"},
React.DOM.input( {type:"url", value:this.props.callUrl, readOnly:"true"} ),
React.DOM.button( {onClick:this.handleButtonClick,
className:"btn btn-success"}, __("new_url"))
)
)
);
}
});
var CallUrlForm = React.createClass({displayName: 'CallUrlForm',
propTypes: {
client: React.PropTypes.object.isRequired,
notifier: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
disabled: true,
callUrl: false
};
},
retry: function() {
this.setState(this.getInitialState());
},
handleTextChange: function(event) {
this.setState({disabled: !event.currentTarget.value});
},
handleFormSubmit: function(event) {
event.preventDefault();
this.setState({pending: true});
this.props.client.requestCallUrl(
this.refs.caller.getDOMNode().value, this._onCallUrlReceived);
},
_onCallUrlReceived: function(err, callUrlData) {
var callUrl = false;
this.props.notifier.clear();
if (err) {
this.props.notifier.errorL10n("unable_retrieve_url");
} else {
callUrl = callUrlData.callUrl || callUrlData.call_url;
}
this.setState({pending: false, callUrl: callUrl});
},
render: function() {
// If we have a call url, render result
if (this.state.callUrl) {
return (
CallUrlResult( {callUrl:this.state.callUrl, retry:this.retry})
);
}
// If we don't display the form
var cx = React.addons.classSet;
return (
PanelLayout( {summary:__("get_link_to_share")},
React.DOM.form( {className:"invite", onSubmit:this.handleFormSubmit},
React.DOM.input( {type:"text", name:"caller", ref:"caller", required:"required",
className:cx({'pending': this.state.pending}),
onChange:this.handleTextChange,
placeholder:__("call_identifier_textinput_placeholder")} ),
React.DOM.button( {type:"submit", className:"get-url btn btn-success",
disabled:this.state.disabled},
__("get_a_call_url")
)
),
ToSView(null )
)
);
return this;
}
});
/**
* Panel view.
*/
var PanelView = React.createClass({displayName: 'PanelView',
propTypes: {
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
var PanelView = sharedViews.BaseView.extend({
template: _.template([
'<div class="description">',
' <p data-l10n-id="get_link_to_share"></p>',
'</div>',
'<div class="action">',
' <form class="invite">',
' <input type="text" name="caller" data-l10n-id="caller" required>',
' <button type="submit" class="get-url btn btn-success"',
' data-l10n-id="get_a_call_url"></button>',
' </form>',
' <p class="tos"></p>',
' <p class="result hide">',
' <input id="call-url" type="url" readonly>',
' <a class="go-back btn btn-info" href="" data-l10n-id="new_url"></a>',
' </p>',
' <p class="dnd"></p>',
'</div>',
].join("")),
className: "share generate-url",
/**
* Do not disturb view.
* @type {DoNotDisturbView|undefined}
*/
dndView: undefined,
events: {
"keyup input[name=caller]": "changeButtonState",
"submit form.invite": "getCallUrl",
"click a.go-back": "goBack"
},
initialize: function(options) {
options = options || {};
if (!options.notifier) {
throw new Error("missing required notifier");
}
this.notifier = options.notifier;
this.client = new loop.Client();
},
getNickname: function() {
return this.$("input[name=caller]").val();
},
getCallUrl: function(event) {
this.notifier.clear();
event.preventDefault();
var callback = function(err, callUrlData) {
this.clearPending();
if (err) {
this.notifier.errorL10n("unable_retrieve_url");
this.render();
return;
}
this.onCallUrlReceived(callUrlData);
}.bind(this);
this.setPending();
this.client.requestCallUrl(this.getNickname(), callback);
},
goBack: function(event) {
event.preventDefault();
this.$(".action .result").hide();
this.$(".action .invite").show();
this.$(".description p").text(__("get_link_to_share"));
this.changeButtonState();
},
onCallUrlReceived: function(callUrlData) {
this.notifier.clear();
this.$(".action .invite").hide();
this.$(".action .invite input").val("");
this.$(".action .result input").val(callUrlData.callUrl);
this.$(".action .result").show();
this.$(".description p").text(__("share_link_url"));
},
setPending: function() {
this.$("[name=caller]").addClass("pending");
this.$(".get-url").addClass("disabled").attr("disabled", "disabled");
},
clearPending: function() {
this.$("[name=caller]").removeClass("pending");
this.changeButtonState();
},
changeButtonState: function() {
var enabled = !!this.$("input[name=caller]").val();
if (enabled) {
this.$(".get-url").removeClass("disabled")
.removeAttr("disabled", "disabled");
} else {
this.$(".get-url").addClass("disabled").attr("disabled", "disabled");
}
},
render: function() {
return (
React.DOM.div(null,
CallUrlForm( {client:this.props.client,
notifier:this.props.notifier} ),
DoNotDisturb(null )
)
);
this.$el.html(this.template());
// Do not Disturb sub view
this.dndView = new DoNotDisturbView({el: this.$(".dnd")}).render();
this.tosView = new ToSView({el: this.$(".tos")}).render();
return this;
}
});
@ -259,12 +230,10 @@ loop.panel = (function(_, mozL10n) {
* Resets this router to its initial state.
*/
reset: function() {
// purge pending notifications
this._notifier.clear();
var client = new loop.Client({
baseServerUrl: navigator.mozLoop.serverUrl
});
this.loadReactComponent(PanelView( {client:client,
notifier:this._notifier} ));
// reset home view
this.loadView(new PanelView({notifier: this._notifier}));
}
});
@ -290,9 +259,8 @@ loop.panel = (function(_, mozL10n) {
return {
init: init,
DoNotDisturb: DoNotDisturb,
CallUrlForm: CallUrlForm,
PanelView: PanelView,
DoNotDisturbView: DoNotDisturbView,
PanelRouter: PanelRouter,
ToSView: ToSView
};

View File

@ -1,299 +0,0 @@
/** @jsx React.DOM */
/* 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/. */
/*jshint newcap:false*/
/*global loop:true, React */
var loop = loop || {};
loop.panel = (function(_, mozL10n) {
"use strict";
var sharedViews = loop.shared.views,
// aliasing translation function as __ for concision
__ = mozL10n.get;
/**
* Panel router.
* @type {loop.desktopRouter.DesktopRouter}
*/
var router;
/**
* Do not disturb panel subview.
*/
var DoNotDisturb = React.createClass({
getInitialState: function() {
return {doNotDisturb: navigator.mozLoop.doNotDisturb};
},
handleCheckboxChange: function() {
// Note: side effect!
navigator.mozLoop.doNotDisturb = !navigator.mozLoop.doNotDisturb;
this.setState({doNotDisturb: navigator.mozLoop.doNotDisturb});
},
render: function() {
// XXX https://github.com/facebook/react/issues/310 for === htmlFor
return (
<p className="dnd">
<input type="checkbox" checked={this.state.doNotDisturb}
id="dnd-component" onChange={this.handleCheckboxChange} />
<label htmlFor="dnd-component">{__("do_not_disturb")}</label>
</p>
);
}
});
var ToSView = React.createClass({
getInitialState: function() {
return {seenToS: navigator.mozLoop.getLoopCharPref('seenToS')};
},
render: function() {
var tosHTML = __("legal_text_and_links", {
"terms_of_use_url": "https://accounts.firefox.com/legal/terms",
"privacy_notice_url": "www.mozilla.org/privacy/"
});
if (!this.state.seenToS) {
navigator.mozLoop.setLoopCharPref('seenToS', 'seen');
return <p className="tos"
dangerouslySetInnerHTML={{__html: tosHTML}}></p>;
} else {
return <div />;
}
}
});
var PanelLayout = React.createClass({
propTypes: {
summary: React.PropTypes.string.isRequired
},
render: function() {
return (
<div className="share generate-url">
<div className="description">
<p>{this.props.summary}</p>
</div>
<div className="action">
{this.props.children}
</div>
</div>
);
}
});
var CallUrlResult = React.createClass({
propTypes: {
callUrl: React.PropTypes.string.isRequired,
retry: React.PropTypes.func.isRequired
},
handleButtonClick: function() {
this.props.retry();
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
return (
<PanelLayout summary={__("share_link_url")}>
<div className="invite">
<input type="url" value={this.props.callUrl} readOnly="true" />
<button onClick={this.handleButtonClick}
className="btn btn-success">{__("new_url")}</button>
</div>
</PanelLayout>
);
}
});
var CallUrlForm = React.createClass({
propTypes: {
client: React.PropTypes.object.isRequired,
notifier: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
disabled: true,
callUrl: false
};
},
retry: function() {
this.setState(this.getInitialState());
},
handleTextChange: function(event) {
this.setState({disabled: !event.currentTarget.value});
},
handleFormSubmit: function(event) {
event.preventDefault();
this.setState({pending: true});
this.props.client.requestCallUrl(
this.refs.caller.getDOMNode().value, this._onCallUrlReceived);
},
_onCallUrlReceived: function(err, callUrlData) {
var callUrl = false;
this.props.notifier.clear();
if (err) {
this.props.notifier.errorL10n("unable_retrieve_url");
} else {
callUrl = callUrlData.callUrl || callUrlData.call_url;
}
this.setState({pending: false, callUrl: callUrl});
},
render: function() {
// If we have a call url, render result
if (this.state.callUrl) {
return (
<CallUrlResult callUrl={this.state.callUrl} retry={this.retry}/>
);
}
// If we don't display the form
var cx = React.addons.classSet;
return (
<PanelLayout summary={__("get_link_to_share")}>
<form className="invite" onSubmit={this.handleFormSubmit}>
<input type="text" name="caller" ref="caller" required="required"
className={cx({'pending': this.state.pending})}
onChange={this.handleTextChange}
placeholder={__("call_identifier_textinput_placeholder")} />
<button type="submit" className="get-url btn btn-success"
disabled={this.state.disabled}>
{__("get_a_call_url")}
</button>
</form>
<ToSView />
</PanelLayout>
);
}
});
/**
* Panel view.
*/
var PanelView = React.createClass({
propTypes: {
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
render: function() {
return (
<div>
<CallUrlForm client={this.props.client}
notifier={this.props.notifier} />
<DoNotDisturb />
</div>
);
}
});
var PanelRouter = loop.desktopRouter.DesktopRouter.extend({
/**
* DOM document object.
* @type {HTMLDocument}
*/
document: undefined,
routes: {
"": "home"
},
initialize: function(options) {
options = options || {};
if (!options.document) {
throw new Error("missing required document");
}
this.document = options.document;
this._registerVisibilityChangeEvent();
this.on("panel:open panel:closed", this.reset, this);
},
/**
* Register the DOM visibility API event for the whole document, and trigger
* appropriate events accordingly:
*
* - `panel:opened` when the panel is open
* - `panel:closed` when the panel is closed
*
* @link http://www.w3.org/TR/page-visibility/
*/
_registerVisibilityChangeEvent: function() {
this.document.addEventListener("visibilitychange", function(event) {
this.trigger(event.currentTarget.hidden ? "panel:closed"
: "panel:open");
}.bind(this));
},
/**
* Default entry point.
*/
home: function() {
this.reset();
},
/**
* Resets this router to its initial state.
*/
reset: function() {
this._notifier.clear();
var client = new loop.Client({
baseServerUrl: navigator.mozLoop.serverUrl
});
this.loadReactComponent(<PanelView client={client}
notifier={this._notifier} />);
}
});
/**
* Panel initialisation.
*/
function init() {
// Do the initial L10n setup, we do this before anything
// else to ensure the L10n environment is setup correctly.
mozL10n.initialize(navigator.mozLoop);
router = new PanelRouter({
document: document,
notifier: new sharedViews.NotificationListView({el: "#messages"})
});
Backbone.history.start();
// Notify the window that we've finished initalization and initial layout
var evtObject = document.createEvent('Event');
evtObject.initEvent('loopPanelInitialized', true, false);
window.dispatchEvent(evtObject);
}
return {
init: init,
DoNotDisturb: DoNotDisturb,
CallUrlForm: CallUrlForm,
PanelView: PanelView,
PanelRouter: PanelRouter,
ToSView: ToSView
};
})(_, document.mozL10n);

View File

@ -15,7 +15,6 @@
<div id="main"></div>
<script type="text/javascript" src="loop/shared/libs/react-0.10.0.js"></script>
<script type="text/javascript" src="loop/libs/l10n.js"></script>
<script type="text/javascript" src="loop/shared/libs/jquery-2.1.0.js"></script>
<script type="text/javascript" src="loop/shared/libs/lodash-2.4.1.js"></script>

View File

@ -53,11 +53,8 @@ a {
margin: 0 0 1em 0;
}
p.dnd {
margin: 0 10px 10px 10px;
/* The panel won't increase its height when using a bottom margin, while it
works using a padding */
padding-bottom: 10px;
.share .action p.dnd {
margin-top: 1em;
}
.share .action input[type="text"],
@ -68,7 +65,6 @@ p.dnd {
font-size: .9em;
width: 65%;
padding: .5em;
margin-right: .35em;
}
.share .action input.pending {

View File

@ -18,7 +18,7 @@ loop.shared.router = (function(l10n) {
var BaseRouter = Backbone.Router.extend({
/**
* Active view.
* @type {Object}
* @type {loop.shared.views.BaseView}
*/
_activeView: undefined,
@ -51,38 +51,12 @@ loop.shared.router = (function(l10n) {
*
* @param {loop.shared.views.BaseView} view View.
*/
loadView: function(view) {
this.clearActiveView();
this._activeView = {type: "backbone", view: view.render().show()};
this.updateView(this._activeView.view.$el);
},
/**
* Renders a React component as current active view.
*
* @param {React} reactComponent React component.
*/
loadReactComponent: function(reactComponent) {
this.clearActiveView();
this._activeView = {
type: "react",
view: React.renderComponent(reactComponent,
document.querySelector("#main"))
};
},
/**
* Clears current active view.
*/
clearActiveView: function() {
if (!this._activeView) {
return;
}
if (this._activeView.type === "react") {
React.unmountComponentAtNode(document.querySelector("#main"));
} else {
this._activeView.view.remove();
loadView : function(view) {
if (this._activeView) {
this._activeView.remove();
}
this._activeView = view.render().show();
this.updateView(this._activeView.$el);
},
/**

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@ browser.jar:
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
content/browser/loop/shared/js/router.js (content/shared/js/router.js)
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
content/browser/loop/shared/libs/react-0.10.0.js (content/shared/libs/react-0.10.0.js)
content/browser/loop/shared/libs/lodash-2.4.1.js (content/shared/libs/lodash-2.4.1.js)
content/browser/loop/shared/libs/jquery-2.1.0.js (content/shared/libs/jquery-2.1.0.js)
content/browser/loop/shared/libs/backbone-1.1.2.js (content/shared/libs/backbone-1.1.2.js)

View File

@ -139,11 +139,8 @@ describe("loop.conversation", function() {
router.accept();
sinon.assert.calledOnce(conversation.initiate);
sinon.assert.calledWithMatch(conversation.initiate, {
client: {
mozLoop: navigator.mozLoop,
settings: {}
},
sinon.assert.calledWithExactly(conversation.initiate, {
baseServerUrl: "http://example.com",
outgoing: false
});
});

View File

@ -16,7 +16,6 @@
<div id="fixtures"></div>
<!-- libs -->
<script src="../../content/libs/l10n.js"></script>
<script src="../../content/shared/libs/react-0.10.0.js"></script>
<script src="../../content/shared/libs/jquery-2.1.0.js"></script>
<script src="../../content/shared/libs/lodash-2.4.1.js"></script>
<script src="../../content/shared/libs/backbone-1.1.2.js"></script>
@ -36,8 +35,8 @@
<script src="../../content/shared/js/router.js"></script>
<script src="../../content/shared/js/views.js"></script>
<script src="../../content/js/client.js"></script>
<script src="../../content/js/desktopRouter.js"></script>
<script src="../../content/js/conversation.js"></script>
<script src="../../content/js/desktopRouter.js"></script>
<script src="../../content/js/panel.js"></script>
<!-- Test scripts -->

View File

@ -5,12 +5,12 @@
/*global loop, sinon */
var expect = chai.expect;
var TestUtils = React.addons.TestUtils;
describe("loop.panel", function() {
"use strict";
var sandbox, notifier, fakeXHR, requests = [];
var sandbox, notifier, fakeXHR, requests = [], savedMozLoop,
fakeSeenToSPref = 0;
function createTestRouter(fakeDocument) {
return new loop.panel.PanelRouter({
@ -42,13 +42,18 @@ describe("loop.panel", function() {
return "http://example.com";
},
getStrings: function() {
return JSON.stringify({textContent: "fakeText"});
return "{}";
},
get locale() {
return "en-US";
},
setLoopCharPref: sandbox.stub(),
getLoopCharPref: sandbox.stub()
getLoopCharPref: function () {
if (fakeSeenToSPref === 0) {
return null;
}
return 'seen';
}
};
document.mozL10n.initialize(navigator.mozLoop);
@ -56,6 +61,7 @@ describe("loop.panel", function() {
afterEach(function() {
delete navigator.mozLoop;
$("#fixtures").empty();
sandbox.restore();
});
@ -84,7 +90,6 @@ describe("loop.panel", function() {
});
sandbox.stub(router, "loadView");
sandbox.stub(router, "loadReactComponent");
});
describe("#home", function() {
@ -107,233 +112,277 @@ describe("loop.panel", function() {
it("should load the home view", function() {
router.reset();
sinon.assert.calledOnce(router.loadReactComponent);
sinon.assert.calledWithExactly(router.loadReactComponent,
sinon.match(function(value) {
return React.addons.TestUtils.isComponentOfType(
value, loop.panel.PanelView);
}));
sinon.assert.calledOnce(router.loadView);
sinon.assert.calledWithExactly(router.loadView,
sinon.match.instanceOf(loop.panel.PanelView));
});
});
});
describe("Events", function() {
beforeEach(function() {
sandbox.stub(loop.panel.PanelRouter.prototype, "trigger");
});
it("should listen to document visibility changes", function() {
var fakeDocument = {
hidden: true,
addEventListener: sandbox.spy()
};
var router = createTestRouter(fakeDocument);
sinon.assert.calledOnce(fakeDocument.addEventListener);
sinon.assert.calledWith(fakeDocument.addEventListener,
"visibilitychange");
});
it("should trigger panel:open when the panel document is visible",
function() {
var router = createTestRouter({
hidden: false,
addEventListener: function(name, cb) {
cb({currentTarget: {hidden: false}});
}
});
sinon.assert.calledOnce(router.trigger);
sinon.assert.calledWithExactly(router.trigger, "panel:open");
});
it("should trigger panel:closed when the panel document is hidden",
function() {
var router = createTestRouter({
describe("Events", function() {
it("should listen to document visibility changes", function() {
var fakeDocument = {
hidden: true,
addEventListener: function(name, cb) {
cb({currentTarget: {hidden: true}});
}
addEventListener: sandbox.spy()
};
var router = createTestRouter(fakeDocument);
sinon.assert.calledOnce(fakeDocument.addEventListener);
sinon.assert.calledWith(fakeDocument.addEventListener,
"visibilitychange");
});
it("should trigger panel:open when the panel document is visible",
function(done) {
var router = createTestRouter({
hidden: false,
addEventListener: function(name, cb) {
setTimeout(function() {
cb({currentTarget: {hidden: false}});
}, 0);
}
});
router.once("panel:open", function() {
done();
});
});
sinon.assert.calledOnce(router.trigger);
sinon.assert.calledWithExactly(router.trigger, "panel:closed");
});
it("should trigger panel:closed when the panel document is hidden",
function(done) {
var router = createTestRouter({
addEventListener: function(name, cb) {
hidden: true,
setTimeout(function() {
cb({currentTarget: {hidden: true}});
}, 0);
}
});
router.once("panel:closed", function() {
done();
});
});
});
});
});
describe("loop.panel.DoNotDisturb", function() {
describe("loop.panel.DoNotDisturbView", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(loop.panel.DoNotDisturb());
$("#fixtures").append('<div id="dnd-view"></div>');
view = new loop.panel.DoNotDisturbView({el: $("#dnd-view")});
});
describe("Checkbox change event", function() {
describe("#toggle", function() {
beforeEach(function() {
navigator.mozLoop.doNotDisturb = false;
var checkbox = TestUtils.findRenderedDOMComponentWithTag(view, "input");
TestUtils.Simulate.change(checkbox);
});
it("should toggle the value of mozLoop.doNotDisturb", function() {
view.toggle();
expect(navigator.mozLoop.doNotDisturb).eql(true);
});
it("should update the DnD checkbox value", function() {
expect(view.getDOMNode().querySelector("input").checked).eql(true);
view.toggle();
expect(view.$("input").is(":checked")).eql(true);
});
});
describe("render", function() {
it("should check the dnd checkbox when dnd is enabled", function() {
navigator.mozLoop.doNotDisturb = false;
view.render();
expect(view.$("input").is(":checked")).eql(false);
});
it("should uncheck the dnd checkbox when dnd is disabled", function() {
navigator.mozLoop.doNotDisturb = true;
view.render();
expect(view.$("input").is(":checked")).eql(true);
});
});
});
describe("loop.panel.CallUrlForm", function() {
var fakeClient, callUrlData, view;
describe("loop.panel.PanelView", function() {
beforeEach(function() {
callUrlData = {
call_url: "http://call.invalid/",
expiresAt: 1000
};
fakeClient = {
requestCallUrl: function(_, cb) {
cb(null, callUrlData);
}
};
view = TestUtils.renderIntoDocument(loop.panel.CallUrlForm({
notifier: notifier,
client: fakeClient
}));
$("#fixtures").append('<div id="messages"></div><div id="main"></div>');
});
describe("#render", function() {
it("should render a ToSView", function() {
TestUtils.findRenderedComponentWithType(view, loop.panel.ToSView);
});
});
describe("Form submit event", function() {
function submitForm(callerValue) {
// fill caller field
TestUtils.Simulate.change(
TestUtils.findRenderedDOMComponentWithTag(view, "input"), {
target: {value: callerValue}
});
// submit form
TestUtils.Simulate.submit(
TestUtils.findRenderedDOMComponentWithTag(view, "form"));
}
describe("#getCallUrl", function() {
it("should reset all pending notifications", function() {
submitForm("foo");
var requestCallUrl = sandbox.stub(loop.Client.prototype,
"requestCallUrl");
var view = new loop.panel.PanelView({notifier: notifier}).render();
sinon.assert.calledOnce(notifier.clear, "clear");
view.getCallUrl({preventDefault: sandbox.spy()});
sinon.assert.calledOnce(view.notifier.clear, "clear");
});
it("should request a call url to the server", function() {
fakeClient.requestCallUrl = sandbox.stub();
var requestCallUrl = sandbox.stub(loop.Client.prototype,
"requestCallUrl");
var view = new loop.panel.PanelView({notifier: notifier});
sandbox.stub(view, "getNickname").returns("foo");
submitForm("foo");
view.getCallUrl({preventDefault: sandbox.spy()});
sinon.assert.calledOnce(fakeClient.requestCallUrl);
sinon.assert.calledWith(fakeClient.requestCallUrl, "foo");
sinon.assert.calledOnce(requestCallUrl);
sinon.assert.calledWith(requestCallUrl, "foo");
});
it("should set the call url form in a pending state", function() {
// Cancel requestCallUrl effect to keep the state pending
fakeClient.requestCallUrl = sandbox.stub();
var requestCallUrl = sandbox.stub(loop.Client.prototype,
"requestCallUrl");
sandbox.stub(loop.panel.PanelView.prototype, "setPending");
submitForm("foo");
var view = new loop.panel.PanelView({notifier: notifier});
expect(view.state.pending).eql(true);
});
view.getCallUrl({preventDefault: sandbox.spy()});
it("should update state with the call url received", function() {
submitForm("foo");
expect(view.state.pending).eql(false);
expect(view.state.callUrl).eql(callUrlData.call_url);
sinon.assert.calledOnce(view.setPending);
});
it("should clear the pending state when a response is received",
function() {
submitForm("foo");
sandbox.stub(loop.panel.PanelView.prototype,
"clearPending");
var requestCallUrl = sandbox.stub(
loop.Client.prototype, "requestCallUrl", function(_, cb) {
cb("fake error");
});
var view = new loop.panel.PanelView({notifier: notifier});
expect(view.state.pending).eql(false);
view.getCallUrl({preventDefault: sandbox.spy()});
sinon.assert.calledOnce(view.clearPending);
});
it("should update CallUrlResult with the call url", function() {
submitForm("foo");
var urlField = view.getDOMNode().querySelector("input[type='url']");
expect(urlField.value).eql(callUrlData.call_url);
});
it("should reset all pending notifications", function() {
submitForm("foo");
sinon.assert.calledOnce(view.props.notifier.clear);
});
it("should notify the user when the operation failed", function() {
fakeClient.requestCallUrl = function(_, cb) {
cb("fake error");
};
var requestCallUrl = sandbox.stub(
loop.Client.prototype, "requestCallUrl", function(_, cb) {
cb("fake error");
});
var view = new loop.panel.PanelView({notifier: notifier});
submitForm("foo");
view.getCallUrl({preventDefault: sandbox.spy()});
sinon.assert.calledOnce(notifier.errorL10n);
sinon.assert.calledWithExactly(notifier.errorL10n,
sinon.assert.calledOnce(view.notifier.errorL10n);
sinon.assert.calledWithExactly(view.notifier.errorL10n,
"unable_retrieve_url");
});
});
});
describe('loop.panel.ToSView', function() {
describe("#onCallUrlReceived", function() {
var callUrlData;
it("should set the value of the loop.seenToS preference to 'seen'",
function() {
TestUtils.renderIntoDocument(loop.panel.ToSView());
sinon.assert.calledOnce(navigator.mozLoop.setLoopCharPref);
sinon.assert.calledWithExactly(navigator.mozLoop.setLoopCharPref,
'seenToS', 'seen');
beforeEach(function() {
callUrlData = {
callUrl: "http://call.me/",
expiresAt: 1000
};
});
it("should not set the value of loop.seenToS when it's already set",
function() {
navigator.mozLoop.getLoopCharPref = function() {
return "seen";
};
it("should update the text field with the call url", function() {
var view = new loop.panel.PanelView({notifier: notifier});
view.render();
TestUtils.renderIntoDocument(loop.panel.ToSView());
view.onCallUrlReceived(callUrlData);
expect(view.$("#call-url").val()).eql("http://call.me/");
});
it("should reset all pending notifications", function() {
var view = new loop.panel.PanelView({notifier: notifier}).render();
view.onCallUrlReceived(callUrlData);
sinon.assert.calledOnce(view.notifier.clear);
});
});
describe("events", function() {
describe("goBack", function() {
it("should update the button state");
});
describe("changeButtonState", function() {
it("should do set the disabled state if there is no text");
it("should do set the enabled state if there is text");
});
});
describe("#render", function() {
it("should render a DoNotDisturbView", function() {
var renderDnD = sandbox.stub(loop.panel.DoNotDisturbView.prototype,
"render");
var view = new loop.panel.PanelView({notifier: notifier});
view.render();
sinon.assert.calledOnce(renderDnD);
});
it("should render a ToSView", function() {
var renderToS = sandbox.stub(loop.panel.ToSView.prototype, "render");
var view = new loop.panel.PanelView({notifier: notifier});
view.render();
sinon.assert.calledOnce(renderToS);
});
});
describe('loop.panel.ToSView', function() {
beforeEach(function() {
$('#fixtures').append('<div id="#tos-view"></div>');
});
// XXX Until it's possible to easily test creation of text,
// not doing so. As it stands, the magic in the L10nView
// class makes stubbing BaseView.render impractical.
it("should set the value of the loop.seenToS preference to 'seen'",
function() {
var ToSView = new loop.panel.ToSView({el: $("#tos-view")});
ToSView.render();
sinon.assert.calledOnce(navigator.mozLoop.setLoopCharPref);
sinon.assert.calledWithExactly(navigator.mozLoop.setLoopCharPref,
'seenToS', 'seen');
});
it("should render when the value of loop.seenToS is not set", function() {
var renderToS = sandbox.spy(loop.panel.ToSView.prototype, "render");
var ToSView = new loop.panel.ToSView({el: $('#tos-view')});
ToSView.render();
sinon.assert.calledOnce(renderToS);
});
it("should not render when the value of loop.seenToS is set to 'seen'",
function() {
var ToSView = new loop.panel.ToSView({el: $('#tos-view')});
fakeSeenToSPref = 1;
ToSView.render();
sinon.assert.notCalled(navigator.mozLoop.setLoopCharPref);
});
it("should render when the value of loop.seenToS is not set", function() {
var view = TestUtils.renderIntoDocument(loop.panel.ToSView());
TestUtils.findRenderedDOMComponentWithClass(view, "tos");
});
it("should not render when the value of loop.seenToS is set to 'seen'",
function(done) {
navigator.mozLoop.getLoopCharPref = function() {
return "seen";
};
try {
TestUtils.findRenderedDOMComponentWithClass(view, "tos");
} catch (err) {
done();
}
});
});
});

View File

@ -69,10 +69,7 @@ describe("loop.shared.router", function() {
it("should set the active view", function() {
router.loadView(view);
expect(router._activeView).eql({
type: "backbone",
view: view
});
expect(router._activeView).eql(view);
});
it("should load and render the passed view", function() {

View File

@ -10,7 +10,7 @@ do_not_disturb=Do not disturb
get_a_call_url=Get a call url
new_url=New url
call_identifier_textinput_placeholder=Identify this call
caller.placeholder=Identify this call
unable_retrieve_url=Sorry, we were unable to retrieve a call url.
@ -32,6 +32,6 @@ network_disconnected=The network connection terminated abruptly.
connection_error_see_console_notification=Call failed; see console for details.
## LOCALIZATION NOTE (legal_text_and_links): In this item, don't translate the
## part between {{..}}
legal_text_and_links=By using this product you agree to the <a \
target="_blank" href="{{terms_of_use_url}}">Terms of Use</a> and <a \
legal_text_and_links.innerHTML=By using this product you agree to the <a \
href="{{terms_of_use_url}}">Terms of Use</a> and <a \
href="{{privacy_notice_url}}">Privacy Notice</a>

View File

@ -92,6 +92,7 @@
<li><a href="about:license#gyp">gyp License</a></li>
<li><a href="about:license#halloc">halloc License</a></li>
<li><a href="about:license#harfbuzz">HarfBuzz License</a></li>
<li><a href="about:license#hawk-browser">Hawk Browser License</a></li>
<li><a href="about:license#icu">ICU License</a></li>
<li><a href="about:license#jpnic">Japan Network Information Center License</a></li>
<li><a href="about:license#jemalloc">jemalloc License</a></li>
@ -121,6 +122,7 @@
<li><a href="about:license#xdg">Red Hat xdg_user_dir_lookup License</a></li>
<li><a href="about:license#hunspell-ru">Russian Spellchecking Dictionary License</a></li>
<li><a href="about:license#sctp">SCTP Licenses</a></li>
<li><a href="about:license#sjcl">SJCL License</a></li>
<li><a href="about:license#skia">Skia License</a></li>
<li><a href="about:license#snappy">Snappy License</a></li>
<li><a href="about:license#sparkle">Sparkle License</a></li>
@ -2639,6 +2641,40 @@ PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
</pre>
<hr>
<h1><a id="hawk-browser"></a>Hawk Browser License</h1>
<p>This license applies to the file
<span class="path">browser/components/loop/content/shared/libs/hawk-browser-*.js</span>.</p>
<pre>
Copyright (c) 2012-2014, Eran Hammer.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Eran Hammer nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ERAN HAMMER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>
<hr>
<h1><a id="icu"></a>ICU License</h1>
@ -3879,6 +3915,47 @@ THE POSSIBILITY OF SUCH DAMAGE.
</pre>
<hr>
<h1><a id="sjcl"></a>SJCL License</h1>
<p>This license applies to the file
<span class="path">browser/components/loop/content/shared/libs/sjcl*.js</span>.</p>
<pre>
Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL &lt;COPYRIGHT HOLDER&gt;<!-- sic --> OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation
are those of the authors and should not be interpreted as representing
official policies, either expressed or implied, of the authors.
</pre>
<hr>
<h1><a id="skia"></a>Skia License</h1>