<commit_before>/* See license.txt for terms of usage */

/**
 * This file defines Core APIs for test drivers. The FBTest object is injected
 * into this scope by the Firebug test harness.
 */

(function() {

// ********************************************************************************************* //
// Constants

// XPCOM
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu["import"]("resource://fbtest/EventUtils.js");

// Backward compatibility
FBTest.Firebug = FBTest;
FBTestFirebug = FBTest;

// ********************************************************************************************* //
// Error Handling

/** @ignore */
window.onerror = function(errType, errURL, errLineNum)
{
    var path = window.location.pathname;
    var fileName = path.substr(path.lastIndexOf("/") + 1);
    var errorDesc = errType + " (" + errLineNum + ")" + " " + errURL;
    FBTest.sysout(fileName + " ERROR " + errorDesc);
    FBTest.ok(false, fileName + " ERROR " + errorDesc);
    FBTestFirebug.testDone();
    return false;
};

// ********************************************************************************************* //
// Test Driver Core API (direct access to FBTestApp)

/**
 * Verification method, prints result of a test. If the first "pass" parameter is "true"
 * the test passes, otherwise fails.
 *
 * @param {Boolean} pass Result of a test.
 * @param {String} msg A message to be displayed as a test results under the current test
 *      within the test console.
 */
this.ok = function(pass, msg)
{
    if (!pass)
        FBTest.sysout("FBTest **** FAILS **** " + msg);
    else
        FBTest.sysout("FBTest ok " + msg);

    FBTestApp.TestRunner.appendResult(new FBTestApp.TestResult(window, pass, msg));

    if (!pass)
        this.onFailure(msg);
    else
        FBTest.resetTimeout();

    return pass;
};

/**
 * Verification method. Compares expected and actual string (typically from the Firebug UI).
 * If "actual" and "expected" parameters are equal, the test passes, otherwise it fails.
 *
 * @param {String} expected Expected value
 * @param {String} actual Actual value
 * @param {String} msg A message to be displayed as a test result under the current test
 *      within the test console.
 * @param {String} shouldNotMatch Specifies whether expected and actual should not match
 */
this.compare = function(expected, actual, msg, shouldNotMatch)
{
    var result;
    if (expected instanceof RegExp)
    {
        result = actual ? actual.match(expected) : null;
        expected = expected ? expected.toString() : null;
    }
    else
    {
        // xxxHonza: TODO: lib/textSearch doesn't like '==='
        result = (expected == actual);
    }

    if (shouldNotMatch)
        result = !result;

    FBTest.sysout("compare "+(result?"passes":"**** FAILS ****")+" "+msg,
        {expected: expected, actual: actual});

    var shownMsg = msg;
    if (!result)
    {
        shownMsg += " (was: " + actual + ", expected" +
            (shouldNotMatch ? " otherwise" : ": " + expected) +
            (typeof actual === typeof expected ? ")" : " - different types)");
    }

    FBTestApp.TestRunner.appendResult(new FBTestApp.TestResult(window,
        result, shownMsg, expected, actual));

    if (result)
        FBTest.resetTimeout();
    else
        FBTest.onFailure(msg);

    return result;
};

/**
 * Logs an exception under the current test within the test console.
 *
 * @param {String} msg A message to be displayed under the current test within the test console.
 * @param {Exception} err An exception object.
 */
this.exception = function(msg, err)
{
    FBTestApp.TestRunner.appendResult(new FBTestApp.TestException(window, msg, err));
};

/**
 * Prints a message into test results (displayed under a test within test console).
 *
 * @param {String} msg A message to be displayed under the current test within the test console.
 */
this.progress = function(msg)
{
    FBTestApp.TestRunner.appendResult(new FBTestApp.TestResult(window, true, "progress: "+msg));
    FBTestApp.TestSummary.setMessage(msg);
    FBTest.sysout("FBTest progress: ------------- "+msg+" -------------");
    FBTest.resetTimeout();
};

/**
 * Executed by the framework at the beginning of each test.
 */
this.testStart = function(test)
{
    FBTest.sysout("FBTestFirebug.testStart; " + test.path + " - " + test.desc, test);
};

/**
 * Finishes current test and prints also an info message to the status bar. The method
 * performs clean up (closes all browser tabs opened as part of the test,
 * removes all breakpoints, etc.)
 *
 * The method is asynchronous and the test that executed it should not perform
 * any further actions.
 *
 * @param {String} [message] A custom message for tracing. If not provided default
 * message will be generated automatically.
 */
this.testDone = function(message)
{
    FBTest.sysout("FBTestFirebug.testDone; Cleaning...");

    // Clean up now so, annotations are cleared and Firebug is not activated for the
    // next activated tab that would coincidentally come from the same domain.
    this.setToKnownState(() =>
    {
        var test = FBTestApp.TestRunner.currentTest;

        // Make sure the current stack is gone.
        setTimeout(() =>
        {
            this.closeFirebug();
            this.cleanUpTestTabs();

            if (!message)
            {
                // Compose default message for the tracing
                var path = test.path;
                var index = path.lastIndexOf("/");
                message = path.substr(index + 1) + " DONE";
            }

            FBTest.progress(message);

            FBTest.sysout("FBTestFirebug.testDone; DONE");

            FBTestApp.TestRunner.testDone(false, test);
        });
    });
};

/**
 * Returns URL of a directory with test cases (HTML pages with a manual test implementation)
 */
this.getHTTPURLBase = function()
{
    // xxxHonza: should be set as a global in this scope.
    return FBTestApp.TestConsole.getHTTPURLBase();
};

/**
 * Returns URL of a directory with test driver files.
 */
this.getLocalURLBase = function()
{
    // xxxHonza: should be set as a global in this scope.
    if (/file:/.test(FBTestApp.TestRunner.currentTest.driverBaseURI))
        return FBTestApp.TestRunner.currentTest.driverBaseURI;

    return FBTestApp.TestConsole.chromeToUrl(FBTestApp.TestRunner.currentTest.driverBaseURI, true);
};

/**
 * Basic logging into the Firebug tracing console. All logs made through this function
 * appears only if 'TESTCASE' options is set.
 *
 * @param {String} text A message to log.
 * @param {Object} obj An object to log.
 */
this.sysout = function(text, obj)
{
    if (FBTrace.DBG_TESTCASE)
        FBTrace.sysout(text, obj);
};

/**
 * In some cases the test can take longer time to execute than it's expected (e.g. due to a slow
 * test server connection).
 *
 * Instead of changing the default timeout to another (bigger) - but still fixed value, the test
 * can regularly reset the timeout.
 *
 * This way the runner knows that the test is not frozen and is still doing something.
 */
this.resetTimeout = function()
{
    FBTestApp.TestRunner.setTestTimeout(window);
};

// ********************************************************************************************* //
// APIs used by test harness (direct access to FBTestApp)

/**
 * Called by the test harness framework in case of a failing test. If *Fail Halt* option
 * is set and *Chromebug* extension installed, the debugger will halt the test execution.
 *
 * @param {String} msg A message to be displayed under the current test within the test console.
 */
this.onFailure = function(msg)
{
    FBTestApp.TestConsole.notifyObservers(this, "fbtest", "onFailure");
};

/**
 * This function is automatically called before every test sequence.
 */
this.setToKnownState = function(callback)
{
    FBTest.sysout("FBTestFirebug setToKnownState");

    // xxxHonza: TODO
    // 1) cookies permissions are not reset
    // 2) Net panel filter is not reset (the preference is, but the UI isn't)

    var Firebug = FBTest.FirebugWindow.Firebug;

    // Console preview is hidden by default
    if (this.isConsolePreviewVisible())
        this.clickConsolePreviewButton();

    // Use default Firebug height and side panel width
    this.setBrowserWindowSize(1024, 768);
    this.setFirebugBarHeight(270);
    this.setSidePanelWidth(350);

    this.clearSearchField();

    this.clearCache();

    // First clear all breakpoints and then perform deactivation.
    this.removeAllBreakpoints(function()
    {
        // These should be done with button presses not API calls.
        Firebug.PanelActivation.toggleAll("off");
        Firebug.PanelActivation.toggleAll("none");
        Firebug.PanelActivation.clearAnnotations(true);

        if (Firebug.isDetached())
            Firebug.toggleDetachBar();

        // Reset all options that also clears the breakpoints storage.
        Firebug.resetAllOptions(false);

        callback();
    });

    // xxxHonza: xxxJJB how clear the persisted panel state?
};

// ********************************************************************************************* //
// Module Loader

this.getRequire = function()
{
    if (typeof FW.require !== "undefined")
        return FW.require;

    var fbMainFrame = FW.document.getElementById("fbMainFrame");
    return fbMainFrame.contentWindow.require;
};

// ********************************************************************************************* //
// Task List (replaces the single runTestSuite method.

this.TaskList = function()
{
    this.tasks = [];
};

this.TaskList.prototype =
{
    push: function()
    {
        var args = FW.FBL.cloneArray(arguments);
        args = FW.FBL.arrayInsert(args, 1, [window]);
        this.tasks.push(FW.FBL.bind.apply(this, args));
    },

    /**
     * Wrap a function that does not take a callback parameter and push it to the list.
     */
    wrapAndPush: function(func)
    {
        var args = Array.prototype.slice.call(arguments, 1);
        this.push(function(callback)
        {
            func.apply(FBTest, args);
            callback();
        });
    },

    run: function(callback, delay)
    {
        FBTest.runTestSuite(this.tasks, callback, delay);
    }
};

/**
 * Support for set of asynchronous actions within a FBTest.
 *
 * Example:
 * ~~
 *  // A suite of asynchronous tests.
 *  var testSuite = [];
 *  testSuite.push(function(callback) {
 *      // TODO: test implementation
 *      // Continue with other tests.
 *      callback();
 *  });
 *  testSuite.push(function(callback) {
 *      // TODO: test implementation
 *      // Continue with other tests.
 *      callback();
 *  });
 *  // Run entire suite.
 *  runTestSuite(testSuite, function() {
 *      FBTestFirebug.testDone("DONE");
 *  });
 * ~~
 * @param {Array} tests List of asynchronous functions to be executed in order.
 * @param {Function} callback A callback that is executed as soon
 *                   as all functions in the list are finished.
 * @param {Number} delay A delay between tasks [ms]
 */
this.runTestSuite = function(tests, callback, delay)
{
    delay = delay || 200;

    setTimeout(function()
    {
        var test = tests.shift();
        if (!test)
        {
            callback();
            return;
        }

        function runNext()
        {
            FBTestFirebug.runTestSuite(tests, callback, delay);
        }

        try
        {
            test.call(this, runNext);
        }
        catch (err)
        {
            FBTest.exception("runTestSuite", err);
        }
    }, delay);
};

// ********************************************************************************************* //
// Helper Shortcuts

try
{
    window.FW = FBTest.FirebugWindow;   // Set by test harness
    window.basePath = FBTest.getHTTPURLBase();
    window.baseLocalPath = FBTest.getLocalURLBase();
}
catch (e)
{
    FBTrace.sysout("FBTest; EXCEPTION " + e, e);
}

// ********************************************************************************************* //
}).apply(FBTest);
<commit_msg>Ignore the value of 'this' in FBTest.testDone<commit_after>/* See license.txt for terms of usage */

