<commit_before>const {clipboard, nativeImage, Menu, MenuItem, shell, dialog} = require('electron');
const url = require('url');
const fs = require('fs');
const request = require('request');

const MAILTO_PREFIX = "mailto:";

const PERMITTED_URL_SCHEMES = [
    'http:',
    'https:',
    MAILTO_PREFIX,
];

function safeOpenURL(target) {
    // openExternal passes the target to open/start/xdg-open,
    // so put fairly stringent limits on what can be opened
    // (for instance, open /bin/sh does indeed open a terminal
    // with a shell, albeit with no arguments)
    const parsedUrl = url.parse(target);
    if (PERMITTED_URL_SCHEMES.indexOf(parsedUrl.protocol) > -1) {
        // explicitly use the URL re-assembled by the url library,
        // so we know the url parser has understood all the parts
        // of the input string
        const newTarget = url.format(parsedUrl);
        shell.openExternal(newTarget);
    }
}

function onWindowOrNavigate(ev, target) {
    // always prevent the default: if something goes wrong,
    // we don't want to end up opening it in the electron
    // app, as we could end up opening any sort of random
    // url in a window that has node scripting access.
    ev.preventDefault();
    safeOpenURL(target);
}

function onLinkContextMenu(ev, params) {
    const url = params.linkURL || params.srcURL;

    const popupMenu = new Menu();
    // No point trying to open blob: URLs in an external browser: it ain't gonna work.
    if (!url.startsWith('blob:')) {
        popupMenu.append(new MenuItem({
            label: url,
            click() {
                safeOpenURL(url);
            },
        }));
    }

    let addSaveAs = false;
    if (params.mediaType && params.mediaType === 'image' && !url.startsWith('file://')) {
        popupMenu.append(new MenuItem({
            label: 'Copy image',
            click() {
                if (url.startsWith('data:')) {
                    clipboard.writeImage(nativeImage.createFromDataURL(url));
                } else {
                    ev.sender.copyImageAt(params.x, params.y);
                }
            },
        }));

        // We want the link to be ordered below the copy stuff, but don't want to duplicate
        // the `if` statement, so use a flag.
        addSaveAs = true;
    }

    // No point offering to copy a blob: URL either
    if (!url.startsWith('blob:')) {
        // Special-case e-mail URLs to strip the `mailto:` like modern browsers do
        if (url.startsWith(MAILTO_PREFIX)) {
            popupMenu.append(new MenuItem({
                label: 'Copy email address',
                click() {
                    clipboard.writeText(url.substr(MAILTO_PREFIX.length));
                },
            }));
        } else {
            popupMenu.append(new MenuItem({
                label: 'Copy link address',
                click() {
                    clipboard.writeText(url);
                },
            }));
        }
    }

    if (addSaveAs) {
        popupMenu.append(new MenuItem({
            label: 'Save image as...',
            click() {
                const targetFileName = params.titleText || "image.png";
                const filePath = dialog.showSaveDialog({
                    defaultPath: targetFileName,
                });

                try {
                    if (url.startsWith("data:")) {
                        fs.writeFileSync(filePath, nativeImage.createFromDataURL(url));
                    } else {
                        request.get(url).pipe(fs.createWriteStream(filePath));
                    }

                } catch (err) {
                    console.error(err);
                    dialog.showMessageBox({
                        type: "error",
                        title: "Failed to save image",
                        message: "The image failed to save",
                    });
                }
            },
        }));
    }

    // popup() requires an options object even for no options
    popupMenu.popup({});
    ev.preventDefault();
}

function _CutCopyPasteSelectContextMenus(params) {
    return [{
        role: 'cut',
        enabled: params.editFlags.canCut,
    }, {
        role: 'copy',
        enabled: params.editFlags.canCopy,
    }, {
        role: 'paste',
        enabled: params.editFlags.canPaste,
    }, {
        role: 'pasteandmatchstyle',
        enabled: params.editFlags.canPaste,
    }, {
        role: 'selectall',
        enabled: params.editFlags.canSelectAll,
    }];
}

function onSelectedContextMenu(ev, params) {
    const items = _CutCopyPasteSelectContextMenus(params);
    const popupMenu = Menu.buildFromTemplate(items);

    // popup() requires an options object even for no options
    popupMenu.popup({});
    ev.preventDefault();
}

function onEditableContextMenu(ev, params) {
    const items = [
        { role: 'undo' },
        { role: 'redo', enabled: params.editFlags.canRedo },
        { type: 'separator' },
    ].concat(_CutCopyPasteSelectContextMenus(params));

    const popupMenu = Menu.buildFromTemplate(items);

    // popup() requires an options object even for no options
    popupMenu.popup({});
    ev.preventDefault();
}

module.exports = (webContents) => {
    webContents.on('new-window', onWindowOrNavigate);
    // XXX: The below now does absolutely nothing because of
    // https://github.com/electron/electron/issues/8841
    // Whilst this isn't a security issue since without
    // node integration and with the sandbox, it should be
    // no worse than opening the site in Chrome, it obviously
    // means the user has to restart Riot to make it usable
    // again (often unintuitive because it minimises to the
    // system tray). We therefore need to be vigilant about
    // putting target="_blank" on links in Riot (although
    // we should generally be doing this anyway since links
    // navigating you away from Riot in the browser is
    // also annoying).
    webContents.on('will-navigate', onWindowOrNavigate);

    webContents.on('context-menu', function(ev, params) {
        if (params.linkURL || params.srcURL) {
            onLinkContextMenu(ev, params);
        } else if (params.selectionText) {
            onSelectedContextMenu(ev, params);
        } else if (params.isEditable) {
            onEditableContextMenu(ev, params);
        }
    });
};
<commit_msg>Don't try to save files the user didn't want to save<commit_after>const {clipboard, nativeImage, Menu, MenuItem, shell, dialog} = require('electron');
const url = require('url');
const fs = require('fs');
const request = require('request');

const MAILTO_PREFIX = "mailto:";

const PERMITTED_URL_SCHEMES = [
    'http:',
    'https:',
    MAILTO_PREFIX,
];

function safeOpenURL(target) {
    // openExternal passes the target to open/start/xdg-open,
    // so put fairly stringent limits on what can be opened
    // (for instance, open /bin/sh does indeed open a terminal
    // with a shell, albeit with no arguments)
    const parsedUrl = url.parse(target);
    if (PERMITTED_URL_SCHEMES.indexOf(parsedUrl.protocol) > -1) {
        // explicitly use the URL re-assembled by the url library,
        // so we know the url parser has understood all the parts
        // of the input string
        const newTarget = url.format(parsedUrl);
        shell.openExternal(newTarget);
    }
}

function onWindowOrNavigate(ev, target) {
    // always prevent the default: if something goes wrong,
    // we don't want to end up opening it in the electron
    // app, as we could end up opening any sort of random
    // url in a window that has node scripting access.
    ev.preventDefault();
    safeOpenURL(target);
}

function onLinkContextMenu(ev, params) {
    const url = params.linkURL || params.srcURL;

    const popupMenu = new Menu();
    // No point trying to open blob: URLs in an external browser: it ain't gonna work.
    if (!url.startsWith('blob:')) {
        popupMenu.append(new MenuItem({
            label: url,
            click() {
                safeOpenURL(url);
            },
        }));
    }

    let addSaveAs = false;
    if (params.mediaType && params.mediaType === 'image' && !url.startsWith('file://')) {
        popupMenu.append(new MenuItem({
            label: 'Copy image',
            click() {
                if (url.startsWith('data:')) {
                    clipboard.writeImage(nativeImage.createFromDataURL(url));
                } else {
                    ev.sender.copyImageAt(params.x, params.y);
                }
            },
        }));

        // We want the link to be ordered below the copy stuff, but don't want to duplicate
        // the `if` statement, so use a flag.
        addSaveAs = true;
    }

    // No point offering to copy a blob: URL either
    if (!url.startsWith('blob:')) {
        // Special-case e-mail URLs to strip the `mailto:` like modern browsers do
        if (url.startsWith(MAILTO_PREFIX)) {
            popupMenu.append(new MenuItem({
                label: 'Copy email address',
                click() {
                    clipboard.writeText(url.substr(MAILTO_PREFIX.length));
                },
            }));
        } else {
            popupMenu.append(new MenuItem({
                label: 'Copy link address',
                click() {
                    clipboard.writeText(url);
                },
            }));
        }
    }

    if (addSaveAs) {
        popupMenu.append(new MenuItem({
            label: 'Save image as...',
            click() {
                const targetFileName = params.titleText || "image.png";
                const filePath = dialog.showSaveDialog({
                    defaultPath: targetFileName,
                });

                if (!filePath) return; // user cancelled dialog

                try {
                    if (url.startsWith("data:")) {
                        fs.writeFileSync(filePath, nativeImage.createFromDataURL(url));
                    } else {
                        request.get(url).pipe(fs.createWriteStream(filePath));
                    }
                } catch (err) {
                    console.error(err);
                    dialog.showMessageBox({
                        type: "error",
                        title: "Failed to save image",
                        message: "The image failed to save",
                    });
                }
            },
        }));
    }

    // popup() requires an options object even for no options
    popupMenu.popup({});
    ev.preventDefault();
}

function _CutCopyPasteSelectContextMenus(params) {
    return [{
        role: 'cut',
        enabled: params.editFlags.canCut,
    }, {
        role: 'copy',
        enabled: params.editFlags.canCopy,
    }, {
        role: 'paste',
        enabled: params.editFlags.canPaste,
    }, {
        role: 'pasteandmatchstyle',
        enabled: params.editFlags.canPaste,
    }, {
        role: 'selectall',
        enabled: params.editFlags.canSelectAll,
    }];
}

function onSelectedContextMenu(ev, params) {
    const items = _CutCopyPasteSelectContextMenus(params);
    const popupMenu = Menu.buildFromTemplate(items);

    // popup() requires an options object even for no options
    popupMenu.popup({});
    ev.preventDefault();
}

function onEditableContextMenu(ev, params) {
    const items = [
        { role: 'undo' },
        { role: 'redo', enabled: params.editFlags.canRedo },
        { type: 'separator' },
    ].concat(_CutCopyPasteSelectContextMenus(params));

    const popupMenu = Menu.buildFromTemplate(items);

    // popup() requires an options object even for no options
    popupMenu.popup({});
    ev.preventDefault();
}