/**
 * This file defines Core APIs for test drivers. The FBTest object is injected
 * into this scope by the Firebug test harness.
 */

(function() {

// ********************************************************************************************* //
// Constants

// XPCOM
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu["import"]("resource://fbtest/EventUtils.js");

// Backward compatibility
FBTest.Firebug = FBTest;
FBTestFirebug = FBTest;

// ********************************************************************************************* //
// Error Handling

/** @ignore */
window.onerror = function(errType, errURL, errLineNum)
{
    var path = window.location.pathname;
    var fileName = path.substr(path.lastIndexOf("/") + 1);
    var errorDesc = errType + " (" + errLineNum + ")" + " " + errURL;
    FBTest.sysout(fileName + " ERROR " + errorDesc);
    FBTest.ok(false, fileName + " ERROR " + errorDesc);
    FBTestFirebug.testDone();
    return false;
};

// ********************************************************************************************* //
// Test Driver Core API (direct access to FBTestApp)

/**
 * Verification method, prints result of a test. If the first "pass" parameter is "true"
 * the test passes, otherwise fails.
 *
 * @param {Boolean} pass Result of a test.
 * @param {String} msg A message to be displayed as a test results under the current test
 *      within the test console.
 */
this.ok = function(pass, msg)
{
    if (!pass)
        FBTest.sysout("FBTest **** FAILS **** " + msg);
    else
        FBTest.sysout("FBTest ok " + msg);

    FBTestApp.TestRunner.appendResult(new FBTestApp.TestResult(window, pass, msg));

    if (!pass)
        this.onFailure(msg);
    else
        FBTest.resetTimeout();

    return pass;
};

/**
 * Verification method. Compares expected and actual string (typically from the Firebug UI).
 * If "actual" and "expected" parameters are equal, the test passes, otherwise it fails.
 *
 * @param {String} expected Expected value
 * @param {String} actual Actual value
 * @param {String} msg A message to be displayed as a test result under the current test
 *      within the test console.
 * @param {String} shouldNotMatch Specifies whether expected and actual should not match
 */
this.compare = function(expected, actual, msg, shouldNotMatch)
{
    var result;
    if (expected instanceof RegExp)
    {
        result = actual ? actual.match(expected) : null;
        expected = expected ? expected.toString() : null;
    }
    else
    {
        // xxxHonza: TODO: lib/textSearch doesn't like '==='
        result = (expected == actual);
    }

    if (shouldNotMatch)
        result = !result;

    FBTest.sysout("compare "+(result?"passes":"**** FAILS ****")+" "+msg,
        {expected: expected, actual: actual});

    var shownMsg = msg;
    if (!result)
    {
        shownMsg += " (was: " + actual + ", expected" +
            (shouldNotMatch ? " otherwise" : ": " + expected) +
            (typeof actual === typeof expected ? ")" : " - different types)");
    }

    FBTestApp.TestRunner.appendResult(new FBTestApp.TestResult(window,
        result, shownMsg, expected, actual));

    if (result)
        FBTest.resetTimeout();
    else
        FBTest.onFailure(msg);

    return result;
};

/**
 * Logs an exception under the current test within the test console.
 *
 * @param {String} msg A message to be displayed under the current test within the test console.
 * @param {Exception} err An exception object.
 */
this.exception = function(msg, err)
{
    FBTestApp.TestRunner.appendResult(new FBTestApp.TestException(window, msg, err));
};

/**
 * Prints a message into test results (displayed under a test within test console).
 *
 * @param {String} msg A message to be displayed under the current test within the test console.
 */
this.progress = function(msg)
{
    FBTestApp.TestRunner.appendResult(new FBTestApp.TestResult(window, true, "progress: "+msg));
    FBTestApp.TestSummary.setMessage(msg);
    FBTest.sysout("FBTest progress: ------------- "+msg+" -------------");
    FBTest.resetTimeout();
};

/**
 * Executed by the framework at the beginning of each test.
 */
this.testStart = function(test)
{
    FBTest.sysout("FBTestFirebug.testStart; " + test.path + " - " + test.desc, test);
};

/**
 * Finishes current test and prints also an info message to the status bar. The method
 * performs clean up (closes all browser tabs opened as part of the test,
 * removes all breakpoints, etc.)
 *
 * The method is asynchronous and the test that executed it should not perform
 * any further actions.
 *
 * @param {String} [message] A custom message for tracing. If not provided default
 * message will be generated automatically.
 */
this.testDone = function(message)
{
    FBTest.sysout("FBTestFirebug.testDone; Cleaning...");

    // Clean up now so, annotations are cleared and Firebug is not activated for the
    // next activated tab that would coincidentally come from the same domain.
    FBTest.setToKnownState(() =>
    {
        var test = FBTestApp.TestRunner.currentTest;

        // Make sure the current stack is gone.
        setTimeout(() =>
        {
            FBTest.closeFirebug();
            FBTest.cleanUpTestTabs();

            if (!message)
            {
                // Compose default message for the tracing
                var path = test.path;
                var index = path.lastIndexOf("/");
                message = path.substr(index + 1) + " DONE";
            }

            FBTest.progress(message);

            FBTest.sysout("FBTestFirebug.testDone; DONE");

            FBTestApp.TestRunner.testDone(false, test);
        });
    });
};

/**
 * Returns URL of a directory with test cases (HTML pages with a manual test implementation)
 */
this.getHTTPURLBase = function()
{
    // xxxHonza: should be set as a global in this scope.
    return FBTestApp.TestConsole.getHTTPURLBase();
};

/**
 * Returns URL of a directory with test driver files.
 */
this.getLocalURLBase = function()
{
    // xxxHonza: should be set as a global in this scope.
    if (/file:/.test(FBTestApp.TestRunner.currentTest.driverBaseURI))
        return FBTestApp.TestRunner.currentTest.driverBaseURI;

    return FBTestApp.TestConsole.chromeToUrl(FBTestApp.TestRunner.currentTest.driverBaseURI, true);
};

/**
 * Basic logging into the Firebug tracing console. All logs made through this function
 * appears only if 'TESTCASE' options is set.
 *
 * @param {String} text A message to log.
 * @param {Object} obj An object to log.
 */
this.sysout = function(text, obj)
{
    if (FBTrace.DBG_TESTCASE)
        FBTrace.sysout(text, obj);
};

/**
 * In some cases the test can take longer time to execute than it's expected (e.g. due to a slow
 * test server connection).
 *
 * Instead of changing the default timeout to another (bigger) - but still fixed value, the test
 * can regularly reset the timeout.
 *
 * This way the runner knows that the test is not frozen and is still doing something.
 */
this.resetTimeout = function()
{
    FBTestApp.TestRunner.setTestTimeout(window);
};

// ********************************************************************************************* //
// APIs used by test harness (direct access to FBTestApp)

/**
 * Called by the test harness framework in case of a failing test. If *Fail Halt* option
 * is set and *Chromebug* extension installed, the debugger will halt the test execution.
 *
 * @param {String} msg A message to be displayed under the current test within the test console.
 */
this.onFailure = function(msg)
{
    FBTestApp.TestConsole.notifyObservers(this, "fbtest", "onFailure");
};

/**
 * This function is automatically called before every test sequence.
 */
this.setToKnownState = function(callback)
{
    FBTest.sysout("FBTestFirebug setToKnownState");

    // xxxHonza: TODO
    // 1) cookies permissions are not reset
    // 2) Net panel filter is not reset (the preference is, but the UI isn't)

    var Firebug = FBTest.FirebugWindow.Firebug;

    // Console preview is hidden by default
    if (this.isConsolePreviewVisible())
        this.clickConsolePreviewButton();

    // Use default Firebug height and side panel width
    this.setBrowserWindowSize(1024, 768);
    this.setFirebugBarHeight(270);
    this.setSidePanelWidth(350);

    this.clearSearchField();

    this.clearCache();

    // First clear all breakpoints and then perform deactivation.
    this.removeAllBreakpoints(function()
    {
        // These should be done with button presses not API calls.
        Firebug.PanelActivation.toggleAll("off");
        Firebug.PanelActivation.toggleAll("none");
        Firebug.PanelActivation.clearAnnotations(true);

        if (Firebug.isDetached())
            Firebug.toggleDetachBar();

        // Reset all options that also clears the breakpoints storage.
        Firebug.resetAllOptions(false);

        callback();
    });

    // xxxHonza: xxxJJB how clear the persisted panel state?
};

// ********************************************************************************************* //
// Module Loader

this.getRequire = function()
{
    if (typeof FW.require !== "undefined")
        return FW.require;

    var fbMainFrame = FW.document.getElementById("fbMainFrame");
    return fbMainFrame.contentWindow.require;
};

// ********************************************************************************************* //
// Task List (replaces the single runTestSuite method.

this.TaskList = function()
{
    this.tasks = [];
};

this.TaskList.prototype =
{
    push: function()
    {
        var args = FW.FBL.cloneArray(arguments);
        args = FW.FBL.arrayInsert(args, 1, [window]);
        this.tasks.push(FW.FBL.bind.apply(this, args));
    },

    /**
     * Wrap a function that does not take a callback parameter and push it to the list.
     */
    wrapAndPush: function(func)
    {
        var args = Array.prototype.slice.call(arguments, 1);
        this.push(function(callback)
        {
            func.apply(FBTest, args);
            callback();
        });
    },

    run: function(callback, delay)
    {
        FBTest.runTestSuite(this.tasks, callback, delay);
    }
};

/**
 * Support for set of asynchronous actions within a FBTest.
 *
 * Example:
 * ~~
 *  // A suite of asynchronous tests.
 *  var testSuite = [];
 *  testSuite.push(function(callback) {
 *      // TODO: test implementation
 *      // Continue with other tests.
 *      callback();
 *  });
 *  testSuite.push(function(callback) {
 *      // TODO: test implementation
 *      // Continue with other tests.
 *      callback();
 *  });
 *  // Run entire suite.
 *  runTestSuite(testSuite, function() {
 *      FBTestFirebug.testDone("DONE");
 *  });
 * ~~
 * @param {Array} tests List of asynchronous functions to be executed in order.
 * @param {Function} callback A callback that is executed as soon
 *                   as all functions in the list are finished.
 * @param {Number} delay A delay between tasks [ms]
 */
this.runTestSuite = function(tests, callback, delay)
{
    delay = delay || 200;

    setTimeout(function()
    {
        var test = tests.shift();
        if (!test)
        {
            callback();
            return;
        }

        function runNext()
        {
            FBTestFirebug.runTestSuite(tests, callback, delay);
        }

        try
        {
            test.call(this, runNext);
        }
        catch (err)
        {
            FBTest.exception("runTestSuite", err);
        }
    }, delay);
};

// ********************************************************************************************* //
// Helper Shortcuts

try
{
    window.FW = FBTest.FirebugWindow;   // Set by test harness
    window.basePath = FBTest.getHTTPURLBase();
    window.baseLocalPath = FBTest.getLocalURLBase();
}
catch (e)
{
    FBTrace.sysout("FBTest; EXCEPTION " + e, e);
}

// ********************************************************************************************* //
}).apply(FBTest);
<|endoftext|>


<commit_before>/* global $, APP, config, Strophe */

var logger = require("jitsi-meet-logger").getLogger(__filename);
var EventEmitter = require("events");
var Pako = require("pako");
var RTCEvents = require("../../service/RTC/RTCEvents");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var JitsiConnectionErrors = require("../../JitsiConnectionErrors");
var JitsiConnectionEvents = require("../../JitsiConnectionEvents");
var RTC = require("../RTC/RTC");
var RTCBrowserType = require("../RTC/RTCBrowserType");

var authenticatedUser = false;

function createConnection(bosh, token) {
    bosh = bosh || '/http-bind';

    // Append token as URL param
    if (token) {
        bosh += (bosh.indexOf('?') == -1 ? '?' : '&') + 'token=' + token;
    }

    return new Strophe.Connection(bosh);
};

//!!!!!!!!!! FIXME: ...
function initStrophePlugins(XMPP) {
    require("./strophe.emuc")(XMPP);
    require("./strophe.jingle")(XMPP, XMPP.eventEmitter);
//    require("./strophe.moderate")(XMPP, eventEmitter);
    require("./strophe.util")();
    require("./strophe.ping")(XMPP, XMPP.eventEmitter);
    require("./strophe.rayo")();
    require("./strophe.logger")();
}

function XMPP(options, token) {
    this.eventEmitter = new EventEmitter();
    this.connection = null;
    this.disconnectInProgress = false;
    this.connectionTimes = {};
    this.forceMuted = false;
    this.options = options;
    initStrophePlugins(this);

    this.connection = createConnection(options.bosh, token);

    // Initialize features advertised in disco-info
    this.initFeaturesList();

    // Setup a disconnect on unload as a way to facilitate API consumers. It
    // sounds like they would want that. A problem for them though may be if
    // they wanted to utilize the connected connection in an unload handler of
    // their own. However, it should be fairly easy for them to do that by
    // registering their unload handler before us.
    $(window).on('beforeunload unload', this.disconnect.bind(this));
}

/**
 * Initializes the list of feature advertised through the disco-info mechanism
 */
XMPP.prototype.initFeaturesList = function () {
    var disco = this.connection.disco;
    if (disco) {
        // http://xmpp.org/extensions/xep-0167.html#support
        // http://xmpp.org/extensions/xep-0176.html#support
        disco.addFeature('urn:xmpp:jingle:1');
        disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
        disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
        disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
        disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
        disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
        disco.addFeature('urn:xmpp:jingle:apps:rtp:video');

        if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
            || RTCBrowserType.isTemasysPluginUsed()) {
            disco.addFeature('urn:ietf:rfc:4588');
        }

        // this is dealt with by SDP O/A so we don't need to announce this
        //disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
        //disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294

        disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
        disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle

        //disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc

        // Enable Lipsync ?
        if (this.options.enableLipSync && RTCBrowserType.isChrome()) {
            logger.info("Lip-sync enabled !");
            this.connection.disco.addFeature('http://jitsi.org/meet/lipsync');
        }
    }
};

XMPP.prototype.getConnection = function () { return this.connection; };

/**
 * Receive connection status changes and handles them.
 * @password {string} the password passed in connect method
 * @status the connection status
 * @msg message
 */