module.exports = (webContents) => {
    webContents.on('new-window', onWindowOrNavigate);
    // XXX: The below now does absolutely nothing because of
    // https://github.com/electron/electron/issues/8841
    // Whilst this isn't a security issue since without
    // node integration and with the sandbox, it should be
    // no worse than opening the site in Chrome, it obviously
    // means the user has to restart Riot to make it usable
    // again (often unintuitive because it minimises to the
    // system tray). We therefore need to be vigilant about
    // putting target="_blank" on links in Riot (although
    // we should generally be doing this anyway since links
    // navigating you away from Riot in the browser is
    // also annoying).
    webContents.on('will-navigate', onWindowOrNavigate);

    webContents.on('context-menu', function(ev, params) {
        if (params.linkURL || params.srcURL) {
            onLinkContextMenu(ev, params);
        } else if (params.selectionText) {
            onSelectedContextMenu(ev, params);
        } else if (params.isEditable) {
            onEditableContextMenu(ev, params);
        }
    });
};
<|endoftext|>


<commit_before>'use strict';

var logger = require('./log');
var path = require('path');
var fs = require('fs');
var yaml = require('js-yaml');

/**
 * Tests given exception to see if the code is `MODULE_NOT_FOUND` and
 * if the exception text matches the module name.
 *
 * @param {String} name Module name to check in exception.
 * @param {Object} e Exception.
 * @return {Boolean}
 */
function isModuleNotFound(name, e) {
  if (e.code !== 'MODULE_NOT_FOUND') {
    return false;
  }

  return e.message.split('\'')[1].split('\'')[0] === name;
}

// Object identifier for module not found exception.
var MODULE_NOT_FOUND = {};

// Illegal type exception
var ILLEGAL_TYPE = {};

/**
 * Wrapper for `require` that will throw above `MODULE_NOT_FOUND` object
 * reference if the very module `name` was not found. If `name` is found
 * but an inner `require` fails, the normal exception will be thrown.
 */
function requireNotFound(name) {
  try {
    return require(name);
  } catch (e) {
    if (isModuleNotFound(name, e)) {
      throw MODULE_NOT_FOUND;
    }

    throw e;
  }
}

/**
 * Resolve and configuration file path.
 *
 * @param {String} config
 * @return {String}
 */
function resolveConfig(config) {
  return path.resolve(process.cwd(),  config);
}

/**
 * Read a config file according to its extension.
 *
 * @param {String} file
 */
function readConfig(file) {
  return yaml.safeLoad(fs.readFileSync(file, 'utf-8'));
}

/**
 * Try an array of config files until one is actually found, or
 * return empty object.
 *
 * @param {String[]} configs
 */
function tryConfigs(configs) {
  for (var i = 0, length = configs.length; i < length; i++) {
    try {
      return readConfig(resolveConfig(configs[i]));
    } catch (e) {
      if (e !== ILLEGAL_TYPE && e.code !== 'ENOENT') {
        throw e;
      }
    }
  }

  // Empty view config, maybe the theme will set default values.
  return {};
}

/**
 * Resolve and require configuration value.
 *
 * @param {String|Object} config
 * @return {Object}
 */
function requireConfig(config) {
  if (config) {
    try {
      return readConfig(resolveConfig(config));
    } catch (e) {
      if (e === ILLEGAL_TYPE) {
        // Already logged
      } else if (e.code === 'ENOENT') {
        logger.error('Config file `' + config + '` not found.');
      } else {
        throw e;
      }

      logger.warn('Falling back to default config.');
    }
  }

  return tryConfigs(['view.json', 'view.yaml', 'view.yml']);
}

/**
 * Resolve and require package value.
 *
 * @param {String} dir
 * @param {String|Object} package
 * @return {Object}
 */
function requirePackage(dir, pkg) {
  if (!pkg) {
    try {
      // Try `package.json` in the same directory.
      return requireNotFound(dir + '/package.json');
    } catch (e) {
      if (e !== MODULE_NOT_FOUND) {
        throw e;
      }

      logger.warn('No package information.');
      return null;
    }
  }

  var pkgPath = dir + '/' + pkg;

  try {
    return requireNotFound(pkgPath);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }

    var message = 'Can\'t find a package file at `' + pkgPath + '`.';
    logger.warn(message);
  }
}

/**
 * @param {*} theme
 * @return {Function}
 * @throws If the theme is not a function.
 */
function inspectTheme(theme) {
  var opt = Object.prototype.toString;
  var message; // Error message helper variable

  if (typeof theme !== 'function') {
    message = 'Given theme is ' + opt.call(theme) + ', expected ' +
              opt.call(inspectTheme) + '.';

    logger.error(message);

    throw message;
  }

  if (theme.length !== 2) {
    message = 'Given theme takes ' + theme.length + ' arguments, ' +
              'expected 2.';

    logger.warn(message);
  }

  return theme;
}

/**
 * Resolve and require theme value.
 *
 * `MODULE_NOT_FOUND` reference is thrown if nothing at all is found.
 *
 * @param {String} dir
 * @param {String} theme
 * @return {*}
 */
function requireRawTheme(dir, theme) {
  if (!theme) {
    theme = 'default';
  }

  try {
    return requireNotFound('sassdoc-theme-' + theme);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }
  }

  try {
    return requireNotFound(dir + '/' + theme);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }
  }

  return requireNotFound(theme);
}

/*global requireTheme:true */

/**
 * Fallback to default theme, logging a message.
 *
 * @param {String} dir
 */
function defaultTheme(dir) {
  logger.warn('Falling back to default theme.');
  return requireTheme(dir);
}

/**
 * Resolve require and validate theme value.
 *
 * @param {String} dir
 * @param {String} theme
 * @return {Function}
 */
function requireTheme(dir, theme) {
  try {
    theme = requireRawTheme(dir, theme);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }

    if (!theme) {
      // Default theme was not found? WTF!
      throw 'Holy shit, the default theme was not found!';
    }

    logger.error('Theme `' + theme + '` not found.');
    return defaultTheme(dir);
  }

  try {
    // Already logs any error
    return inspectTheme(theme);
  } catch (e) {
    return defaultTheme(dir);
  }
}

/**
 * Parse configuration.
 *
 * @param {String|Object} view
 * @return {Object}
 */
module.exports = function (view) {
  // Relative directory for `package` file
  var dir;
  var config = {};

  if (typeof view !== 'object') {
    dir = path.resolve(path.dirname(config));
    view = requireConfig(view);
  } else {
    // `package` is relative to CWD
    dir = process.cwd();
  }

  if (!('view' in view)) {
    config.view = view;
  } else {
    // Already processed
    config = view;
  }

  // Resolve package
  if (typeof view.package === 'object') {
    config.package = view.package;
  } else {
    config.package = requirePackage(dir, view.package);
  }

  // Resolve theme
  if (typeof view.theme === 'function') {
    config.theme = view.theme;
  } else {
    config.theme = requireTheme(dir, view.theme);
  }

  // Expose the relative path base
  config.dir = dir;

  return config;
};
<commit_msg>Add warning about default config naming scheme<commit_after>'use strict';

var logger = require('./log');
var path = require('path');
var fs = require('fs');
var yaml = require('js-yaml');
var chalk = require('chalk');

/**
 * Tests given exception to see if the code is `MODULE_NOT_FOUND` and
 * if the exception text matches the module name.
 *
 * @param {String} name Module name to check in exception.
 * @param {Object} e Exception.
 * @return {Boolean}
 */
function isModuleNotFound(name, e) {
  if (e.code !== 'MODULE_NOT_FOUND') {
    return false;
  }

  return e.message.split('\'')[1].split('\'')[0] === name;
}

// Object identifier for module not found exception.
var MODULE_NOT_FOUND = {};

// Illegal type exception
var ILLEGAL_TYPE = {};

/**
 * Wrapper for `require` that will throw above `MODULE_NOT_FOUND` object
 * reference if the very module `name` was not found. If `name` is found
 * but an inner `require` fails, the normal exception will be thrown.
 */
function requireNotFound(name) {
  try {
    return require(name);
  } catch (e) {
    if (isModuleNotFound(name, e)) {
      throw MODULE_NOT_FOUND;
    }

    throw e;
  }
}

/**
 * Resolve and configuration file path.
 *
 * @param {String} config
 * @return {String}
 */
function resolveConfig(config) {
  return path.resolve(process.cwd(),  config);
}

/**
 * Read a config file according to its extension.
 *
 * @param {String} file
 */
function readConfig(file) {
  return yaml.safeLoad(fs.readFileSync(file, 'utf-8'));
}

/**
 * Try an array of config files until one is actually found, or
 * return empty object.
 *
 * @param {String[]} configs
 */
function tryConfigs(configs) {
  for (var i = 0, length = configs.length; i < length; i++) {
    try {
      return readConfig(resolveConfig(configs[i]));
    } catch (e) {
      if (e !== ILLEGAL_TYPE && e.code !== 'ENOENT') {
        throw e;
      }
    }
  }

  // Empty view config, maybe the theme will set default values.
  return {};
}

/**
 * Resolve and require configuration value.
 *
 * @param {String|Object} config
 * @return {Object}
 */
function requireConfig(config) {
  if (config) {
    try {
      return readConfig(resolveConfig(config));
    } catch (e) {
      if (e === ILLEGAL_TYPE) {
        // Already logged
      } else if (e.code === 'ENOENT') {
        logger.error('Config file `' + config + '` not found.');
      } else {
        throw e;
      }

      logger.warn('Falling back to default config.');
    }
  }

  logger.warn(
    'Starting SassDoc ' + chalk.grey('v2.0') + ', the default ' +
    'configuration file will be named ' + chalk.grey('.sassdocrc') + ' ' +
    'rather than ' + chalk.grey('view.{json,yaml,yml}') +
    '. Please consider using the ' + chalk.grey('--config') +
    ' option right now to specify your own file.'
  );

  return tryConfigs(['view.json', 'view.yaml', 'view.yml']);
}

/**
 * Resolve and require package value.
 *
 * @param {String} dir
 * @param {String|Object} package
 * @return {Object}
 */
function requirePackage(dir, pkg) {
  if (!pkg) {
    try {
      // Try `package.json` in the same directory.
      return requireNotFound(dir + '/package.json');
    } catch (e) {
      if (e !== MODULE_NOT_FOUND) {
        throw e;
      }

      logger.warn('No package information.');
      return null;
    }
  }

  var pkgPath = dir + '/' + pkg;

  try {
    return requireNotFound(pkgPath);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }

    var message = 'Can\'t find a package file at `' + pkgPath + '`.';
    logger.warn(message);
  }
}

/**
 * @param {*} theme
 * @return {Function}
 * @throws If the theme is not a function.
 */
function inspectTheme(theme) {
  var opt = Object.prototype.toString;
  var message; // Error message helper variable

  if (typeof theme !== 'function') {
    message = 'Given theme is ' + opt.call(theme) + ', expected ' +
              opt.call(inspectTheme) + '.';

    logger.error(message);

    throw message;
  }

  if (theme.length !== 2) {
    message = 'Given theme takes ' + theme.length + ' arguments, ' +
              'expected 2.';

    logger.warn(message);
  }

  return theme;
}