XMPP.prototype.connectionHandler = function (password, status, msg) {
    var now = window.performance.now();
    this.connectionTimes[Strophe.getStatusString(status).toLowerCase()] = now;
    logger.log("(TIME) Strophe " + Strophe.getStatusString(status) +
        (msg ? "[" + msg + "]" : "") + ":\t", now);
    if (status === Strophe.Status.CONNECTED ||
        status === Strophe.Status.ATTACHED) {
        if (this.options.useStunTurn) {
            this.connection.jingle.getStunAndTurnCredentials();
        }

        logger.info("My Jabber ID: " + this.connection.jid);

        // Schedule ping ?
        var pingJid = this.connection.domain;
        this.connection.ping.hasPingSupport(
            pingJid,
            function (hasPing) {
                if (hasPing)
                    this.connection.ping.startInterval(pingJid);
                else
                    logger.warn("Ping NOT supported by " + pingJid);
            }.bind(this));

        if (password)
            authenticatedUser = true;
        if (this.connection && this.connection.connected &&
            Strophe.getResourceFromJid(this.connection.jid)) {
            // .connected is true while connecting?
//                this.connection.send($pres());
            this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_ESTABLISHED,
                    Strophe.getResourceFromJid(this.connection.jid));
        }
    } else if (status === Strophe.Status.CONNFAIL) {
        if (msg === 'x-strophe-bad-non-anon-jid') {
            this.anonymousConnectionFailed = true;
        } else {
            this.connectionFailed = true;
        }
        this.lastErrorMsg = msg;
    } else if (status === Strophe.Status.DISCONNECTED) {
        // Stop ping interval
        this.connection.ping.stopInterval();
        this.disconnectInProgress = false;
        if (this.anonymousConnectionFailed) {
            // prompt user for username and password
            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
                JitsiConnectionErrors.PASSWORD_REQUIRED);
        } else if(this.connectionFailed) {
            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
                JitsiConnectionErrors.OTHER_ERROR,
                msg ? msg : this.lastErrorMsg);
        } else {
            this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_DISCONNECTED,
                    msg ? msg : this.lastErrorMsg);
        }
    } else if (status === Strophe.Status.AUTHFAIL) {
        // wrong password or username, prompt user
        this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
            JitsiConnectionErrors.PASSWORD_REQUIRED);

    }
}

XMPP.prototype._connect = function (jid, password) {
    // connection.connect() starts the connection process.
    //
    // As the connection process proceeds, the user supplied callback will
    // be triggered multiple times with status updates. The callback should
    // take two arguments - the status code and the error condition.
    //
    // The status code will be one of the values in the Strophe.Status
    // constants. The error condition will be one of the conditions defined
    // in RFC 3920 or the condition ‘strophe-parsererror’.
    //
    // The Parameters wait, hold and route are optional and only relevant
    // for BOSH connections. Please see XEP 124 for a more detailed
    // explanation of the optional parameters.
    //
    // Connection status constants for use by the connection handler
    // callback.
    //
    //  Status.ERROR - An error has occurred (websockets specific)
    //  Status.CONNECTING - The connection is currently being made
    //  Status.CONNFAIL - The connection attempt failed
    //  Status.AUTHENTICATING - The connection is authenticating
    //  Status.AUTHFAIL - The authentication attempt failed
    //  Status.CONNECTED - The connection has succeeded
    //  Status.DISCONNECTED - The connection has been terminated
    //  Status.DISCONNECTING - The connection is currently being terminated
    //  Status.ATTACHED - The connection has been attached

    this.anonymousConnectionFailed = false;
    this.connectionFailed = false;
    this.lastErrorMsg = undefined;
    this.connection.connect(jid, password,
        this.connectionHandler.bind(this, password));
}

/**
 * Attach to existing connection. Can be used for optimizations. For example:
 * if the connection is created on the server we can attach to it and start
 * using it.
 *
 * @param options {object} connecting options - rid, sid, jid and password.
 */
 XMPP.prototype.attach = function (options) {
    var now = this.connectionTimes["attaching"] = window.performance.now();
    logger.log("(TIME) Strophe Attaching\t:" + now);
    this.connection.attach(options.jid, options.sid, parseInt(options.rid,10)+1,
        this.connectionHandler.bind(this, options.password));
}

XMPP.prototype.connect = function (jid, password) {
    if (!jid) {
        var configDomain
            = this.options.hosts.anonymousdomain || this.options.hosts.domain;
        // Force authenticated domain if room is appended with '?login=true'
        if (this.options.hosts.anonymousdomain
                && window.location.search.indexOf("login=true") !== -1) {
            configDomain = this.options.hosts.domain;
        }
        jid = configDomain || window.location.hostname;
    }
    return this._connect(jid, password);
};

XMPP.prototype.createRoom = function (roomName, options, settings) {
    var roomjid = roomName  + '@' + this.options.hosts.muc;

    if (options.useNicks) {
        if (options.nick) {
            roomjid += '/' + options.nick;
        } else {
            roomjid += '/' + Strophe.getNodeFromJid(this.connection.jid);
        }
    } else {
        var tmpJid = Strophe.getNodeFromJid(this.connection.jid);

        if (!authenticatedUser)
            tmpJid = tmpJid.substr(0, 8);
        roomjid += '/' + tmpJid;
    }

    return this.connection.emuc.createRoom(roomjid, null, options, settings);
}

XMPP.prototype.addListener = function(type, listener) {
    this.eventEmitter.on(type, listener);
};

XMPP.prototype.removeListener = function (type, listener) {
    this.eventEmitter.removeListener(type, listener);
};

/**
 * Sends 'data' as a log message to the focus. Returns true iff a message
 * was sent.
 * @param data
 * @returns {boolean} true iff a message was sent.
 */
XMPP.prototype.sendLogs = function (data) {
    if (!this.connection.emuc.focusMucJid)
        return false;

    var deflate = true;

    var content = JSON.stringify(data);
    if (deflate) {
        content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
    }
    content = Base64.encode(content);
    // XEP-0337-ish
    var message = $msg({to: this.connection.emuc.focusMucJid, type: 'normal'});
    message.c('log', {xmlns: 'urn:xmpp:eventlog', id: 'PeerConnectionStats'});
    message.c('message').t(content).up();
    if (deflate) {
        message.c('tag', {name: "deflated", value: "true"}).up();
    }
    message.up();

    this.connection.send(message);
    return true;
};

// Gets the logs from strophe.jingle.
XMPP.prototype.getJingleLog = function () {
    return this.connection.jingle ? this.connection.jingle.getLog() : {};
};

// Gets the logs from strophe.
XMPP.prototype.getXmppLog = function () {
    return this.connection.logger ? this.connection.logger.log : null;
};

XMPP.prototype.dial = function (to, from, roomName,roomPass) {
    this.connection.rayo.dial(to, from, roomName,roomPass);
};

XMPP.prototype.setMute = function (jid, mute) {
    this.connection.moderate.setMute(jid, mute);
};

XMPP.prototype.eject = function (jid) {
    this.connection.moderate.eject(jid);
};

XMPP.prototype.getSessions = function () {
    return this.connection.jingle.sessions;
};

/**
 * Disconnects this from the XMPP server (if this is connected).
 *
 * @param ev optionally, the event which triggered the necessity to disconnect
 * from the XMPP server (e.g. beforeunload, unload)
 */
XMPP.prototype.disconnect = function (ev) {
    if (this.disconnectInProgress
            || !this.connection
            || !this.connection.connected) {
        this.eventEmitter.emit(JitsiConnectionEvents.WRONG_STATE);
        return;
    }

    this.disconnectInProgress = true;

    // XXX Strophe is asynchronously sending by default. Unfortunately, that
    // means that there may not be enough time to send an unavailable presence
    // or disconnect at all. Switching Strophe to synchronous sending is not
    // much of an option because it may lead to a noticeable delay in navigating
    // away from the current location. As a compromise, we will try to increase
    // the chances of sending an unavailable presence and/or disconecting within
    // the short time span that we have upon unloading by invoking flush() on
    // the connection. We flush() once before disconnect() in order to attemtp
    // to have its unavailable presence at the top of the send queue. We flush()
    // once more after disconnect() in order to attempt to have its unavailable
    // presence sent as soon as possible.
    this.connection.flush();

    if (ev !== null && typeof ev !== 'undefined') {
        var evType = ev.type;

        if (evType == 'beforeunload' || evType == 'unload') {
            // XXX Whatever we said above, synchronous sending is the best
            // (known) way to properly disconnect from the XMPP server.
            // Consequently, it may be fine to have the source code and comment
            // it in or out depending on whether we want to run with it for some
            // time.
            this.connection.options.sync = true;
        }
    }

    this.connection.disconnect();

    if (this.connection.options.sync !== true) {
        this.connection.flush();
    }
};

module.exports = XMPP;
<commit_msg>Append random hex string to MUC nickname with XMPP login<commit_after>/* global $, APP, config, Strophe */

var logger = require("jitsi-meet-logger").getLogger(__filename);
var EventEmitter = require("events");
var Pako = require("pako");
var RandomUtil = require("../util/RandomUtil");
var RTCEvents = require("../../service/RTC/RTCEvents");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var JitsiConnectionErrors = require("../../JitsiConnectionErrors");
var JitsiConnectionEvents = require("../../JitsiConnectionEvents");
var RTC = require("../RTC/RTC");
var RTCBrowserType = require("../RTC/RTCBrowserType");