/**
 * Resolve and require theme value.
 *
 * `MODULE_NOT_FOUND` reference is thrown if nothing at all is found.
 *
 * @param {String} dir
 * @param {String} theme
 * @return {*}
 */
function requireRawTheme(dir, theme) {
  if (!theme) {
    theme = 'default';
  }

  try {
    return requireNotFound('sassdoc-theme-' + theme);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }
  }

  try {
    return requireNotFound(dir + '/' + theme);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }
  }

  return requireNotFound(theme);
}

/*global requireTheme:true */

/**
 * Fallback to default theme, logging a message.
 *
 * @param {String} dir
 */
function defaultTheme(dir) {
  logger.warn('Falling back to default theme.');
  return requireTheme(dir);
}

/**
 * Resolve require and validate theme value.
 *
 * @param {String} dir
 * @param {String} theme
 * @return {Function}
 */
function requireTheme(dir, theme) {
  try {
    theme = requireRawTheme(dir, theme);
  } catch (e) {
    if (e !== MODULE_NOT_FOUND) {
      throw e;
    }

    if (!theme) {
      // Default theme was not found? WTF!
      throw 'Holy shit, the default theme was not found!';
    }

    logger.error('Theme `' + theme + '` not found.');
    return defaultTheme(dir);
  }

  try {
    // Already logs any error
    return inspectTheme(theme);
  } catch (e) {
    return defaultTheme(dir);
  }
}

/**
 * Parse configuration.
 *
 * @param {String|Object} view
 * @return {Object}
 */
module.exports = function (view) {
  // Relative directory for `package` file
  var dir;
  var config = {};

  if (typeof view !== 'object') {
    dir = path.resolve(path.dirname(config));
    view = requireConfig(view);
  } else {
    // `package` is relative to CWD
    dir = process.cwd();
  }

  if (!('view' in view)) {
    config.view = view;
  } else {
    // Already processed
    config = view;
  }

  // Resolve package
  if (typeof view.package === 'object') {
    config.package = view.package;
  } else {
    config.package = requirePackage(dir, view.package);
  }

  // Resolve theme
  if (typeof view.theme === 'function') {
    config.theme = view.theme;
  } else {
    config.theme = requireTheme(dir, view.theme);
  }

  // Expose the relative path base
  config.dir = dir;

  return config;
};
<|endoftext|>


<commit_before>/*
 * In order to prevent errors caused by e2e tests running too fast you can slow them down by calling the following
 * function. Use higher values for slower tests.
 *
 * utils.delayPromises(50);
 *
 */

describe('arachne 4 frontend', function() {

    beforeEach(function(){
        browser.get('/');
    });

    it('should have the total numver of entities listed on the front page', function() {
        var entityCount = element(by.binding('entityCount'));
        expect(entityCount.isPresent()).toBe(true);
        expect(entityCount.getText()).toMatch(/[0-9,.]/);
    });

});
<commit_msg>Fix typo.<commit_after>/*
 * In order to prevent errors caused by e2e tests running too fast you can slow them down by calling the following
 * function. Use higher values for slower tests.
 *
 * utils.delayPromises(50);
 *
 */

describe('arachne 4 frontend', function() {

    beforeEach(function(){
        browser.get('/');
    });

    it('should have the total number of entities listed on the front page', function() {
        var entityCount = element(by.binding('entityCount'));
        expect(entityCount.isPresent()).toBe(true);
        expect(entityCount.getText()).toMatch(/[0-9,.]/);
    });

});
<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var BaseTween = require('./BaseTween');
var Class = require('../../utils/Class');
var Events = require('../events');
var GameObjectCreator = require('../../gameobjects/GameObjectCreator');
var GameObjectFactory = require('../../gameobjects/GameObjectFactory');
var Tween = require('./Tween');
var TWEEN_CONST = require('./const');
var TweenBuilder = require('../builders/TweenBuilder');

/**
 * @classdesc
 * TODO
 *
 * @class TweenChain
 * @memberof Phaser.Tweens
 * @extends Phaser.Tweens.BaseTween
 * @constructor
 * @since 3.60.0
 *
 * @param {Phaser.Tweens.TweenManager} parent - A reference to the Tween Manager that owns this Tween.
 */
var TweenChain = new Class({

    Extends: BaseTween,

    initialize:

    function TweenChain (parent)
    {
        BaseTween.call(this, parent);

        this.currentTween = null;

        this.currentIndex = 0;
    },

    /**
     * Prepares this Tween for playback.
     *
     * Called automatically by the TweenManager. Should not be called directly.
     *
     * @method Phaser.Tweens.Tween#init
     * @fires Phaser.Tweens.Events#TWEEN_ACTIVE
     * @since 3.0.0
     *
     * @return {this} This Tween instance.
     */
    init: function ()
    {
        this.loopCounter = (this.loop === -1) ? 999999999999 : this.loop;

        this.setCurrentTween(0);

        if (this.startDelay > 0)
        {
            this.setStartDelayState();
        }
        else
        {
            this.setActiveState();
        }

        this.dispatchEvent(Events.TWEEN_ACTIVE, 'onActive');

        return this;
    },

    /**
     * Create a sequence of Tweens, chained to one-another, and add them to this Tween Manager.
     *
     * The tweens are played in order, from start to finish. You can optionally set the chain
     * to repeat as many times as you like. Once the chain has finished playing, or repeating if set,
     * all tweens in the chain will be destroyed automatically. To override this, set the 'persists'
     * argument to 'true'.
     *
     * Playback will start immediately unless the _first_ Tween has been configured to be paused.
     *
     * Please note that Tweens will not manipulate any target property that begins with an underscore.
     *
     * @method Phaser.Tweens.TweenManager#add
     * @since 3.60.0
     *
     * @param {Phaser.Types.Tweens.TweenBuilderConfig[]|object[]} tweens - An array of Tween configuration objects for the Tweens in this chain.
     *
     * @return {Phaser.Tweens.TweenChain} This TweenChain instance.
     */
    add: function (tweens)
    {
        if (!Array.isArray(tweens))
        {
            tweens = [ tweens ];
        }

        var data = this.data;

        for (var i = 0; i < tweens.length; i++)
        {
            var tween = tweens[i];

            if (tween instanceof Tween)
            {
                data.push(tween.init());
            }
            else
            {
                tween = TweenBuilder(this, tween);

                data.push(tween.init());
            }
        }

        this.totalData = data.length;

        return this;
    },

    /**
     * See if this Tween is currently acting upon the given target.
     *
     * @method Phaser.Tweens.Tween#hasTarget
     * @since 3.0.0
     *
     * @param {object} target - The target to check against this Tween.
     *
     * @return {boolean} `true` if the given target is a target of this Tween, otherwise `false`.
     */
    hasTarget: function (target)
    {
        var data = this.data;

        for (var i = 0; i < this.totalData; i++)
        {
            if (data[i].hasTarget(target))
            {
                return true;
            }
        }

        return false;
    },

    /**
     * Restarts the Tween from the beginning.
     *
     * You can only restart a Tween that is currently playing. If the Tween has already been stopped, restarting
     * it will throw an error.
     *
     * If you wish to restart the Tween from a specific point, use the `Tween.seek` method instead.
     *
     * @method Phaser.Tweens.Tween#restart
     * @since 3.0.0
     *
     * @return {this} This Tween instance.
     */
    restart: function ()
    {
        switch (this.state)
        {
            case TWEEN_CONST.REMOVED:
            case TWEEN_CONST.FINISHED:
                this.parent.makeActive(this);
                break;

            case TWEEN_CONST.PENDING:
            case TWEEN_CONST.PENDING_REMOVE:
                this.parent.reset(this);
                break;

            case TWEEN_CONST.DESTROYED:
                console.warn('Cannot restart destroyed TweenChain');
                break;

            default:
                // this.seek();
                break;
        }

        this.paused = false;
        this.hasStarted = false;
        this.currentIndex = 0;
        this.currentTween = this.data[0];

        return this;
    },

    /**
     * Resets the given Tween.
     *
     * If the Tween does not belong to this Tween Manager, it will first be added.
     *
     * Then it will seek to position 0 and playback will start on the next frame.
     *
     * @method Phaser.Tweens.TweenManager#reset
     * @since 3.60.0
     *
     * @param {Phaser.Tweens.Tween} tween - The Tween to be reset.
     *
     * @return {this} This Tween Manager instance.
     */
    reset: function (tween)
    {
        tween.seek();

        tween.setActiveState();

        return this;
    },

    /**
     * Checks if a Tween is active and adds it to the Tween Manager at the start of the frame if it isn't.
     *
     * @method Phaser.Tweens.TweenManager#makeActive
     * @since 3.0.0
     *
     * @param {Phaser.Tweens.Tween} tween - The Tween to check.
     *
     * @return {this} This Tween Manager instance.
     */
    makeActive: function (tween)
    {
        tween.init();

        tween.setActiveState();

        return this;
    },

    /**
     * Internal method that advances to the next state of the Tween during playback.
     *
     * @method Phaser.Tweens.Tween#nextState
     * @fires Phaser.Tweens.Events#TWEEN_COMPLETE
     * @fires Phaser.Tweens.Events#TWEEN_LOOP
     * @since 3.0.0
     *
     * @return {boolean} `true` if this Tween has completed, otherwise `false`.
     */
    nextState: function ()
    {
        if (this.loopCounter > 0)
        {
            this.loopCounter--;

            this.resetTweens();

            if (this.loopDelay > 0)
            {
                this.countdown = this.loopDelay;

                this.setLoopDelayState();
            }
            else
            {
                this.setActiveState();

                this.dispatchEvent(Events.TWEEN_LOOP, 'onLoop');
            }
        }
        else if (this.completeDelay > 0)
        {
            this.countdown = this.completeDelay;

            this.setCompleteDelayState();
        }
        else
        {
            this.onCompleteHandler();

            return true;
        }

        return false;
    },

    /**
     * Starts a Tween playing.
     *
     * You only need to call this method if you have configured the tween to be paused on creation.
     *
     * If the Tween is already playing, calling this method again will have no effect. If you wish to
     * restart the Tween, use `Tween.restart` instead.
     *
     * Calling this method after the Tween has completed will start the Tween playing again from the beginning.
     * This is the same as calling `Tween.seek(0)` and then `Tween.play()`.
     *
     * @method Phaser.Tweens.Tween#play
     * @since 3.0.0
     *
     * @return {this} This Tween instance.
     */
    play: function ()
    {
        if (this.isDestroyed())
        {
            console.warn('Cannot play destroyed TweenChain', this);

            return this;
        }

        if (this.isPendingRemove() || this.isPending())
        {
            this.resetTweens();
        }

        this.paused = false;

        if (this.startDelay > 0)
        {
            this.setStartDelayState();
        }
        else
        {
            this.setActiveState();
        }

        return this;
    },

    /**
     * Internal method that resets all of the Tween Data, including the progress and elapsed values.
     *
     * @method Phaser.Tweens.Tween#resetTweens
     * @since 3.0.0
     */
    resetTweens: function ()
    {
        var data = this.data;
        var total = this.totalData;

        for (var i = 0; i < total; i++)
        {
            data[i].reset(false);
        }

        this.currentIndex = 0;
        this.currentTween = data[0];
    },

    /**
     * Internal method that advances the Tween based on the time values.
     *
     * @method Phaser.Tweens.Tween#update
     * @fires Phaser.Tweens.Events#TWEEN_COMPLETE
     * @fires Phaser.Tweens.Events#TWEEN_LOOP
     * @fires Phaser.Tweens.Events#TWEEN_START
     * @since 3.0.0
     *
     * @param {number} delta - The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
     *
     * @return {boolean} Returns `true` if this Tween has finished and should be removed from the Tween Manager, otherwise returns `false`.
     */
    update: function (delta)
    {
        if (this.isPendingRemove() || this.isDestroyed())
        {
            return true;
        }
        else if (this.isFinished() || this.paused)
        {
            return false;
        }

        //  The TweehChain.timeScale is applied within Tween.update, so doesn't need including here
        delta *= this.parent.timeScale;

        if (this.isLoopDelayed())
        {
            this.updateLoopCountdown(delta);
        }
        else if (this.isCompleteDelayed())
        {
            this.updateCompleteDelay(delta);
        }
        else if (this.isStartDelayed())
        {
            //  Reset the delta so we always start progress from zero
            delta = this.updateStartCountdown(delta);
        }

        var remove = false;

        if (this.isActive() && this.currentTween)
        {
            if (this.currentTween.update(delta))
            {
                //  This tween has finshed playback, so move to the next one
                if (this.nextTween())
                {
                    this.nextState();
                }
            }

            //  if nextState called onCompleteHandler then we're ready to be removed, unless we persist
            remove = this.isPendingRemove();

            if (remove && this.persist)
            {
                this.setFinishedState();

                remove = false;
            }
        }

        return remove;
    },

    getCurrentTween: function ()
    {
        return this.data[this.currentIndex];
    },

    nextTween: function ()
    {
        this.currentIndex++;

        if (this.currentIndex === this.totalData)
        {
            return true;
        }
        else
        {
            this.setCurrentTween(this.currentIndex);
        }
    },

    setCurrentTween: function (index)
    {
        this.currentIndex = index;

        this.currentTween = this.data[index];

        this.currentTween.setActiveState();

        this.currentTween.dispatchEvent(Events.TWEEN_ACTIVE, 'onActive');
    },

    /**
     * Internal method that will emit a Tween based Event and invoke the given callback.
     *
     * @method Phaser.Tweens.Tween#dispatchEvent
     * @since 3.60.0
     *
     * @param {Phaser.Types.Tweens.Event} event - The Event to be dispatched.
     * @param {Phaser.Types.Tweens.TweenCallbackTypes} [callback] - The name of the callback to be invoked. Can be `null` or `undefined` to skip invocation.
     */
    dispatchEvent: function (event, callback)
    {
        this.emit(event, this, this.targets);

        var handler = this.callbacks[callback];

        if (handler)
        {
            handler.func.apply(handler.scope, [ this, this.targets ].concat(handler.params));
        }
    },

    /**
     * Immediately destroys this TweenChain, nulling of all its references.
     *
     * @method Phaser.Tweens.TweenChain#destroy
     * @since 3.60.0
     */
    destroy: function ()
    {
        BaseTween.prototype.destroy.call(this);

        console.log('TweenChain destroyed');
    }

});