var authenticatedUser = false;

function createConnection(bosh, token) {
    bosh = bosh || '/http-bind';

    // Append token as URL param
    if (token) {
        bosh += (bosh.indexOf('?') == -1 ? '?' : '&') + 'token=' + token;
    }

    return new Strophe.Connection(bosh);
};

//!!!!!!!!!! FIXME: ...
function initStrophePlugins(XMPP) {
    require("./strophe.emuc")(XMPP);
    require("./strophe.jingle")(XMPP, XMPP.eventEmitter);
//    require("./strophe.moderate")(XMPP, eventEmitter);
    require("./strophe.util")();
    require("./strophe.ping")(XMPP, XMPP.eventEmitter);
    require("./strophe.rayo")();
    require("./strophe.logger")();
}

function XMPP(options, token) {
    this.eventEmitter = new EventEmitter();
    this.connection = null;
    this.disconnectInProgress = false;
    this.connectionTimes = {};
    this.forceMuted = false;
    this.options = options;
    initStrophePlugins(this);

    this.connection = createConnection(options.bosh, token);

    // Initialize features advertised in disco-info
    this.initFeaturesList();

    // Setup a disconnect on unload as a way to facilitate API consumers. It
    // sounds like they would want that. A problem for them though may be if
    // they wanted to utilize the connected connection in an unload handler of
    // their own. However, it should be fairly easy for them to do that by
    // registering their unload handler before us.
    $(window).on('beforeunload unload', this.disconnect.bind(this));
}

/**
 * Initializes the list of feature advertised through the disco-info mechanism
 */
XMPP.prototype.initFeaturesList = function () {
    var disco = this.connection.disco;
    if (disco) {
        // http://xmpp.org/extensions/xep-0167.html#support
        // http://xmpp.org/extensions/xep-0176.html#support
        disco.addFeature('urn:xmpp:jingle:1');
        disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
        disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
        disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
        disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
        disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
        disco.addFeature('urn:xmpp:jingle:apps:rtp:video');

        if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
            || RTCBrowserType.isTemasysPluginUsed()) {
            disco.addFeature('urn:ietf:rfc:4588');
        }

        // this is dealt with by SDP O/A so we don't need to announce this
        //disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
        //disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294

        disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
        disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle

        //disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc

        // Enable Lipsync ?
        if (this.options.enableLipSync && RTCBrowserType.isChrome()) {
            logger.info("Lip-sync enabled !");
            this.connection.disco.addFeature('http://jitsi.org/meet/lipsync');
        }
    }
};

XMPP.prototype.getConnection = function () { return this.connection; };

/**
 * Receive connection status changes and handles them.
 * @password {string} the password passed in connect method
 * @status the connection status
 * @msg message
 */
XMPP.prototype.connectionHandler = function (password, status, msg) {
    var now = window.performance.now();
    this.connectionTimes[Strophe.getStatusString(status).toLowerCase()] = now;
    logger.log("(TIME) Strophe " + Strophe.getStatusString(status) +
        (msg ? "[" + msg + "]" : "") + ":\t", now);
    if (status === Strophe.Status.CONNECTED ||
        status === Strophe.Status.ATTACHED) {
        if (this.options.useStunTurn) {
            this.connection.jingle.getStunAndTurnCredentials();
        }

        logger.info("My Jabber ID: " + this.connection.jid);

        // Schedule ping ?
        var pingJid = this.connection.domain;
        this.connection.ping.hasPingSupport(
            pingJid,
            function (hasPing) {
                if (hasPing)
                    this.connection.ping.startInterval(pingJid);
                else
                    logger.warn("Ping NOT supported by " + pingJid);
            }.bind(this));

        if (password)
            authenticatedUser = true;
        if (this.connection && this.connection.connected &&
            Strophe.getResourceFromJid(this.connection.jid)) {
            // .connected is true while connecting?
//                this.connection.send($pres());
            this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_ESTABLISHED,
                    Strophe.getResourceFromJid(this.connection.jid));
        }
    } else if (status === Strophe.Status.CONNFAIL) {
        if (msg === 'x-strophe-bad-non-anon-jid') {
            this.anonymousConnectionFailed = true;
        } else {
            this.connectionFailed = true;
        }
        this.lastErrorMsg = msg;
    } else if (status === Strophe.Status.DISCONNECTED) {
        // Stop ping interval
        this.connection.ping.stopInterval();
        this.disconnectInProgress = false;
        if (this.anonymousConnectionFailed) {
            // prompt user for username and password
            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
                JitsiConnectionErrors.PASSWORD_REQUIRED);
        } else if(this.connectionFailed) {
            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
                JitsiConnectionErrors.OTHER_ERROR,
                msg ? msg : this.lastErrorMsg);
        } else {
            this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_DISCONNECTED,
                    msg ? msg : this.lastErrorMsg);
        }
    } else if (status === Strophe.Status.AUTHFAIL) {
        // wrong password or username, prompt user
        this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
            JitsiConnectionErrors.PASSWORD_REQUIRED);

    }
}

XMPP.prototype._connect = function (jid, password) {
    // connection.connect() starts the connection process.
    //
    // As the connection process proceeds, the user supplied callback will
    // be triggered multiple times with status updates. The callback should
    // take two arguments - the status code and the error condition.
    //
    // The status code will be one of the values in the Strophe.Status
    // constants. The error condition will be one of the conditions defined
    // in RFC 3920 or the condition ‘strophe-parsererror’.
    //
    // The Parameters wait, hold and route are optional and only relevant
    // for BOSH connections. Please see XEP 124 for a more detailed
    // explanation of the optional parameters.
    //
    // Connection status constants for use by the connection handler
    // callback.
    //
    //  Status.ERROR - An error has occurred (websockets specific)
    //  Status.CONNECTING - The connection is currently being made
    //  Status.CONNFAIL - The connection attempt failed
    //  Status.AUTHENTICATING - The connection is authenticating
    //  Status.AUTHFAIL - The authentication attempt failed
    //  Status.CONNECTED - The connection has succeeded
    //  Status.DISCONNECTED - The connection has been terminated
    //  Status.DISCONNECTING - The connection is currently being terminated
    //  Status.ATTACHED - The connection has been attached

    this.anonymousConnectionFailed = false;
    this.connectionFailed = false;
    this.lastErrorMsg = undefined;
    this.connection.connect(jid, password,
        this.connectionHandler.bind(this, password));
}

/**
 * Attach to existing connection. Can be used for optimizations. For example:
 * if the connection is created on the server we can attach to it and start
 * using it.
 *
 * @param options {object} connecting options - rid, sid, jid and password.
 */
 XMPP.prototype.attach = function (options) {
    var now = this.connectionTimes["attaching"] = window.performance.now();
    logger.log("(TIME) Strophe Attaching\t:" + now);
    this.connection.attach(options.jid, options.sid, parseInt(options.rid,10)+1,
        this.connectionHandler.bind(this, options.password));
}

XMPP.prototype.connect = function (jid, password) {
    if (!jid) {
        var configDomain
            = this.options.hosts.anonymousdomain || this.options.hosts.domain;
        // Force authenticated domain if room is appended with '?login=true'
        if (this.options.hosts.anonymousdomain
                && window.location.search.indexOf("login=true") !== -1) {
            configDomain = this.options.hosts.domain;
        }
        jid = configDomain || window.location.hostname;
    }
    return this._connect(jid, password);
};

XMPP.prototype.createRoom = function (roomName, options, settings) {
    var roomjid = roomName  + '@' + this.options.hosts.muc;

    if (options.useNicks) {
        if (options.nick) {
            roomjid += '/' + options.nick;
        } else {
            roomjid += '/' + Strophe.getNodeFromJid(this.connection.jid);
        }
    } else {
        var tmpJid = Strophe.getNodeFromJid(this.connection.jid);

        if (!authenticatedUser)
            tmpJid = tmpJid.substr(0, 8);
        else
            tmpJid += "-" + RandomUtil.randomHexString(6);

        roomjid += '/' + tmpJid;
    }

    return this.connection.emuc.createRoom(roomjid, null, options, settings);
}

XMPP.prototype.addListener = function(type, listener) {
    this.eventEmitter.on(type, listener);
};

XMPP.prototype.removeListener = function (type, listener) {
    this.eventEmitter.removeListener(type, listener);
};

/**
 * Sends 'data' as a log message to the focus. Returns true iff a message
 * was sent.
 * @param data
 * @returns {boolean} true iff a message was sent.
 */
XMPP.prototype.sendLogs = function (data) {
    if (!this.connection.emuc.focusMucJid)
        return false;

    var deflate = true;

    var content = JSON.stringify(data);
    if (deflate) {
        content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
    }
    content = Base64.encode(content);
    // XEP-0337-ish
    var message = $msg({to: this.connection.emuc.focusMucJid, type: 'normal'});
    message.c('log', {xmlns: 'urn:xmpp:eventlog', id: 'PeerConnectionStats'});
    message.c('message').t(content).up();
    if (deflate) {
        message.c('tag', {name: "deflated", value: "true"}).up();
    }
    message.up();

    this.connection.send(message);
    return true;
};

// Gets the logs from strophe.jingle.
XMPP.prototype.getJingleLog = function () {
    return this.connection.jingle ? this.connection.jingle.getLog() : {};
};

// Gets the logs from strophe.
XMPP.prototype.getXmppLog = function () {
    return this.connection.logger ? this.connection.logger.log : null;
};

XMPP.prototype.dial = function (to, from, roomName,roomPass) {
    this.connection.rayo.dial(to, from, roomName,roomPass);
};

XMPP.prototype.setMute = function (jid, mute) {
    this.connection.moderate.setMute(jid, mute);
};

XMPP.prototype.eject = function (jid) {
    this.connection.moderate.eject(jid);
};

XMPP.prototype.getSessions = function () {
    return this.connection.jingle.sessions;
};

/**
 * Disconnects this from the XMPP server (if this is connected).
 *
 * @param ev optionally, the event which triggered the necessity to disconnect
 * from the XMPP server (e.g. beforeunload, unload)
 */
XMPP.prototype.disconnect = function (ev) {
    if (this.disconnectInProgress
            || !this.connection
            || !this.connection.connected) {
        this.eventEmitter.emit(JitsiConnectionEvents.WRONG_STATE);
        return;
    }

    this.disconnectInProgress = true;

    // XXX Strophe is asynchronously sending by default. Unfortunately, that
    // means that there may not be enough time to send an unavailable presence
    // or disconnect at all. Switching Strophe to synchronous sending is not
    // much of an option because it may lead to a noticeable delay in navigating
    // away from the current location. As a compromise, we will try to increase
    // the chances of sending an unavailable presence and/or disconecting within
    // the short time span that we have upon unloading by invoking flush() on
    // the connection. We flush() once before disconnect() in order to attemtp
    // to have its unavailable presence at the top of the send queue. We flush()
    // once more after disconnect() in order to attempt to have its unavailable
    // presence sent as soon as possible.
    this.connection.flush();

    if (ev !== null && typeof ev !== 'undefined') {
        var evType = ev.type;

        if (evType == 'beforeunload' || evType == 'unload') {
            // XXX Whatever we said above, synchronous sending is the best
            // (known) way to properly disconnect from the XMPP server.
            // Consequently, it may be fine to have the source code and comment
            // it in or out depending on whether we want to run with it for some
            // time.
            this.connection.options.sync = true;
        }
    }

    this.connection.disconnect();

    if (this.connection.options.sync !== true) {
        this.connection.flush();
    }
};

module.exports = XMPP;
<|endoftext|>


<commit_before>import {
  closeMessage,
  fetchRecordTypes,
  fetchAttributeTypes,
  default as uiReducer
} from 'store/uiReducer';
import _ from 'lodash';

describe('(Redux Module) UI', () => {
  describe('(Reducer) uiReducer', () => {
    let _initialState;
    beforeEach(() => {
      _initialState = {
        isFetching: false,
        recordTypes: {},
        attributeTypes: {},
        message: {
          active: false,
          text: '',
          success: false
        }
      };
    });
    it('Should be a function.', () => {
      expect(uiReducer).to.be.a('function');
    });

    it('Should initialize with a correct state', () => {
      expect(uiReducer(undefined, {})).to.deep.equal(_initialState);
    });

    it('Should return the previous state if an action was not matched.', () => {
      let state = uiReducer(undefined, {});
      expect(state).to.deep.equal(_initialState);
      state = uiReducer(state, {
        type: 'DOESNOTACTUALLYEXISTLOL'
      });
      expect(state).to.deep.equal(_initialState);
      state.message.active = true;                  // TODO: Improve this
      expect(state.message.active).to.equal(true);
      state = uiReducer(state, closeMessage());
      expect(state.message.active).to.equal(false);
      state = uiReducer(state, {
        type: 'DOESNOTACTUALLYEXISTLOL'
      });
      expect(state.message.active).to.equal(false);
    });
  });

  describe('(Action Creator) fetchRecordTypes', () => {
    let _dispatchSpy;
    let _globalState;

    beforeEach(() => {
      _globalState = {
        ui: uiReducer(undefined, {})
      };
      _dispatchSpy = sinon.spy((action) => {
        _globalState = {
          ..._globalState,
          ui: uiReducer(_globalState.home, action)
        };
      });
    });

    it('Should fetch record types', () => {
      expect(_.keys(_globalState.ui.recordTypes).length).to.equal(0);
      return setTimeout(fetchRecordTypes()(_dispatchSpy)
        .then(() => {
          _dispatchSpy.should.have.been.calledOnce;
          expect(_.keys(_globalState.ui.recordTypes).length).to.be.greaterThan(0);
        }), 15000);
    });
  });

  describe('(Action Creator) fetchAttributeTypes', () => {
    let _dispatchSpy;
    let _globalState;

    beforeEach(() => {
      _globalState = {
        ui: uiReducer(undefined, {})
      };
      _dispatchSpy = sinon.spy((action) => {
        _globalState = {
          ..._globalState,
          ui: uiReducer(_globalState.ui, action)
        };
      });
    });

    it('Should fetch validation rules and attribute types', () => {
      expect(_.keys(_globalState.ui.attributeTypes).length).to.equal(0);
      return setTimeout(fetchAttributeTypes()(_dispatchSpy)
        .then(() => {
          _dispatchSpy.should.have.been.calledOnce;
          expect(_.keys(_globalState.ui.attributeTypes).length).to.be.greaterThan(0);
        }), 15000);
    });
  });
});
<commit_msg>Remove redundand tests<commit_after>import {
  closeMessage,
  fetchAttributeTypes,
  default as uiReducer
} from 'store/uiReducer';
import _ from 'lodash';

describe('(Redux Module) UI', () => {
  describe('(Reducer) uiReducer', () => {
    let _initialState;
    beforeEach(() => {
      _initialState = {
        isFetching: false,
        recordTypes: {},
        attributeTypes: {},
        message: {
          active: false,
          text: '',
          success: false
        }
      };
    });
    it('Should be a function.', () => {
      expect(uiReducer).to.be.a('function');
    });

    it('Should initialize with a correct state', () => {
      expect(uiReducer(undefined, {})).to.deep.equal(_initialState);
    });

    it('Should return the previous state if an action was not matched.', () => {
      let state = uiReducer(undefined, {});
      expect(state).to.deep.equal(_initialState);
      state = uiReducer(state, {
        type: 'DOESNOTACTUALLYEXISTLOL'
      });
      expect(state).to.deep.equal(_initialState);
      state.message.active = true;                  // TODO: Improve this
      expect(state.message.active).to.equal(true);
      state = uiReducer(state, closeMessage());
      expect(state.message.active).to.equal(false);
      state = uiReducer(state, {
        type: 'DOESNOTACTUALLYEXISTLOL'
      });
      expect(state.message.active).to.equal(false);
    });
  });

  describe('(Action Creator) fetchAttributeTypes', () => {
    let _dispatchSpy;
    let _globalState;

    beforeEach(() => {
      _globalState = {
        ui: uiReducer(undefined, {})
      };
      _dispatchSpy = sinon.spy((action) => {
        _globalState = {
          ..._globalState,
          ui: uiReducer(_globalState.ui, action)
        };
      });
    });

    it('Should fetch validation rules and attribute types', () => {
      expect(_.keys(_globalState.ui.attributeTypes).length).to.equal(0);
      return setTimeout(fetchAttributeTypes()(_dispatchSpy)
        .then(() => {
          _dispatchSpy.should.have.been.calledOnce;
          expect(_.keys(_globalState.ui.attributeTypes).length).to.be.greaterThan(0);
        }), 15000);
    });
  });
});
<|endoftext|>


<commit_before>"use strict";

var React = require('react');
var assign = require('react/lib/Object.assign');
var cloneWithProps = require('react/lib/cloneWithProps');
var GoogleMapsAPI = require('../../GoogleMapsAPI');
var MapPropTypes = require('../MapPropTypes');

function MapOverlayView(props) {
  this.props = props;
  this.setMap(props.map);
}

MapOverlayView.prototype = new GoogleMapsAPI.OverlayView();

MapOverlayView.prototype.onAdd = function() {
  this._containerElement = document.createElement('div');
  this.getPanes()[this.props.mapPane]
    .appendChild(this._containerElement);
};