/**
 * Creates a new Tween object.
 *
 * Note: This method will only be available if Tweens have been built into Phaser.
 *
 * @method Phaser.GameObjects.GameObjectFactory#tweenchain
 * @since 3.0.0
 *
 * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - The Tween configuration.
 *
 * @return {Phaser.Tweens.Tween} The Tween that was created.
 */
GameObjectFactory.register('tweenchain', function (config)
{
    return this.scene.sys.tweens.add(config);
});

/**
 * Creates a new Tween object and returns it.
 *
 * Note: This method will only be available if Tweens have been built into Phaser.
 *
 * @method Phaser.GameObjects.GameObjectCreator#tweenchain
 * @since 3.0.0
 *
 * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - The Tween configuration.
 *
 * @return {Phaser.Tweens.Tween} The Tween that was created.
 */
GameObjectCreator.register('tweenchain', function (config)
{
    return this.scene.sys.tweens.create(config);
});

module.exports = TweenChain;
<commit_msg>All method and property docs done. Fixed init, restart and others.<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var BaseTween = require('./BaseTween');
var Class = require('../../utils/Class');
var Events = require('../events');
var GameObjectCreator = require('../../gameobjects/GameObjectCreator');
var GameObjectFactory = require('../../gameobjects/GameObjectFactory');

/**
 * @classdesc
 * TODO
 *
 * @class TweenChain
 * @memberof Phaser.Tweens
 * @extends Phaser.Tweens.BaseTween
 * @constructor
 * @since 3.60.0
 *
 * @param {(Phaser.Tweens.TweenManager|Phaser.Tweens.TweenChain)} parent - A reference to the Tween Manager, or TweenChain, that owns this TweenChain.
 */