MapOverlayView.prototype.draw = function() {
  var props = assign({}, this.props, {position: null, mapPane: null});
  if (this.props.position) {
    var point = this.getProjection()
      .fromLatLngToDivPixel(this.props.position);

    props.style = assign({}, {
      position: 'absolute',
      left: point.x,
      top: point.y
    }, this.props.style);
  }

  React.render(
    cloneWithProps(<div />, props),
    this._containerElement
  )
};

MapOverlayView.prototype.onRemove = function() {
  React.unmountComponentAtNode(this._containerElement);
  this._containerElement.parentNode
    .removeChild(this._containerElement);
  this._containerElement = null;
};

var ReactOverlayView = React.createClass({
  displayName: 'OverlayView',

  propTypes: {
    mapPane: MapPropTypes.MapPanes.isRequired
  },

  getDefaultProps: function() {
    return {
      mapPane: 'overlayLayer'
    };
  },

  render: function() {
    // Nothing to render
    return null;
  },

  componentDidMount: function() {
    this.__node = new MapOverlayView(this.props);
  },

  componentDidUpdate: function(prevProps) {
    this.__node.props = this.props;
    this.__node.draw();

    if (this.props.mapPane != prevProps.mapPane) {
      // Unmount then, mount again onto the correct map pane
      this.__node.setMap(null);
      this.__node.setMap(this.props.map);
    }
  },

  componentWillUnmount: function() {
    this.__node.setMap(null);
    this.__node = null;
  }
});

module.exports = ReactOverlayView;
<commit_msg>Don't draw unless the OverlayView has been added to the map.<commit_after>"use strict";

var React = require('react');
var assign = require('react/lib/Object.assign');
var cloneWithProps = require('react/lib/cloneWithProps');
var GoogleMapsAPI = require('../../GoogleMapsAPI');
var MapPropTypes = require('../MapPropTypes');

function MapOverlayView(props) {
  this.props = props;
  this.setMap(props.map);
}

MapOverlayView.prototype = new GoogleMapsAPI.OverlayView();

MapOverlayView.prototype.onAdd = function() {
  this._containerElement = document.createElement('div');
  this.getPanes()[this.props.mapPane]
    .appendChild(this._containerElement);
};

MapOverlayView.prototype.draw = function() {
  var props = assign({}, this.props, {position: null, mapPane: null});
  if (this.props.position) {
    var point = this.getProjection()
      .fromLatLngToDivPixel(this.props.position);

    props.style = assign({}, {
      position: 'absolute',
      left: point.x,
      top: point.y
    }, this.props.style);
  }

  React.render(
    cloneWithProps(<div />, props),
    this._containerElement
  )
};

MapOverlayView.prototype.onRemove = function() {
  React.unmountComponentAtNode(this._containerElement);
  this._containerElement.parentNode
    .removeChild(this._containerElement);
  this._containerElement = null;
};

var ReactOverlayView = React.createClass({
  displayName: 'OverlayView',

  propTypes: {
    mapPane: MapPropTypes.MapPanes.isRequired
  },

  getDefaultProps: function() {
    return {
      mapPane: 'overlayLayer'
    };
  },

  render: function() {
    // Nothing to render
    return null;
  },

  componentDidMount: function() {
    this.__node = new MapOverlayView(this.props);
  },

  componentDidUpdate: function(prevProps) {
    this.__node.props = this.props;
    if (this.__node._containerElement) {
      this.__node.draw();
    }

    if (this.props.mapPane != prevProps.mapPane) {
      // Unmount then, mount again onto the correct map pane
      this.__node.setMap(null);
      this.__node.setMap(this.props.map);
    }
  },

  componentWillUnmount: function() {
    this.__node.setMap(null);
    this.__node = null;
  }
});

module.exports = ReactOverlayView;
<|endoftext|>


<commit_before>exports.readTestData = function(filename) {
	var fs = require('fs');

	var lines = fs.readFileSync(filename).split("\n")

	var state;
	var output = []
	var current = {}
	var section;

	function readHeader(line) {
		if(line[0] != '#') throw ("Got " + line[0] + " but expected #");
		section = line.slice(1);
		if(section == 'document') {
			state = readDocument;
		} else if(section == 'errors') {
			state = readArray;
		} else {
			state = readString;
		}
	}

	function readArray(line) {
		if(!current[section]) {
			current[section] = [];
		}
		current[section].push(line);
	}

	function readString(line) {
		if(!current[section]) {
			current[section] = '';
		}
		current[section] += line + "\n";
	}

	function readDocument(line) {
		if(!current[section]) {
			current[section] = '';
		}
		if(line[0] == '|') line = line.slice(2);
		current[section] += line + "\n";
	}

	state = readHeader;
	for(var l in lines) {
		if(lines[l][0] == '#') {
			state = readHeader;
		} else if(lines[l] == '') {
			output.push(current);
			current = {};
			continue;
		}
		state(lines[l]);
	}

	if(current['data']) output.push(current);

	return output;
}
<commit_msg>- handle test data with double newlines in it<commit_after>exports.readTestData = function(filename) {
	var fs = require('fs');

	var lines = fs.readFileSync(filename).split("\n")

	var state;
	var output = []
	var current = {}
	var section;

	function readHeader(line) {
		if(line[0] != '#') throw ("Got " + line[0] + " but expected #");
		section = line.slice(1);
		if(section == 'document') {
			state = readDocument;
		} else if(section == 'errors') {
			state = readArray;
		} else {
			state = readString;
		}
	}

	function readArray(line) {
		if(!current[section]) {
			current[section] = [];
		}
		current[section].push(line);
	}

	function readString(line) {
		if(!current[section]) {
			current[section] = '';
		}
		current[section] += line + "\n";
	}

	function readDocument(line) {
		if(!current[section]) {
			current[section] = '';
		}
		if(line[0] == '|') line = line.slice(2);
		current[section] += line + "\n";
	}

	state = readHeader;
	for(var l in lines) {
		if(lines[l][0] == '#') {
			state = readHeader;
		} else if(lines[l] == '' && section != 'data') {
			output.push(current);
			current = {};
			continue;
		}
		state(lines[l]);
	}

	if(current['data']) output.push(current);

	return output;
}
<|endoftext|>


<commit_before>// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * 'cr-settings-internet-detail' is the settings subpage containing details
 * for a network.
 *
 * @group Chrome Settings Elements
 * @element cr-settings-internet-detail
 */
(function() {

Polymer({
  is: 'cr-settings-internet-detail-page',

  properties: {
    /**
     * ID of the page.
     *
     * @attribute PAGE_ID
     * @const {string}
     */
    PAGE_ID: {
      type: String,
      value: 'internet-detail',
      readOnly: true
    },

    /**
     * Route for the page.
     */
    route: {
      type: String,
      value: ''
    },

    /**
     * Whether the page is a subpage.
     */
    subpage: {
      type: Boolean,
      value: false
    },

    /**
     * Title for the page header and navigation menu.
     */
    pageTitle: {
      type: String,
      value: function() {
        return loadTimeData.getString('internetDetailPageTitle');
      }
    },

    /**
     * Reflects the selected settings page. We use this to extract guid from
     * window.location.href when this page is navigated to. This is a
     * workaround for a bug in the 1.0 version of more-routing where
     * selected-params="{{params}}" is not correctly setting params in
     * settings_main.html. TODO(stevenjb): Remove once more-routing is fixed.
     */
    selectedPage: {
      type: String,
      value: '',
      observer: 'selectedPageChanged_'
    },

    /**
     * Name of the 'core-icon' to show. TODO(stevenjb): Update this with the
     * icon for the active internet connection.
     */
    icon: {
      type: String,
      value: 'settings-ethernet',
      readOnly: true
    },

    /**
     * The network GUID to display details for.
     */
    guid: {
      type: String,
      value: '',
      observer: 'guidChanged_',
    },

    /**
     * The current state for the network matching |guid|.
     *
     * @type {?CrOnc.NetworkStateProperties}
     */
    networkState: {
      type: Object,
      value: null,
      observer: 'networkStateChanged_'
    },
  },

  /**
   * Listener function for chrome.networkingPrivate.onNetworksChanged event.
   * @type {?function(!Array<string>)}
   * @private
   */
  networksChangedListener_: null,

  /** @override */
  attached: function() {
    this.networksChangedListener_ = this.onNetworksChangedEvent_.bind(this);
    chrome.networkingPrivate.onNetworksChanged.addListener(
        this.networksChangedListener_);
  },

  /** @override */
  detached: function() {
    chrome.networkingPrivate.onNetworksChanged.removeListener(
        this.networksChangedListener_);
  },

  /**
   * Polymer guid changed method.
   */
  guidChanged_: function() {
    if (!this.guid)
      return;
    this.getNetworkDetails_();
  },

  /**
   * Polymer guid changed method. TODO(stevenjb): Remove, see TODO above.
   */
  selectedPageChanged_: function() {
    if ((this.selectedPage && this.selectedPage.PAGE_ID) != this.PAGE_ID)
      return;
    var href = window.location.href;
    var idx = href.lastIndexOf('/');
    var guid = href.slice(idx + 1);
    this.guid = guid;
  },

  /**
   * networkingPrivate.onNetworksChanged event callback.
   * @param {!Array<string>} networkIds The list of changed network GUIDs.
   * @private
   */
  onNetworksChangedEvent_: function(networkIds) {
    if (networkIds.indexOf(this.guid) != -1)
      this.getNetworkDetails_();
  },

  /**
   * Calls networkingPrivate.getState for this.guid.
   * @private
   */
  getNetworkDetails_: function() {
    if (!this.guid)
      return;
    chrome.networkingPrivate.getProperties(
        this.guid, this.getPropertiesCallback_.bind(this));
  },

  /**
   * networkingPrivate.getProperties callback.
   * @param {!CrOnc.NetworkStateProperties} state The network state properties.
   * @private
   */
  getPropertiesCallback_: function(state) {
    this.networkState = state;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {string} The text to display for the network name.
   * @private
   */
  getStateName_: function(state) {
    return (state && state.Name) || '';
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {string} The text to display for the network name.
   * @private
   */
  getStateClass_: function(state) {
    return this.isConnectedState_(state) ? 'connected' : '';
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {string} The text to display for the network connection state.
   * @private
   */
  getStateText_: function(state) {
    // TODO(stevenjb): Localize.
    return (state && state.ConnectionState) || '';
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @param {string} property The property name.
   * @return {string} The text to display for the property, including the label.
   * @private
   */
  getProperty_: function(state, property) {
    if (!state)
      return '';
    var value = this.get(property, state) || '';
    // TODO(stevenjb): Localize.
    return property + ': ' + value;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {boolean} True if the state is connected.
   * @private
   */
  isConnectedState_: function(state) {
    return state && state.ConnectionState == CrOnc.ConnectionState.CONNECTED;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {boolean} Whether or not the network can be connected.
   * @private
   */
  canConnect_: function(state) {
    return state && state.Type != 'Ethernet' &&
           state.ConnectionState == CrOnc.ConnectionState.NOT_CONNECTED;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {boolean} Whether or not the network can be disconnected.
   * @private
   */
  canDisconnect_: function(state) {
    return state && state.Type != 'Ethernet' &&
           state.ConnectionState != CrOnc.ConnectionState.NOT_CONNECTED;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @param {string} type The network type to match.
   * @return {boolean} Whether or not the type of 'state' matches 'type'.
   * @private
   */
  isType_: function(state, type) {
    return state && state.Type == type;
  },

  /**
   * Callback when the Connect button is clicked.
   * @private
   */
  onConnectClicked_: function() {
    chrome.networkingPrivate.startConnect(this.guid);
  },

  /**
   * Callback when the Disconnect button is clicked.
   * @private
   */
  onDisconnectClicked_: function() {
    chrome.networkingPrivate.startDisconnect(this.guid);
  },
});
})();
<commit_msg>Elim defunct networkStateChanged_ observer<commit_after>// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * 'cr-settings-internet-detail' is the settings subpage containing details
 * for a network.
 *
 * @group Chrome Settings Elements
 * @element cr-settings-internet-detail
 */
(function() {

Polymer({
  is: 'cr-settings-internet-detail-page',

  properties: {
    /**
     * ID of the page.
     *
     * @attribute PAGE_ID
     * @const {string}
     */
    PAGE_ID: {
      type: String,
      value: 'internet-detail',
      readOnly: true
    },

    /**
     * Route for the page.
     */
    route: {
      type: String,
      value: ''
    },

    /**
     * Whether the page is a subpage.
     */
    subpage: {
      type: Boolean,
      value: false
    },

    /**
     * Title for the page header and navigation menu.
     */
    pageTitle: {
      type: String,
      value: function() {
        return loadTimeData.getString('internetDetailPageTitle');
      }
    },

    /**
     * Reflects the selected settings page. We use this to extract guid from
     * window.location.href when this page is navigated to. This is a
     * workaround for a bug in the 1.0 version of more-routing where
     * selected-params="{{params}}" is not correctly setting params in
     * settings_main.html. TODO(stevenjb): Remove once more-routing is fixed.
     */
    selectedPage: {
      type: String,
      value: '',
      observer: 'selectedPageChanged_'
    },

    /**
     * Name of the 'core-icon' to show. TODO(stevenjb): Update this with the
     * icon for the active internet connection.
     */
    icon: {
      type: String,
      value: 'settings-ethernet',
      readOnly: true
    },

    /**
     * The network GUID to display details for.
     */
    guid: {
      type: String,
      value: '',
      observer: 'guidChanged_',
    },

    /**
     * The current state for the network matching |guid|.
     *
     * @type {?CrOnc.NetworkStateProperties}
     */
    networkState: {
      type: Object,
      value: null
    },
  },

  /**
   * Listener function for chrome.networkingPrivate.onNetworksChanged event.
   * @type {?function(!Array<string>)}
   * @private
   */
  networksChangedListener_: null,

  /** @override */
  attached: function() {
    this.networksChangedListener_ = this.onNetworksChangedEvent_.bind(this);
    chrome.networkingPrivate.onNetworksChanged.addListener(
        this.networksChangedListener_);
  },

  /** @override */
  detached: function() {
    chrome.networkingPrivate.onNetworksChanged.removeListener(
        this.networksChangedListener_);
  },

  /**
   * Polymer guid changed method.
   */
  guidChanged_: function() {
    if (!this.guid)
      return;
    this.getNetworkDetails_();
  },

  /**
   * Polymer guid changed method. TODO(stevenjb): Remove, see TODO above.
   */
  selectedPageChanged_: function() {
    if ((this.selectedPage && this.selectedPage.PAGE_ID) != this.PAGE_ID)
      return;
    var href = window.location.href;
    var idx = href.lastIndexOf('/');
    var guid = href.slice(idx + 1);
    this.guid = guid;
  },

  /**
   * networkingPrivate.onNetworksChanged event callback.
   * @param {!Array<string>} networkIds The list of changed network GUIDs.
   * @private
   */
  onNetworksChangedEvent_: function(networkIds) {
    if (networkIds.indexOf(this.guid) != -1)
      this.getNetworkDetails_();
  },

  /**
   * Calls networkingPrivate.getState for this.guid.
   * @private
   */
  getNetworkDetails_: function() {
    if (!this.guid)
      return;
    chrome.networkingPrivate.getProperties(
        this.guid, this.getPropertiesCallback_.bind(this));
  },

  /**
   * networkingPrivate.getProperties callback.
   * @param {!CrOnc.NetworkStateProperties} state The network state properties.
   * @private
   */
  getPropertiesCallback_: function(state) {
    this.networkState = state;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {string} The text to display for the network name.
   * @private
   */
  getStateName_: function(state) {
    return (state && state.Name) || '';
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {string} The text to display for the network name.
   * @private
   */
  getStateClass_: function(state) {
    return this.isConnectedState_(state) ? 'connected' : '';
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {string} The text to display for the network connection state.
   * @private
   */
  getStateText_: function(state) {
    // TODO(stevenjb): Localize.
    return (state && state.ConnectionState) || '';
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @param {string} property The property name.
   * @return {string} The text to display for the property, including the label.
   * @private
   */
  getProperty_: function(state, property) {
    if (!state)
      return '';
    var value = this.get(property, state) || '';
    // TODO(stevenjb): Localize.
    return property + ': ' + value;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {boolean} True if the state is connected.
   * @private
   */
  isConnectedState_: function(state) {
    return state && state.ConnectionState == CrOnc.ConnectionState.CONNECTED;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {boolean} Whether or not the network can be connected.
   * @private
   */
  canConnect_: function(state) {
    return state && state.Type != 'Ethernet' &&
           state.ConnectionState == CrOnc.ConnectionState.NOT_CONNECTED;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @return {boolean} Whether or not the network can be disconnected.
   * @private
   */
  canDisconnect_: function(state) {
    return state && state.Type != 'Ethernet' &&
           state.ConnectionState != CrOnc.ConnectionState.NOT_CONNECTED;
  },

  /**
   * @param {?CrOnc.NetworkStateProperties} state The network state properties.
   * @param {string} type The network type to match.
   * @return {boolean} Whether or not the type of 'state' matches 'type'.
   * @private
   */
  isType_: function(state, type) {
    return state && state.Type == type;
  },

  /**
   * Callback when the Connect button is clicked.
   * @private
   */
  onConnectClicked_: function() {
    chrome.networkingPrivate.startConnect(this.guid);
  },

  /**
   * Callback when the Disconnect button is clicked.
   * @private
   */
  onDisconnectClicked_: function() {
    chrome.networkingPrivate.startDisconnect(this.guid);
  },
});
})();
<|endoftext|>