var TweenChain = new Class({

    Extends: BaseTween,

    initialize:

    function TweenChain (parent)
    {
        BaseTween.call(this, parent);

        /**
         * A reference to the Tween that this TweenChain is currently playing.
         *
         * @name Phaser.Tweens.TweenChain#currentTween
         * @type {Phaser.Tweens.Tween}
         * @since 3.60.0
         */
        this.currentTween = null;

        /**
         * A reference to the data array index of the currently playing tween.
         *
         * @name Phaser.Tweens.TweenChain#currentIndex
         * @type {number}
         * @since 3.60.0
         */
        this.currentIndex = 0;
    },

    /**
     * Prepares this TweenChain for playback.
     *
     * Called automatically by the TweenManager. Should not be called directly.
     *
     * @method Phaser.Tweens.TweenChain#init
     * @fires Phaser.Tweens.Events#TWEEN_ACTIVE
     * @since 3.60.0
     *
     * @return {this} This TweenChain instance.
     */
    init: function ()
    {
        this.loopCounter = (this.loop === -1) ? 999999999999 : this.loop;

        this.setCurrentTween(0);

        if (this.startDelay > 0 && !this.isStartDelayed())
        {
            this.setStartDelayState();
        }
        else
        {
            this.setActiveState();
        }

        this.dispatchEvent(Events.TWEEN_ACTIVE, 'onActive');

        return this;
    },

    /**
     * Create a sequence of Tweens, chained to one-another, and add them to this Tween Manager.
     *
     * The tweens are played in order, from start to finish. You can optionally set the chain
     * to repeat as many times as you like. Once the chain has finished playing, or repeating if set,
     * all tweens in the chain will be destroyed automatically. To override this, set the 'persists'
     * argument to 'true'.
     *
     * Playback will start immediately unless the _first_ Tween has been configured to be paused.
     *
     * Please note that Tweens will not manipulate any target property that begins with an underscore.
     *
     * @method Phaser.Tweens.TweenChain#add
     * @since 3.60.0
     *
     * @param {Phaser.Types.Tweens.TweenBuilderConfig[]|object[]} tweens - An array of Tween configuration objects for the Tweens in this chain.
     *
     * @return {this} This TweenChain instance.
     */
    add: function (tweens)
    {
        var newTweens = this.parent.create(tweens);

        if (!Array.isArray(newTweens))
        {
            newTweens = [ newTweens ];
        }

        var data = this.data;

        for (var i = 0; i < newTweens.length; i++)
        {
            var tween = newTweens[i];

            tween.parent = this;

            data.push(tween.init());
        }

        this.totalData = data.length;

        return this;
    },

    /**
     * See if any of the tweens in this Tween Chain is currently acting upon the given target.
     *
     * @method Phaser.Tweens.TweenChain#hasTarget
     * @since 3.60.0
     *
     * @param {object} target - The target to check against this TweenChain.
     *
     * @return {boolean} `true` if the given target is a target of this TweenChain, otherwise `false`.
     */
    hasTarget: function (target)
    {
        var data = this.data;

        for (var i = 0; i < this.totalData; i++)
        {
            if (data[i].hasTarget(target))
            {
                return true;
            }
        }

        return false;
    },

    /**
     * Restarts the TweenChain from the beginning.
     *
     * If this TweenChain was configured to have a loop, or start delay, those
     * are reset to their initial values as well. It will also dispatch the
     * `onActive` callback and event again.
     *
     * @method Phaser.Tweens.TweenChain#restart
     * @since 3.60.0
     *
     * @return {this} This TweenChain instance.
     */
    restart: function ()
    {
        if (this.isDestroyed())
        {
            console.warn('Cannot restart destroyed TweenChain', this);

            return this;
        }

        if (this.isRemoved())
        {
            this.parent.makeActive(this);
        }

        this.resetTweens();

        this.paused = false;

        return this.init();
    },

    /**
     * Resets the given Tween.
     *
     * It will seek to position 0 and playback will start on the next frame.
     *
     * @method Phaser.Tweens.TweenChain#reset
     * @since 3.60.0
     *
     * @param {Phaser.Tweens.Tween} tween - The Tween to be reset.
     *
     * @return {this} This TweenChain instance.
     */
    reset: function (tween)
    {
        tween.seek();

        tween.setActiveState();

        return this;
    },

    /**
     * Re-initiases the given Tween and sets it to the Active state.
     *
     * @method Phaser.Tweens.TweenChain#makeActive
     * @since 3.60.0
     *
     * @param {Phaser.Tweens.Tween} tween - The Tween to check.
     *
     * @return {this} This TweenChain instance.
     */
    makeActive: function (tween)
    {
        tween.init();

        tween.setActiveState();

        return this;
    },

    /**
     * Internal method that advances to the next state of the TweenChain playback.
     *
     * @method Phaser.Tweens.TweenChain#nextState
     * @fires Phaser.Tweens.Events#TWEEN_COMPLETE
     * @fires Phaser.Tweens.Events#TWEEN_LOOP
     * @since 3.60.0
     *
     * @return {boolean} `true` if this TweenChain has completed, otherwise `false`.
     */
    nextState: function ()
    {
        if (this.loopCounter > 0)
        {
            this.loopCounter--;

            this.resetTweens();

            if (this.loopDelay > 0)
            {
                this.countdown = this.loopDelay;

                this.setLoopDelayState();
            }
            else
            {
                this.setActiveState();

                this.dispatchEvent(Events.TWEEN_LOOP, 'onLoop');
            }
        }
        else if (this.completeDelay > 0)
        {
            this.countdown = this.completeDelay;

            this.setCompleteDelayState();
        }
        else
        {
            this.onCompleteHandler();

            return true;
        }

        return false;
    },

    /**
     * Starts this TweenChain playing.
     *
     * You only need to call this method if you have configured this TweenChain to be paused on creation.
     *
     * If the TweenChain is already playing, calling this method again will have no effect. If you wish to
     * restart the chain, use `TweenChain.restart` instead.
     *
     * Calling this method after the TweenChain has completed will start the chain playing again from the beginning.
     *
     * @method Phaser.Tweens.TweenChain#play
     * @since 3.60.0
     *
     * @return {this} This TweenChain instance.
     */
    play: function ()
    {
        if (this.isDestroyed())
        {
            console.warn('Cannot play destroyed TweenChain', this);

            return this;
        }

        if (this.isPendingRemove() || this.isPending())
        {
            this.resetTweens();
        }

        this.paused = false;

        if (this.startDelay > 0 && !this.isStartDelayed())
        {
            this.setStartDelayState();
        }
        else
        {
            this.setActiveState();
        }

        return this;
    },

    /**
     * Internal method that resets all of the Tweens and the current index pointer.
     *
     * @method Phaser.Tweens.TweenChain#resetTweens
     * @since 3.60.0
     */
    resetTweens: function ()
    {
        var data = this.data;
        var total = this.totalData;

        for (var i = 0; i < total; i++)
        {
            data[i].reset(false);
        }

        this.currentIndex = 0;
        this.currentTween = data[0];
    },

    /**
     * Internal method that advances the TweenChain based on the time values.
     *
     * @method Phaser.Tweens.TweenChain#update
     * @fires Phaser.Tweens.Events#TWEEN_COMPLETE
     * @fires Phaser.Tweens.Events#TWEEN_LOOP
     * @fires Phaser.Tweens.Events#TWEEN_START
     * @since 3.60.0
     *
     * @param {number} delta - The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
     *
     * @return {boolean} Returns `true` if this TweenChain has finished and should be removed from the Tween Manager, otherwise returns `false`.
     */
    update: function (delta)
    {
        if (this.isPendingRemove() || this.isDestroyed())
        {
            return true;
        }
        else if (this.isFinished() || this.paused)
        {
            return false;
        }

        //  The TweehChain.timeScale is applied within Tween.update, so doesn't need including here
        delta *= this.parent.timeScale;

        if (this.isLoopDelayed())
        {
            this.updateLoopCountdown(delta);
        }
        else if (this.isCompleteDelayed())
        {
            this.updateCompleteDelay(delta);
        }
        else if (this.isStartDelayed())
        {
            //  Reset the delta so we always start progress from zero
            delta = this.updateStartCountdown(delta);
        }

        var remove = false;

        if (this.isActive() && this.currentTween)
        {
            if (this.currentTween.update(delta))
            {
                //  This tween has finshed playback, so move to the next one
                if (this.nextTween())
                {
                    this.nextState();
                }
            }

            //  if nextState called onCompleteHandler then we're ready to be removed, unless we persist
            remove = this.isPendingRemove();

            if (remove && this.persist)
            {
                this.setFinishedState();

                remove = false;
            }
        }

        return remove;
    },

    /**
     * Immediately advances to the next Tween in the chain.
     *
     * This is typically called internally, but can be used if you need to
     * advance playback for some reason.
     *
     * @method Phaser.Tweens.TweenChain#nextTween
     * @since 3.60.0
     *
     * @return {boolean} `true` if there are no more Tweens in the chain, otherwise `false`.
     */
    nextTween: function ()
    {
        this.currentIndex++;

        if (this.currentIndex === this.totalData)
        {
            return true;
        }
        else
        {
            this.setCurrentTween(this.currentIndex);
        }

        return false;
    },

    /**
     * Sets the current active Tween to the given index, based on its
     * entry in the TweenChain data array.
     *
     * @method Phaser.Tweens.TweenChain#setCurrentTween
     * @since 3.60.0
     *
     * @param {number} index - The index of the Tween to be made current.
     */
    setCurrentTween: function (index)
    {
        this.currentIndex = index;

        this.currentTween = this.data[index];

        this.currentTween.setActiveState();

        this.currentTween.dispatchEvent(Events.TWEEN_ACTIVE, 'onActive');
    },

    /**
     * Internal method that will emit a TweenChain based Event and invoke the given callback.
     *
     * @method Phaser.Tweens.TweenChain#dispatchEvent
     * @since 3.60.0
     *
     * @param {Phaser.Types.Tweens.Event} event - The Event to be dispatched.
     * @param {Phaser.Types.Tweens.TweenCallbackTypes} [callback] - The name of the callback to be invoked. Can be `null` or `undefined` to skip invocation.
     */
    dispatchEvent: function (event, callback)
    {
        this.emit(event, this);

        var handler = this.callbacks[callback];

        if (handler)
        {
            handler.func.apply(handler.scope, [ this ].concat(handler.params));
        }
    },

    /**
     * Immediately destroys this TweenChain, nulling of all its references.
     *
     * @method Phaser.Tweens.TweenChain#destroy
     * @since 3.60.0
     */
    destroy: function ()
    {
        BaseTween.prototype.destroy.call(this);

        this.currentTween = null;
    }

});

/**
 * Creates a new TweenChain object and adds it to the Tween Manager.
 *
 * Note: This method will only be available if Tweens have been built into Phaser.
 *
 * @method Phaser.GameObjects.GameObjectFactory#tweenchain
 * @since 3.60.0
 *
 * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - The TweenChain configuration.
 *
 * @return {Phaser.Tweens.TweenChain} The TweenChain that was created.
 */
GameObjectFactory.register('tweenchain', function (config)
{
    return this.scene.sys.tweens.chain(config);
});

/**
 * Creates a new TweenChain object and returns it, without adding it to the Tween Manager.
 *
 * Note: This method will only be available if Tweens have been built into Phaser.
 *
 * @method Phaser.GameObjects.GameObjectCreator#tweenchain
 * @since 3.60.0
 *
 * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - The TweenChain configuration.
 *
 * @return {Phaser.Tweens.TweenChain} The TweenChain that was created.
 */
GameObjectCreator.register('tweenchain', function (config)
{
    return this.scene.sys.tweens.create(config);
});

module.exports = TweenChain;
<|endoftext|>


<commit_before>var LastResortWebKitFontWatchRunnerTest =
    TestCase('LastResortWebKitFontWatchRunnerTest');

LastResortWebKitFontWatchRunnerTest.prototype.setUp = function() {
  var self = this;

  this.fontFamily_ = 'fontFamily1';
  this.fontDescription_ = 'n4';

  this.fontActiveCalled_ = 0;
  this.fontActive_ = {};
  this.fontInactiveCalled_ = 0;
  this.fontInactive_ = {};
  this.activeCallback_ = function(fontFamily, fontDescription) {
    self.fontActiveCalled_++;
    self.fontActive_[fontFamily + ' ' + fontDescription] = true;
  };
  this.inactiveCallback_ = function(fontFamily, fontDescription) {
    self.fontInactiveCalled_++;
    self.fontInactive_[fontFamily + ' ' + fontDescription] = true;
  };

  this.createElementCalled_ = 0;
  this.createdElements_ = [];
  this.insertIntoCalled_ = 0;
  this.removeElementCalled_ = 0;
  this.setStyleCalled_ = 0;
  this.fakeDomHelper_ = {
    createElement: function(name, attrs, innerHtml) {
      self.createElementCalled_++;
      self.createdElements_.push({
        'name': name,
        'attrs': attrs,
        'innerHtml': innerHtml
      });

      var element = document.createElement(name);

      for (var attr in attrs) {
        element.setAttribute(attr, attrs[attr]);
      }
      element.innerHTML = innerHtml;
      return element;
    },
    insertInto: function(name, el) {
      self.insertIntoCalled_++;
    },
    removeElement: function(el) {
      self.removeElementCalled_++;
    },
    setStyle: function(e, s) {
      self.setStyleCalled_++;
    }
  };

  this.timesToGetTimeBeforeTimeout_ = 10;
  this.fakeGetTime_ = function() {
    if (self.timesToGetTimeBeforeTimeout_ <= 0) {
      return 6000;
    } else {
      self.timesToGetTimeBeforeTimeout_--;
      return 1;
    }
  };

  this.asyncCount_ = 0;
  this.fakeAsyncCall_ = function(func, timeout) {
    self.asyncCount_++;
    func();
  };
};

LastResortWebKitFontWatchRunnerTest.prototype.testLastResortFontIgnored =
    function() {
  var lastResortFontsCount = 11;
  var originalSizeCount = 2;
  var firstSize = 2;
  var secondSize = 2;
  var thirdSize = 2;

  var fontWatchRunner = new webfont.LastResortWebKitFontWatchRunner(
      this.activeCallback_,
      this.inactiveCallback_,
      this.fakeDomHelper_, {getWidth: function() {
        if (lastResortFontsCount > 0) {
	  lastResortFontsCount--;
	  return 2;
	}
	if (originalSizeCount > 0) {
	  originalSizeCount--;
	  return 1;
	}
        if (firstSize > 0) {
	  firstSize--;
	  return 1;
	}
        if (secondSize > 0) {
	  secondSize--;
	  return 2;
	}
        if (thirdSize > 0) {
	  thirdSize--;
	  return 3;
	}
      }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
      this.fontDescription_);

  fontWatchRunner.start();

  assertEquals(2, this.asyncCount_);

  // When on webkit time out ends up activating the font.
  assertEquals(1, this.fontActiveCalled_);
  assertEquals(0, this.fontInactiveCalled_);
  assertEquals(true, this.fontActive_['fontFamily1 n4']);
};

LastResortWebKitFontWatchRunnerTest.prototype.testLastResortFontActiveWhenSizeMatch
    = function() {
  this.timesToGetTimeBeforeTimeout_ = 3;
  var lastResortFontsCount = 11;
  var originalSizeCount = 2;
  var firstSize = 2;

  this.fontFamily_ = "Arimo";

  var fontWatchRunner = new webfont.LastResortWebKitFontWatchRunner(
      this.activeCallback_,
      this.inactiveCallback_,
      this.fakeDomHelper_, {getWidth: function() {
        if (lastResortFontsCount > 0) {
          lastResortFontsCount--;
          return 2;
        }
        if (originalSizeCount > 0) {
          originalSizeCount--;
          return 1;
        }
        if (firstSize > 0) {
          firstSize--;
          return 1;
        }
        return 2;
      }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
      this.fontDescription_);

  fontWatchRunner.start();

  assertEquals(2, this.asyncCount_);

  assertEquals(1, this.fontActiveCalled_);
  assertEquals(0, this.fontInactiveCalled_);
  assertEquals(true, this.fontActive_['Arimo n4']);
};

LastResortWebKitFontWatchRunnerTest.prototype.testLastResortFontInactiveWhenSizeNoMatch
    = function() {
  this.timesToGetTimeBeforeTimeout_ = 3;
  var lastResortFontsCount = 11;
  var originalSizeCount = 2;
  var firstSize = 2;
  var secondSize = 2;
  var thirdSize = 2;

  var fontWatchRunner = new webfont.LastResortWebKitFontWatchRunner(
      this.activeCallback_,
      this.inactiveCallback_,
      this.fakeDomHelper_, {getWidth: function(elem) {
        if (lastResortFontsCount > 0) {
          lastResortFontsCount--;
          return 2;
        }
        if (originalSizeCount > 0) {
          originalSizeCount--;
          return 1;
        }
        if (firstSize > 0) {
          firstSize--;
          return 1;
        }
        if (secondSize > 0) {
          secondSize--;
          return 2;
        }
        if (thirdSize == 2) {
          thirdSize--;
          return 2;
        }
        if (thirdSize == 1) {
          thirdSize--;
          return 4;
        }
      }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
      this.fontDescription_);

  fontWatchRunner.start();

  assertEquals(2, this.asyncCount_);

  // When on webkit time out ends up activating the font.
  assertEquals(0, this.fontActiveCalled_);
  assertEquals(1, this.fontInactiveCalled_);
  assertEquals(true, this.fontInactive_['fontFamily1 n4']);
};
<commit_msg>Change the order in which we return the sizes since the setup of the last resort font moved.<commit_after>var LastResortWebKitFontWatchRunnerTest =
    TestCase('LastResortWebKitFontWatchRunnerTest');

LastResortWebKitFontWatchRunnerTest.prototype.setUp = function() {
  var self = this;

  this.fontFamily_ = 'fontFamily1';
  this.fontDescription_ = 'n4';

  this.fontActiveCalled_ = 0;
  this.fontActive_ = {};
  this.fontInactiveCalled_ = 0;
  this.fontInactive_ = {};
  this.activeCallback_ = function(fontFamily, fontDescription) {
    self.fontActiveCalled_++;
    self.fontActive_[fontFamily + ' ' + fontDescription] = true;
  };
  this.inactiveCallback_ = function(fontFamily, fontDescription) {
    self.fontInactiveCalled_++;
    self.fontInactive_[fontFamily + ' ' + fontDescription] = true;
  };

  this.createElementCalled_ = 0;
  this.createdElements_ = [];
  this.insertIntoCalled_ = 0;
  this.removeElementCalled_ = 0;
  this.setStyleCalled_ = 0;
  this.fakeDomHelper_ = {
    createElement: function(name, attrs, innerHtml) {
      self.createElementCalled_++;
      self.createdElements_.push({
        'name': name,
        'attrs': attrs,
        'innerHtml': innerHtml
      });

      var element = document.createElement(name);

      for (var attr in attrs) {
        element.setAttribute(attr, attrs[attr]);
      }
      element.innerHTML = innerHtml;
      return element;
    },
    insertInto: function(name, el) {
      self.insertIntoCalled_++;
    },
    removeElement: function(el) {
      self.removeElementCalled_++;
    },
    setStyle: function(e, s) {
      self.setStyleCalled_++;
    }
  };

  this.timesToGetTimeBeforeTimeout_ = 10;
  this.fakeGetTime_ = function() {
    if (self.timesToGetTimeBeforeTimeout_ <= 0) {
      return 6000;
    } else {
      self.timesToGetTimeBeforeTimeout_--;
      return 1;
    }
  };

  this.asyncCount_ = 0;
  this.fakeAsyncCall_ = function(func, timeout) {
    self.asyncCount_++;
    func();
  };
};

LastResortWebKitFontWatchRunnerTest.prototype.testLastResortFontIgnored =
    function() {
  var originalSizeCount = 2;
  var lastResortFontsCount = 11;
  var firstSize = 2;
  var secondSize = 2;
  var thirdSize = 2;

  var fontWatchRunner = new webfont.LastResortWebKitFontWatchRunner(
      this.activeCallback_,
      this.inactiveCallback_,
      this.fakeDomHelper_, {getWidth: function() {
	if (originalSizeCount > 0) {
	  originalSizeCount--;
	  return 1;
	}
        if (lastResortFontsCount > 0) {
	  lastResortFontsCount--;
	  return 2;
	}
        if (firstSize > 0) {
	  firstSize--;
	  return 1;
	}
        if (secondSize > 0) {
	  secondSize--;
	  return 2;
	}
        if (thirdSize > 0) {
	  thirdSize--;
	  return 3;
	}
      }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
      this.fontDescription_);

  fontWatchRunner.start();

  assertEquals(2, this.asyncCount_);

  // When on webkit time out ends up activating the font.
  assertEquals(1, this.fontActiveCalled_);
  assertEquals(0, this.fontInactiveCalled_);
  assertEquals(true, this.fontActive_['fontFamily1 n4']);
};

LastResortWebKitFontWatchRunnerTest.prototype.testLastResortFontActiveWhenSizeMatch
    = function() {
  this.timesToGetTimeBeforeTimeout_ = 3;
  var originalSizeCount = 2;
  var lastResortFontsCount = 11;
  var firstSize = 2;

  this.fontFamily_ = "Arimo";

  var fontWatchRunner = new webfont.LastResortWebKitFontWatchRunner(
      this.activeCallback_,
      this.inactiveCallback_,
      this.fakeDomHelper_, {getWidth: function() {
        if (originalSizeCount > 0) {
          originalSizeCount--;
          return 1;
        }
        if (lastResortFontsCount > 0) {
          lastResortFontsCount--;
          return 2;
        }
        if (firstSize > 0) {
          firstSize--;
          return 1;
        }
        return 2;
      }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
      this.fontDescription_);

  fontWatchRunner.start();

  assertEquals(2, this.asyncCount_);

  assertEquals(1, this.fontActiveCalled_);
  assertEquals(0, this.fontInactiveCalled_);
  assertEquals(true, this.fontActive_['Arimo n4']);
};

LastResortWebKitFontWatchRunnerTest.prototype.testLastResortFontInactiveWhenSizeNoMatch
    = function() {
  this.timesToGetTimeBeforeTimeout_ = 3;
  var originalSizeCount = 2;
  var lastResortFontsCount = 11;
  var firstSize = 2;
  var secondSize = 2;
  var thirdSize = 2;

  var fontWatchRunner = new webfont.LastResortWebKitFontWatchRunner(
      this.activeCallback_,
      this.inactiveCallback_,
      this.fakeDomHelper_, {getWidth: function(elem) {
        if (originalSizeCount > 0) {
          originalSizeCount--;
          return 1;
        }
        if (lastResortFontsCount > 0) {
          lastResortFontsCount--;
          return 2;
        }
        if (firstSize > 0) {
          firstSize--;
          return 1;
        }
        if (secondSize > 0) {
          secondSize--;
          return 2;
        }
        if (thirdSize == 2) {
          thirdSize--;
          return 2;
        }
        if (thirdSize == 1) {
          thirdSize--;
          return 4;
        }
      }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
      this.fontDescription_);

  fontWatchRunner.start();

  assertEquals(2, this.asyncCount_);

  // When on webkit time out ends up activating the font.
  assertEquals(0, this.fontActiveCalled_);
  assertEquals(1, this.fontInactiveCalled_);
  assertEquals(true, this.fontInactive_['fontFamily1 n4']);
};
<|endoftext|>


<commit_before>/*global describe, it, expect, example, before, after, beforeEach, afterEach, mocha, sinon*/
'use strict';
var assert = require('chai').assert;
var $osf = require('js/osfHelpers');
var utils = require('./utils');

var registrationRetraction = require('js/registrationRetraction');

// Add sinon asserts to chai.assert, so we can do assert.calledWith instead of sinon.assert.calledWith
sinon.assert.expose(assert, {prefix: ''});

describe('registrationRetraction', () => {
    describe('ViewModels', () => {

        describe('RegistrationRetractionViewModel', () => {
            var vm;
            var registrationTitle = 'This is a fake registration';
            var invalidConfirmationText = 'abcd';
            var submitUrl = '/project/abcdef/retraction/';
            var redirectUrl = '/project/abdef/';
            var response = {redirectUrl: redirectUrl};
            var endpoints = [
                {
                    method: 'POST',
                    url: submitUrl,
                    response: response
                }
            ];
            var server;

            before(() => {
                server = utils.createServer(sinon, endpoints);
            });

            after(() => {
                server.restore();
            });

            beforeEach(() => {
                vm = new registrationRetraction.ViewModel(submitUrl, registrationTitle);
            });

            it('non-matching registration title is invalid', () => {
                vm.confirmationText(invalidConfirmationText);
                assert.isFalse(vm.confirmationText.isValid());
            });

            it('matching registration title is valid', () => {
                 vm.confirmationText(registrationTitle);
                assert.isTrue(vm.confirmationText.isValid());
            });

            describe('submit', () => {
                var growlSpy;
                var postSpy;

                beforeEach(() => {
                    growlSpy = new sinon.stub($osf, 'growl');
                    postSpy = new sinon.spy($osf, 'postJSON');
                });

                afterEach(() => {
                    growlSpy.restore();
                    postSpy.restore();
                });

                it('calls growl if invalid confirmation text submitted', () => {
                    vm.confirmationText(invalidConfirmationText);
                    vm.submit();
                    assert.calledOnce(growlSpy);
                    assert.notCalled(postSpy);
                });
                it('submits successfully with valid confirmation text', (done) => {
                    var onSubmitSuccessStub = new sinon.stub(vm, 'onSubmitSuccess');

                    vm.confirmationText(registrationTitle);
                    vm.submit().done(() => {
                        assert.equal(response, redirectUrl)
                        assert.called(onSubmitSuccessStub);
                        assert.called(postSpy);
                        assert.notCalled(growlSpy);
                    });
                    onSubmitSuccessStub.restore();
                    done();
                });
            });
        });
    });
});

<commit_msg>Wasn't testing submit errors<commit_after>/*global describe, it, expect, example, before, after, beforeEach, afterEach, mocha, sinon*/
'use strict';
var assert = require('chai').assert;
var $osf = require('js/osfHelpers');
var utils = require('./utils');
var Raven = require('raven-js');

var registrationRetraction = require('js/registrationRetraction');

// Add sinon asserts to chai.assert, so we can do assert.calledWith instead of sinon.assert.calledWith
sinon.assert.expose(assert, {prefix: ''});

describe('registrationRetraction', () => {
    describe('ViewModels', () => {

        describe('RegistrationRetractionViewModel', () => {
            var vm;
            var registrationTitle = 'This is a fake registration';
            var invalidConfirmationText = 'abcd';
            var submitUrl = '/project/abcdef/retraction/';
            var invalidSubmitUrl = '/notAnEndpoint/';
            var redirectUrl = '/project/abdef/';
            var response = {redirectUrl: redirectUrl};
            var endpoints = [
                {
                    method: 'POST',
                    url: submitUrl,
                    response: response
                },
                {
                    method: 'POST',
                    url: invalidConfirmationText,
                    response: {status: 500}
                }

            ];
            var server;

            before(() => {
                server = utils.createServer(sinon, endpoints);
            });

            after(() => {
                server.restore();
            });

            beforeEach(() => {
                vm = new registrationRetraction.ViewModel(submitUrl, registrationTitle);
            });

            it('non-matching registration title is invalid', () => {
                vm.confirmationText(invalidConfirmationText);
                assert.isFalse(vm.confirmationText.isValid());
            });

            it('matching registration title is valid', () => {
                 vm.confirmationText(registrationTitle);
                assert.isTrue(vm.confirmationText.isValid());
            });

            describe('submit', () => {
                var growlSpy;
                var postSpy;

                beforeEach(() => {
                    growlSpy = new sinon.stub($osf, 'growl');
                    postSpy = new sinon.spy($osf, 'postJSON');
                });

                afterEach(() => {
                    growlSpy.restore();
                    postSpy.restore();
                });

                it('calls growl if invalid confirmation text submitted', () => {
                    vm.confirmationText(invalidConfirmationText);
                    vm.submit();
                    assert.calledOnce(growlSpy);
                    assert.notCalled(postSpy);
                });
                it('submits successfully with valid confirmation text', (done) => {
                    var onSubmitSuccessStub = new sinon.stub(vm, 'onSubmitSuccess');

                    vm.confirmationText(registrationTitle);
                    vm.submit().done(() => {
                        assert.equal(response, redirectUrl);
                        assert.called(onSubmitSuccessStub);
                        assert.called(postSpy);
                        assert.notCalled(growlSpy);
                    });

                    onSubmitSuccessStub.restore();
                    done();
                });
                it('logs error with Raven if submit fails', (done) => {
                    vm = new registrationRetraction.ViewModel(invalidSubmitUrl, registrationTitle);
                    var onSubmitErrorSpy = new sinon.spy(vm, 'onSubmitError');
                    var ravenStub = new sinon.stub(Raven, 'captureMessage');

                    vm.confirmationText(registrationTitle);

                    vm.submit().done(() => {
                        assert.equal(response, redirectUrl);
                        assert.called(onSubmitErrorSpy);
                        assert.called(postSpy);
                        assert.notCalled(growlSpy);
                    });

                    onSubmitErrorSpy.restore();
                    ravenStub.restore();
                    done();
                });
            });
        });
    });
});

<|endoftext|>


<commit_before>var abp = abp || {};
(function () {

    /* Application paths *****************************************/

    //Current application root path (including virtual directory if exists).
    abp.appPath = abp.appPath || '/';

    //Converts given path to absolute path using abp.appPath variable.
    abp.toAbsAppPath = function (path) {
        if (path.indexOf('/') == 0) {
            path = path.substring(1);
        }

        return abp.appPath + path;
    };

    /* LOCALIZATION ***********************************************/
    //Implements Localization API that simplifies usage of localization scripts generated by Abp.

    abp.localization = abp.localization || {};

    abp.localization.localize = function (key, sourceName) {
        sourceName = sourceName || abp.localization.defaultSourceName;

        var source = abp.localization.values[sourceName];

        if (!source) {
            abp.log.warn('Could not find localization source: ' + sourceName);
            return key;
        }

        var value = source[key];
        if (value == undefined) {
            return key;
        }

        var copiedArguments = Array.prototype.slice.call(arguments, 0);
        copiedArguments.splice(1, 1);
        copiedArguments[0] = value;

        return abp.utils.formatString.apply(this, copiedArguments);
    };

    abp.localization.getSource = function (sourceName) {
        return function (key) {
            var copiedArguments = Array.prototype.slice.call(arguments, 0);
            copiedArguments.splice(1, 0, sourceName);
            return abp.localization.localize.apply(this, copiedArguments);
        };
    };

    abp.localization.isCurrentCulture = function (name) {
        return abp.localization.currentCulture
            && abp.localization.currentCulture.name
            && abp.localization.currentCulture.name.indexOf(name) == 0;
    };

    abp.localization.defaultSourceName = undefined;
    abp.localization.abpWeb = abp.localization.getSource('AbpWeb');

    /* AUTHORIZATION **********************************************/
    //Implements Authorization API that simplifies usage of authorization scripts generated by Abp.

    abp.auth = abp.auth || {};

    abp.auth.allPermissions = abp.auth.allPermissions || {};

    abp.auth.grantedPermissions = abp.auth.grantedPermissions || {};

    abp.auth.hasPermission = function (permissionName) {
        return abp.auth.allPermissions[permissionName] != undefined && abp.auth.grantedPermissions[permissionName] != undefined;
    };

    abp.auth.hasAnyOfPermissions = function () {
        if (!arguments || arguments.length <= 0) {
            return true;
        }

        for (var i = 0; i < arguments.length; i++) {
            if (abp.auth.hasPermission(arguments[i])) {
                return true;
            }
        }

        return false;
    };

    abp.auth.hasAllOfPermissions = function () {
        if (!arguments || arguments.length <= 0) {
            return true;
        }

        for (var i = 0; i < arguments.length; i++) {
            if (!abp.auth.hasPermission(arguments[i])) {
                return false;
            }
        }

        return true;
    };

    /* SETTINGS **************************************************/
    //Implements Settings API that simplifies usage of setting scripts generated by Abp.

    abp.setting = abp.setting || {};

    abp.setting.values = abp.setting.values || {};

    abp.setting.get = function (name) {
        return abp.setting.values[name];
    };

    abp.setting.getBoolean = function (name) {
        return abp.setting.values[name] == 'true';
    };

    abp.setting.getInt = function (name) {
        return parseInt(abp.setting.values[name]);
    };

    /* LOGGING ***************************************************/
    //Implements Logging API that provides secure & controlled usage of console.log

    abp.log = abp.log || {};

    abp.log.levels = {
        DEBUG: 1,
        INFO: 2,
        WARN: 3,
        ERROR: 4,
        FATAL: 5
    };

    abp.log.level = abp.log.levels.DEBUG;

    abp.log.log = function (logObject, logLevel) {
        if (!window.console || !window.console.log) {
            return;
        }

        if (logLevel != undefined && logLevel < abp.log.level) {
            return;
        }

        console.log(logObject);
    };

    abp.log.debug = function (logObject) {
        abp.log.log("DEBUG: ", abp.log.levels.DEBUG);
        abp.log.log(logObject, abp.log.levels.DEBUG);
    };

    abp.log.info = function (logObject) {
        abp.log.log("INFO: ", abp.log.levels.INFO);
        abp.log.log(logObject, abp.log.levels.INFO);
    };

    abp.log.warn = function (logObject) {
        abp.log.log("WARN: ", abp.log.levels.WARN);
        abp.log.log(logObject, abp.log.levels.WARN);
    };

    abp.log.error = function (logObject) {
        abp.log.log("ERROR: ", abp.log.levels.ERROR);
        abp.log.log(logObject, abp.log.levels.ERROR);
    };

    abp.log.fatal = function (logObject) {
        abp.log.log("FATAL: ", abp.log.levels.FATAL);
        abp.log.log(logObject, abp.log.levels.FATAL);
    };

    /* NOTIFICATION *********************************************/
    //Defines Notification API, not implements it

    abp.notify = abp.notify || {};

    abp.notify.success = function (message, title) {
        abp.log.warn('abp.notify.success is not implemented!');
    };

    abp.notify.info = function (message, title) {
        abp.log.warn('abp.notify.info is not implemented!');
    };

    abp.notify.warn = function (message, title) {
        abp.log.warn('abp.notify.warn is not implemented!');
    };

    abp.notify.error = function (message, title) {
        abp.log.warn('abp.notify.error is not implemented!');
    };

    /* MESSAGE **************************************************/
    //Defines Message API, not implements it

    abp.message = abp.message || {};

    var showMessage = function (message, title) {
        alert((title || '') + ' ' + message);

        if (!$) {
            abp.log.warn('abp.message can not return promise since jQuery is not defined!');
            return null;
        }

        return $.Deferred(function ($dfd) {
            $dfd.resolve();
        });
    };

    abp.message.info = function (message, title) {
        abp.log.warn('abp.message.info is not implemented!');
        return showMessage(message, title);
    };

    abp.message.success = function (message, title) {
        abp.log.warn('abp.message.success is not implemented!');
        return showMessage(message, title);
    };

    abp.message.warn = function (message, title) {
        abp.log.warn('abp.message.warn is not implemented!');
        return showMessage(message, title);
    };

    abp.message.error = function (message, title) {
        abp.log.warn('abp.message.error is not implemented!');
        return showMessage(message, title);
    };

    abp.message.confirm = function (message, titleOrCallback, callback) {
        abp.log.warn('abp.message.confirm is not implemented!');

        if (titleOrCallback && !(typeof titleOrCallback == 'string')) {
            callback = titleOrCallback;
        }

        var result = confirm(message);
        callback && callback(result);

        if (!$) {
            abp.log.warn('abp.message can not return promise since jQuery is not defined!');
            return null;
        }

        return $.Deferred(function ($dfd) {
            $dfd.resolve();
        });
    };

    /* UI *******************************************************/

    abp.ui = abp.ui || {};

    /* UI BLOCK */
    //Defines UI Block API, not implements it

    abp.ui.block = function (elm) {
        abp.log.warn('abp.ui.block is not implemented!');
    };

    abp.ui.unblock = function (elm) {
        abp.log.warn('abp.ui.unblock is not implemented!');
    };

    /* UI BUSY */
    //Defines UI Busy API, not implements it

    abp.ui.setBusy = function (elm, optionsOrPromise) {
        abp.log.warn('abp.ui.setBusy is not implemented!');
    };

    abp.ui.clearBusy = function (elm) {
        abp.log.warn('abp.ui.clearBusy is not implemented!');
    };

    /* SIMPLE EVENT BUS *****************************************/

    abp.event = (function() {

        var _callbacks = {};

        var on = function(eventName, callback) {
            if (!_callbacks[eventName]) {
                _callbacks[eventName] = [];
            }

            _callbacks[eventName].push(callback);
        };

        var trigger = function(eventName) {
            var callbacks = _callbacks[eventName];
            if (!callbacks || !callbacks.length) {
                return;
            }

            var args = Array.prototype.slice.call(arguments, 1);
            for (var i = 0; i < callbacks.length; i++) {
                callbacks[i].apply(this, args);
            }
        };

        // Public interface ///////////////////////////////////////////////////

        return {
            on: on,
            trigger: trigger
        };
    })();

    /* UTILS ***************************************************/

    abp.utils = abp.utils || {};

    /* Creates a name namespace.
    *  Example:
    *  var taskService = abp.utils.createNamespace(abp, 'services.task');
    *  taskService will be equal to abp.services.task
    *  first argument (root) must be defined first
    ************************************************************/
    abp.utils.createNamespace = function (root, ns) {
        var parts = ns.split('.');
        for (var i = 0; i < parts.length; i++) {
            if (typeof root[parts[i]] == 'undefined') {
                root[parts[i]] = {};
            }

            root = root[parts[i]];
        }

        return root;
    };

    /* Formats a string just like string.format in C#.
    *  Example:
    *  _formatString('Hello {0}','Halil') = 'Hello Halil'
    ************************************************************/
    abp.utils.formatString = function () {
        if (arguments.length < 1) {
            return null;
        }

        var str = arguments[0];

        for (var i = 1; i < arguments.length; i++) {
            var placeHolder = '{' + (i - 1) + '}';
            str = str.replace(placeHolder, arguments[i]);
        }

        return str;
    };

})();<commit_msg>Resolved #600: Create abp.pageLoadTime variable.<commit_after>var abp = abp || {};
(function () {

    /* Application paths *****************************************/

    //Current application root path (including virtual directory if exists).
    abp.appPath = abp.appPath || '/';

    abp.pageLoadTime = new Date();

    //Converts given path to absolute path using abp.appPath variable.
    abp.toAbsAppPath = function (path) {
        if (path.indexOf('/') == 0) {
            path = path.substring(1);
        }

        return abp.appPath + path;
    };

    /* LOCALIZATION ***********************************************/
    //Implements Localization API that simplifies usage of localization scripts generated by Abp.

    abp.localization = abp.localization || {};

    abp.localization.localize = function (key, sourceName) {
        sourceName = sourceName || abp.localization.defaultSourceName;

        var source = abp.localization.values[sourceName];

        if (!source) {
            abp.log.warn('Could not find localization source: ' + sourceName);
            return key;
        }

        var value = source[key];
        if (value == undefined) {
            return key;
        }

        var copiedArguments = Array.prototype.slice.call(arguments, 0);
        copiedArguments.splice(1, 1);
        copiedArguments[0] = value;

        return abp.utils.formatString.apply(this, copiedArguments);
    };

    abp.localization.getSource = function (sourceName) {
        return function (key) {
            var copiedArguments = Array.prototype.slice.call(arguments, 0);
            copiedArguments.splice(1, 0, sourceName);
            return abp.localization.localize.apply(this, copiedArguments);
        };
    };

    abp.localization.isCurrentCulture = function (name) {
        return abp.localization.currentCulture
            && abp.localization.currentCulture.name
            && abp.localization.currentCulture.name.indexOf(name) == 0;
    };

    abp.localization.defaultSourceName = undefined;
    abp.localization.abpWeb = abp.localization.getSource('AbpWeb');

    /* AUTHORIZATION **********************************************/
    //Implements Authorization API that simplifies usage of authorization scripts generated by Abp.

    abp.auth = abp.auth || {};

    abp.auth.allPermissions = abp.auth.allPermissions || {};

    abp.auth.grantedPermissions = abp.auth.grantedPermissions || {};

    abp.auth.hasPermission = function (permissionName) {
        return abp.auth.allPermissions[permissionName] != undefined && abp.auth.grantedPermissions[permissionName] != undefined;
    };

    abp.auth.hasAnyOfPermissions = function () {
        if (!arguments || arguments.length <= 0) {
            return true;
        }

        for (var i = 0; i < arguments.length; i++) {
            if (abp.auth.hasPermission(arguments[i])) {
                return true;
            }
        }

        return false;
    };

    abp.auth.hasAllOfPermissions = function () {
        if (!arguments || arguments.length <= 0) {
            return true;
        }

        for (var i = 0; i < arguments.length; i++) {
            if (!abp.auth.hasPermission(arguments[i])) {
                return false;
            }
        }

        return true;
    };

    /* SETTINGS **************************************************/
    //Implements Settings API that simplifies usage of setting scripts generated by Abp.

    abp.setting = abp.setting || {};

    abp.setting.values = abp.setting.values || {};

    abp.setting.get = function (name) {
        return abp.setting.values[name];
    };

    abp.setting.getBoolean = function (name) {
        return abp.setting.values[name] == 'true';
    };

    abp.setting.getInt = function (name) {
        return parseInt(abp.setting.values[name]);
    };

    /* LOGGING ***************************************************/
    //Implements Logging API that provides secure & controlled usage of console.log

    abp.log = abp.log || {};

    abp.log.levels = {
        DEBUG: 1,
        INFO: 2,
        WARN: 3,
        ERROR: 4,
        FATAL: 5
    };

    abp.log.level = abp.log.levels.DEBUG;

    abp.log.log = function (logObject, logLevel) {
        if (!window.console || !window.console.log) {
            return;
        }

        if (logLevel != undefined && logLevel < abp.log.level) {
            return;
        }

        console.log(logObject);
    };

    abp.log.debug = function (logObject) {
        abp.log.log("DEBUG: ", abp.log.levels.DEBUG);
        abp.log.log(logObject, abp.log.levels.DEBUG);
    };

    abp.log.info = function (logObject) {
        abp.log.log("INFO: ", abp.log.levels.INFO);
        abp.log.log(logObject, abp.log.levels.INFO);
    };

    abp.log.warn = function (logObject) {
        abp.log.log("WARN: ", abp.log.levels.WARN);
        abp.log.log(logObject, abp.log.levels.WARN);
    };

    abp.log.error = function (logObject) {
        abp.log.log("ERROR: ", abp.log.levels.ERROR);
        abp.log.log(logObject, abp.log.levels.ERROR);
    };

    abp.log.fatal = function (logObject) {
        abp.log.log("FATAL: ", abp.log.levels.FATAL);
        abp.log.log(logObject, abp.log.levels.FATAL);
    };

    /* NOTIFICATION *********************************************/
    //Defines Notification API, not implements it

    abp.notify = abp.notify || {};

    abp.notify.success = function (message, title) {
        abp.log.warn('abp.notify.success is not implemented!');
    };

    abp.notify.info = function (message, title) {
        abp.log.warn('abp.notify.info is not implemented!');
    };

    abp.notify.warn = function (message, title) {
        abp.log.warn('abp.notify.warn is not implemented!');
    };

    abp.notify.error = function (message, title) {
        abp.log.warn('abp.notify.error is not implemented!');
    };

    /* MESSAGE **************************************************/
    //Defines Message API, not implements it

    abp.message = abp.message || {};

    var showMessage = function (message, title) {
        alert((title || '') + ' ' + message);

        if (!$) {
            abp.log.warn('abp.message can not return promise since jQuery is not defined!');
            return null;
        }

        return $.Deferred(function ($dfd) {
            $dfd.resolve();
        });
    };

    abp.message.info = function (message, title) {
        abp.log.warn('abp.message.info is not implemented!');
        return showMessage(message, title);
    };

    abp.message.success = function (message, title) {
        abp.log.warn('abp.message.success is not implemented!');
        return showMessage(message, title);
    };

    abp.message.warn = function (message, title) {
        abp.log.warn('abp.message.warn is not implemented!');
        return showMessage(message, title);
    };

    abp.message.error = function (message, title) {
        abp.log.warn('abp.message.error is not implemented!');
        return showMessage(message, title);
    };

    abp.message.confirm = function (message, titleOrCallback, callback) {
        abp.log.warn('abp.message.confirm is not implemented!');

        if (titleOrCallback && !(typeof titleOrCallback == 'string')) {
            callback = titleOrCallback;
        }

        var result = confirm(message);
        callback && callback(result);

        if (!$) {
            abp.log.warn('abp.message can not return promise since jQuery is not defined!');
            return null;
        }

        return $.Deferred(function ($dfd) {
            $dfd.resolve();
        });
    };

    /* UI *******************************************************/

    abp.ui = abp.ui || {};

    /* UI BLOCK */
    //Defines UI Block API, not implements it

    abp.ui.block = function (elm) {
        abp.log.warn('abp.ui.block is not implemented!');
    };

    abp.ui.unblock = function (elm) {
        abp.log.warn('abp.ui.unblock is not implemented!');
    };

    /* UI BUSY */
    //Defines UI Busy API, not implements it

    abp.ui.setBusy = function (elm, optionsOrPromise) {
        abp.log.warn('abp.ui.setBusy is not implemented!');
    };

    abp.ui.clearBusy = function (elm) {
        abp.log.warn('abp.ui.clearBusy is not implemented!');
    };

    /* SIMPLE EVENT BUS *****************************************/

    abp.event = (function() {

        var _callbacks = {};

        var on = function(eventName, callback) {
            if (!_callbacks[eventName]) {
                _callbacks[eventName] = [];
            }

            _callbacks[eventName].push(callback);
        };

        var trigger = function(eventName) {
            var callbacks = _callbacks[eventName];
            if (!callbacks || !callbacks.length) {
                return;
            }

            var args = Array.prototype.slice.call(arguments, 1);
            for (var i = 0; i < callbacks.length; i++) {
                callbacks[i].apply(this, args);
            }
        };

        // Public interface ///////////////////////////////////////////////////

        return {
            on: on,
            trigger: trigger
        };
    })();

    /* UTILS ***************************************************/

    abp.utils = abp.utils || {};

    /* Creates a name namespace.
    *  Example:
    *  var taskService = abp.utils.createNamespace(abp, 'services.task');
    *  taskService will be equal to abp.services.task
    *  first argument (root) must be defined first
    ************************************************************/
    abp.utils.createNamespace = function (root, ns) {
        var parts = ns.split('.');
        for (var i = 0; i < parts.length; i++) {
            if (typeof root[parts[i]] == 'undefined') {
                root[parts[i]] = {};
            }

            root = root[parts[i]];
        }

        return root;
    };

    /* Formats a string just like string.format in C#.
    *  Example:
    *  _formatString('Hello {0}','Halil') = 'Hello Halil'
    ************************************************************/
    abp.utils.formatString = function () {
        if (arguments.length < 1) {
            return null;
        }

        var str = arguments[0];

        for (var i = 1; i < arguments.length; i++) {
            var placeHolder = '{' + (i - 1) + '}';
            str = str.replace(placeHolder, arguments[i]);
        }

        return str;
    };

})();<|endoftext|>