<commit_before>const dispatch = require('../dispatch/central-dispatch');
const log = require('../util/log');
const maybeFormatMessage = require('../util/maybe-format-message');

const BlockType = require('./block-type');

// These extensions are currently built into the VM repository but should not be loaded at startup.
// TODO: move these out into a separate repository?
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
const Scratch3PenBlocks = require('../extensions/scratch3_pen');
const Scratch3WeDo2Blocks = require('../extensions/scratch3_wedo2');
const Scratch3MusicBlocks = require('../extensions/scratch3_music');
const Scratch3MicroBitBlocks = require('../extensions/scratch3_microbit');
const Scratch3Text2SpeechBlocks = require('../extensions/scratch3_text2speech');
const Scratch3TranslateBlocks = require('../extensions/scratch3_translate');
const Scratch3VideoSensingBlocks = require('../extensions/scratch3_video_sensing');
const Scratch3Speech2TextBlocks = require('../extensions/scratch3_speech2text');
const Scratch3Ev3Blocks = require('../extensions/scratch3_ev3');
const Scratch3MakeyMakeyBlocks = require('../extensions/scratch3_makeymakey');
// todo: only load this extension once we have a compatible way to load its
// Vernier module dependency.
// const Scratch3GdxForBlocks = require('../extensions/scratch3_gdx_for');

const builtinExtensions = {
    pen: Scratch3PenBlocks,
    wedo2: Scratch3WeDo2Blocks,
    music: Scratch3MusicBlocks,
    microbit: Scratch3MicroBitBlocks,
    text2speech: Scratch3Text2SpeechBlocks,
    translate: Scratch3TranslateBlocks,
    videoSensing: Scratch3VideoSensingBlocks,
    speech2text: Scratch3Speech2TextBlocks,
    ev3: Scratch3Ev3Blocks,
    makeymakey: Scratch3MakeyMakeyBlocks
    // gdxfor: Scratch3GdxForBlocks
};

/**
 * @typedef {object} ArgumentInfo - Information about an extension block argument
 * @property {ArgumentType} type - the type of value this argument can take
 * @property {*|undefined} default - the default value of this argument (default: blank)
 */

/**
 * @typedef {object} ConvertedBlockInfo - Raw extension block data paired with processed data ready for scratch-blocks
 * @property {ExtensionBlockMetadata} info - the raw block info
 * @property {object} json - the scratch-blocks JSON definition for this block
 * @property {string} xml - the scratch-blocks XML definition for this block
 */

/**
 * @typedef {object} CategoryInfo - Information about a block category
 * @property {string} id - the unique ID of this category
 * @property {string} name - the human-readable name of this category
 * @property {string|undefined} blockIconURI - optional URI for the block icon image
 * @property {string} color1 - the primary color for this category, in '#rrggbb' format
 * @property {string} color2 - the secondary color for this category, in '#rrggbb' format
 * @property {string} color3 - the tertiary color for this category, in '#rrggbb' format
 * @property {Array.<ConvertedBlockInfo>} blocks - the blocks, separators, etc. in this category
 * @property {Array.<object>} menus - the menus provided by this category
 */

/**
 * @typedef {object} PendingExtensionWorker - Information about an extension worker still initializing
 * @property {string} extensionURL - the URL of the extension to be loaded by this worker
 * @property {Function} resolve - function to call on successful worker startup
 * @property {Function} reject - function to call on failed worker startup
 */

class ExtensionManager {
    constructor (runtime) {
        /**
         * The ID number to provide to the next extension worker.
         * @type {int}
         */
        this.nextExtensionWorker = 0;

        /**
         * FIFO queue of extensions which have been requested but not yet loaded in a worker,
         * along with promise resolution functions to call once the worker is ready or failed.
         *
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingExtensions = [];

        /**
         * Map of worker ID to workers which have been allocated but have not yet finished initialization.
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingWorkers = [];

        /**
         * Set of loaded extension URLs/IDs (equivalent for built-in extensions).
         * @type {Set.<string>}
         * @private
         */
        this._loadedExtensions = new Map();

        /**
         * Keep a reference to the runtime so we can construct internal extension objects.
         * TODO: remove this in favor of extensions accessing the runtime as a service.
         * @type {Runtime}
         */
        this.runtime = runtime;

        dispatch.setService('extensions', this).catch(e => {
            log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);
        });
    }

    /**
     * Check whether an extension is registered or is in the process of loading. This is intended to control loading or
     * adding extensions so it may return `true` before the extension is ready to be used. Use the promise returned by
     * `loadExtensionURL` if you need to wait until the extension is truly ready.
     * @param {string} extensionID - the ID of the extension.
     * @returns {boolean} - true if loaded, false otherwise.
     */
    isExtensionLoaded (extensionID) {
        return this._loadedExtensions.has(extensionID);
    }

    /**
     * Load an extension by URL or internal extension ID
     * @param {string} extensionURL - the URL for the extension to load OR the ID of an internal extension
     * @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure
     */
    loadExtensionURL (extensionURL) {
        if (builtinExtensions.hasOwnProperty(extensionURL)) {
            /** @TODO dupe handling for non-builtin extensions. See commit 670e51d33580e8a2e852b3b038bb3afc282f81b9 */
            if (this.isExtensionLoaded(extensionURL)) {
                const message = `Rejecting attempt to load a second extension with ID ${extensionURL}`;
                log.warn(message);
                return Promise.reject(new Error(message));
            }

            const extension = builtinExtensions[extensionURL];
            const extensionInstance = new extension(this.runtime);
            return this._registerInternalExtension(extensionInstance).then(serviceName => {
                this._loadedExtensions.set(extensionURL, serviceName);
            });
        }

        return new Promise((resolve, reject) => {
            // If we `require` this at the global level it breaks non-webpack targets, including tests
            const ExtensionWorker = require('worker-loader?name=extension-worker.js!./extension-worker');

            this.pendingExtensions.push({extensionURL, resolve, reject});
            dispatch.addWorker(new ExtensionWorker());
        });
    }

    /**
     * Regenerate blockinfo for any loaded extensions
     * @returns {Promise} resolved once all the extensions have been reinitialized
     */
    refreshBlocks () {
        const allPromises = Array.from(this._loadedExtensions.values()).map(serviceName =>
            dispatch.call(serviceName, 'getInfo')
                .then(info => {
                    info = this._prepareExtensionInfo(serviceName, info);
                    dispatch.call('runtime', '_refreshExtensionPrimitives', info);
                })
                .catch(e => {
                    log.error(`Failed to refresh buildtin extension primitives: ${JSON.stringify(e)}`);
                })
        );
        return Promise.all(allPromises);
    }

    allocateWorker () {
        const id = this.nextExtensionWorker++;
        const workerInfo = this.pendingExtensions.shift();
        this.pendingWorkers[id] = workerInfo;
        return [id, workerInfo.extensionURL];
    }

    /**
     * Collect extension metadata from the specified service and begin the extension registration process.
     * @param {string} serviceName - the name of the service hosting the extension.
     */
    registerExtensionService (serviceName) {
        dispatch.call(serviceName, 'getInfo').then(info => {
            this._registerExtensionInfo(serviceName, info);
        });
    }

    /**
     * Called by an extension worker to indicate that the worker has finished initialization.
     * @param {int} id - the worker ID.
     * @param {*?} e - the error encountered during initialization, if any.
     */
    onWorkerInit (id, e) {
        const workerInfo = this.pendingWorkers[id];
        delete this.pendingWorkers[id];
        if (e) {
            workerInfo.reject(e);
        } else {
            workerInfo.resolve(id);
        }
    }

    /**
     * Register an internal (non-Worker) extension object
     * @param {object} extensionObject - the extension object to register
     * @returns {Promise} resolved once the extension is fully registered or rejected on failure
     */
    _registerInternalExtension (extensionObject) {
        const extensionInfo = extensionObject.getInfo();
        const fakeWorkerId = this.nextExtensionWorker++;
        const serviceName = `extension_${fakeWorkerId}_${extensionInfo.id}`;
        return dispatch.setService(serviceName, extensionObject)
            .then(() => {
                dispatch.call('extensions', 'registerExtensionService', serviceName);
                return serviceName;
            });
    }

    /**
     * Sanitize extension info then register its primitives with the VM.
     * @param {string} serviceName - the name of the service hosting the extension
     * @param {ExtensionInfo} extensionInfo - the extension's metadata
     * @private
     */
    _registerExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo);
        dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => {
            log.error(`Failed to register primitives for extension on service ${serviceName}:`, e);
        });
    }

    /**
     * Modify the provided text as necessary to ensure that it may be used as an attribute value in valid XML.
     * @param {string} text - the text to be sanitized
     * @returns {string} - the sanitized text
     * @private
     */
    _sanitizeID (text) {
        return text.toString().replace(/[<"&]/, '_');
    }

    /**
     * Apply minor cleanup and defaults for optional extension fields.
     * TODO: make the ID unique in cases where two copies of the same extension are loaded.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionInfo} extensionInfo - the extension info to be sanitized
     * @returns {ExtensionInfo} - a new extension info object with cleaned-up values
     * @private
     */
    _prepareExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = Object.assign({}, extensionInfo);
        if (!/^[a-z0-9]+$/i.test(extensionInfo.id)) {
            throw new Error('Invalid extension id');
        }
        extensionInfo.name = extensionInfo.name || extensionInfo.id;
        extensionInfo.blocks = extensionInfo.blocks || [];
        extensionInfo.targetTypes = extensionInfo.targetTypes || [];
        extensionInfo.blocks = extensionInfo.blocks.reduce((results, blockInfo) => {
            try {
                let result;
                switch (blockInfo) {
                case '---': // separator
                    result = '---';
                    break;
                default: // an ExtensionBlockMetadata object
                    result = this._prepareBlockInfo(serviceName, blockInfo);
                    break;
                }
                results.push(result);
            } catch (e) {
                // TODO: more meaningful error reporting
                log.error(`Error processing block: ${e.message}, Block:\n${JSON.stringify(blockInfo)}`);
            }
            return results;
        }, []);
        extensionInfo.menus = extensionInfo.menus || [];
        extensionInfo.menus = this._prepareMenuInfo(serviceName, extensionInfo.menus);
        return extensionInfo;
    }

    /**
     * Prepare extension menus. e.g. setup binding for dynamic menu functions.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {Array.<MenuInfo>} menus - the menu defined by the extension.
     * @returns {Array.<MenuInfo>} - a menuInfo object with all preprocessing done.
     * @private
     */
    _prepareMenuInfo (serviceName, menus) {
        const menuNames = Object.getOwnPropertyNames(menus);
        for (let i = 0; i < menuNames.length; i++) {
            const item = menuNames[i];
            // If the value is a string, it should be the name of a function in the
            // extension object to call to populate the menu whenever it is opened.
            // Set up the binding for the function object here so
            // we can use it later when converting the menu for Scratch Blocks.
            if (typeof menus[item] === 'string') {
                const serviceObject = dispatch.services[serviceName];
                const menuName = menus[item];
                menus[item] = this._getExtensionMenuItems.bind(this, serviceObject, menuName);
            }
        }
        return menus;
    }

    /**
     * Fetch the items for a particular extension menu, providing the target ID for context.
     * @param {object} extensionObject - the extension object providing the menu.
     * @param {string} menuName - the name of the menu function to call.
     * @returns {Array} menu items ready for scratch-blocks.
     * @private
     */
    _getExtensionMenuItems (extensionObject, menuName) {
        // Fetch the items appropriate for the target currently being edited. This assumes that menus only
        // collect items when opened by the user while editing a particular target.
        const editingTarget = this.runtime.getEditingTarget() || this.runtime.getTargetForStage();
        const editingTargetID = editingTarget ? editingTarget.id : null;
        const extensionMessageContext = this.runtime.makeMessageContextForTarget(editingTarget);

        // TODO: Fix this to use dispatch.call when extensions are running in workers.
        const menuFunc = extensionObject[menuName];
        const menuItems = menuFunc.call(extensionObject, editingTargetID).map(
            item => {
                item = maybeFormatMessage(item, extensionMessageContext);
                switch (typeof item) {
                case 'object':
                    return [
                        maybeFormatMessage(item.text, extensionMessageContext),
                        item.value
                    ];
                case 'string':
                    return [item, item];
                default:
                    return item;
                }
            });

        if (!menuItems || menuItems.length < 1) {
            throw new Error(`Extension menu returned no items: ${menuName}`);
        }
        return menuItems;
    }

    /**
     * Apply defaults for optional block fields.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionBlockMetadata} blockInfo - the block info from the extension
     * @returns {ExtensionBlockMetadata} - a new block info object which has values for all relevant optional fields.
     * @private
     */
    _prepareBlockInfo (serviceName, blockInfo) {
        blockInfo = Object.assign({}, {
            blockType: BlockType.COMMAND,
            terminal: false,
            blockAllThreads: false,
            arguments: {}
        }, blockInfo);
        blockInfo.opcode = this._sanitizeID(blockInfo.opcode);
        blockInfo.text = blockInfo.text || blockInfo.opcode;

        if (blockInfo.blockType !== BlockType.EVENT) {
            blockInfo.func = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;

            /**
             * This is only here because the VM performs poorly when blocks return promises.
             * @TODO make it possible for the VM to resolve a promise and continue during the same Scratch "tick"
             */
            if (dispatch._isRemoteService(serviceName)) {
                blockInfo.func = dispatch.call.bind(dispatch, serviceName, blockInfo.func);
            } else {
                const serviceObject = dispatch.services[serviceName];
                const func = serviceObject[blockInfo.func];
                if (func) {
                    blockInfo.func = func.bind(serviceObject);
                } else if (blockInfo.blockType !== BlockType.EVENT) {
                    throw new Error(`Could not find extension block function called ${blockInfo.func}`);
                }
            }
        } else if (blockInfo.func) {
            log.warn(`Ignoring function "${blockInfo.func}" for event block ${blockInfo.opcode}`);
        }

        return blockInfo;
    }
}

module.exports = ExtensionManager;
<commit_msg>delay extension evaluation until that extension is installed<commit_after>const dispatch = require('../dispatch/central-dispatch');
const log = require('../util/log');
const maybeFormatMessage = require('../util/maybe-format-message');

const BlockType = require('./block-type');

// These extensions are currently built into the VM repository but should not be loaded at startup.
// TODO: move these out into a separate repository?
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods

const builtinExtensions = {
    pen: () => require('../extensions/scratch3_pen'),
    wedo2: () => require('../extensions/scratch3_wedo2'),
    music: () => require('../extensions/scratch3_music'),
    microbit: () => require('../extensions/scratch3_microbit'),
    text2speech: () => require('../extensions/scratch3_text2speech'),
    translate: () => require('../extensions/scratch3_translate'),
    videoSensing: () => require('../extensions/scratch3_video_sensing'),
    speech2text: () => require('../extensions/scratch3_speech2text'),
    ev3: () => require('../extensions/scratch3_ev3'),
    makeymakey: () => require('../extensions/scratch3_makeymakey')
    // todo: only load this extension once we have a compatible way to load its
    // Vernier module dependency.
    // gdxfor: () => require('../extensions/scratch3_gdx_for')
};

/**
 * @typedef {object} ArgumentInfo - Information about an extension block argument
 * @property {ArgumentType} type - the type of value this argument can take
 * @property {*|undefined} default - the default value of this argument (default: blank)
 */

/**
 * @typedef {object} ConvertedBlockInfo - Raw extension block data paired with processed data ready for scratch-blocks
 * @property {ExtensionBlockMetadata} info - the raw block info
 * @property {object} json - the scratch-blocks JSON definition for this block
 * @property {string} xml - the scratch-blocks XML definition for this block
 */

/**
 * @typedef {object} CategoryInfo - Information about a block category
 * @property {string} id - the unique ID of this category
 * @property {string} name - the human-readable name of this category
 * @property {string|undefined} blockIconURI - optional URI for the block icon image
 * @property {string} color1 - the primary color for this category, in '#rrggbb' format
 * @property {string} color2 - the secondary color for this category, in '#rrggbb' format
 * @property {string} color3 - the tertiary color for this category, in '#rrggbb' format
 * @property {Array.<ConvertedBlockInfo>} blocks - the blocks, separators, etc. in this category
 * @property {Array.<object>} menus - the menus provided by this category
 */

/**
 * @typedef {object} PendingExtensionWorker - Information about an extension worker still initializing
 * @property {string} extensionURL - the URL of the extension to be loaded by this worker
 * @property {Function} resolve - function to call on successful worker startup
 * @property {Function} reject - function to call on failed worker startup
 */

class ExtensionManager {
    constructor (runtime) {
        /**
         * The ID number to provide to the next extension worker.
         * @type {int}
         */
        this.nextExtensionWorker = 0;

        /**
         * FIFO queue of extensions which have been requested but not yet loaded in a worker,
         * along with promise resolution functions to call once the worker is ready or failed.
         *
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingExtensions = [];

        /**
         * Map of worker ID to workers which have been allocated but have not yet finished initialization.
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingWorkers = [];

        /**
         * Set of loaded extension URLs/IDs (equivalent for built-in extensions).
         * @type {Set.<string>}
         * @private
         */
        this._loadedExtensions = new Map();

        /**
         * Keep a reference to the runtime so we can construct internal extension objects.
         * TODO: remove this in favor of extensions accessing the runtime as a service.
         * @type {Runtime}
         */
        this.runtime = runtime;

        dispatch.setService('extensions', this).catch(e => {
            log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);
        });
    }

    /**
     * Check whether an extension is registered or is in the process of loading. This is intended to control loading or
     * adding extensions so it may return `true` before the extension is ready to be used. Use the promise returned by
     * `loadExtensionURL` if you need to wait until the extension is truly ready.
     * @param {string} extensionID - the ID of the extension.
     * @returns {boolean} - true if loaded, false otherwise.
     */
    isExtensionLoaded (extensionID) {
        return this._loadedExtensions.has(extensionID);
    }

    /**
     * Load an extension by URL or internal extension ID
     * @param {string} extensionURL - the URL for the extension to load OR the ID of an internal extension
     * @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure
     */
    loadExtensionURL (extensionURL) {
        if (builtinExtensions.hasOwnProperty(extensionURL)) {
            /** @TODO dupe handling for non-builtin extensions. See commit 670e51d33580e8a2e852b3b038bb3afc282f81b9 */
            if (this.isExtensionLoaded(extensionURL)) {
                const message = `Rejecting attempt to load a second extension with ID ${extensionURL}`;
                log.warn(message);
                return Promise.reject(new Error(message));
            }

            const extension = builtinExtensions[extensionURL]();
            const extensionInstance = new extension(this.runtime);
            return this._registerInternalExtension(extensionInstance).then(serviceName => {
                this._loadedExtensions.set(extensionURL, serviceName);
            });
        }

        return new Promise((resolve, reject) => {
            // If we `require` this at the global level it breaks non-webpack targets, including tests
            const ExtensionWorker = require('worker-loader?name=extension-worker.js!./extension-worker');

            this.pendingExtensions.push({extensionURL, resolve, reject});
            dispatch.addWorker(new ExtensionWorker());
        });
    }

    /**
     * Regenerate blockinfo for any loaded extensions
     * @returns {Promise} resolved once all the extensions have been reinitialized
     */
    refreshBlocks () {
        const allPromises = Array.from(this._loadedExtensions.values()).map(serviceName =>
            dispatch.call(serviceName, 'getInfo')
                .then(info => {
                    info = this._prepareExtensionInfo(serviceName, info);
                    dispatch.call('runtime', '_refreshExtensionPrimitives', info);
                })
                .catch(e => {
                    log.error(`Failed to refresh buildtin extension primitives: ${JSON.stringify(e)}`);
                })
        );
        return Promise.all(allPromises);
    }

    allocateWorker () {
        const id = this.nextExtensionWorker++;
        const workerInfo = this.pendingExtensions.shift();
        this.pendingWorkers[id] = workerInfo;
        return [id, workerInfo.extensionURL];
    }

    /**
     * Collect extension metadata from the specified service and begin the extension registration process.
     * @param {string} serviceName - the name of the service hosting the extension.
     */
    registerExtensionService (serviceName) {
        dispatch.call(serviceName, 'getInfo').then(info => {
            this._registerExtensionInfo(serviceName, info);
        });
    }

    /**
     * Called by an extension worker to indicate that the worker has finished initialization.
     * @param {int} id - the worker ID.
     * @param {*?} e - the error encountered during initialization, if any.
     */
    onWorkerInit (id, e) {
        const workerInfo = this.pendingWorkers[id];
        delete this.pendingWorkers[id];
        if (e) {
            workerInfo.reject(e);
        } else {
            workerInfo.resolve(id);
        }
    }

    /**
     * Register an internal (non-Worker) extension object
     * @param {object} extensionObject - the extension object to register
     * @returns {Promise} resolved once the extension is fully registered or rejected on failure
     */
    _registerInternalExtension (extensionObject) {
        const extensionInfo = extensionObject.getInfo();
        const fakeWorkerId = this.nextExtensionWorker++;
        const serviceName = `extension_${fakeWorkerId}_${extensionInfo.id}`;
        return dispatch.setService(serviceName, extensionObject)
            .then(() => {
                dispatch.call('extensions', 'registerExtensionService', serviceName);
                return serviceName;
            });
    }

    /**
     * Sanitize extension info then register its primitives with the VM.
     * @param {string} serviceName - the name of the service hosting the extension
     * @param {ExtensionInfo} extensionInfo - the extension's metadata
     * @private
     */
    _registerExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo);
        dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => {
            log.error(`Failed to register primitives for extension on service ${serviceName}:`, e);
        });
    }

    /**
     * Modify the provided text as necessary to ensure that it may be used as an attribute value in valid XML.
     * @param {string} text - the text to be sanitized
     * @returns {string} - the sanitized text
     * @private
     */
    _sanitizeID (text) {
        return text.toString().replace(/[<"&]/, '_');
    }

    /**
     * Apply minor cleanup and defaults for optional extension fields.
     * TODO: make the ID unique in cases where two copies of the same extension are loaded.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionInfo} extensionInfo - the extension info to be sanitized
     * @returns {ExtensionInfo} - a new extension info object with cleaned-up values
     * @private
     */
    _prepareExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = Object.assign({}, extensionInfo);
        if (!/^[a-z0-9]+$/i.test(extensionInfo.id)) {
            throw new Error('Invalid extension id');
        }
        extensionInfo.name = extensionInfo.name || extensionInfo.id;
        extensionInfo.blocks = extensionInfo.blocks || [];
        extensionInfo.targetTypes = extensionInfo.targetTypes || [];
        extensionInfo.blocks = extensionInfo.blocks.reduce((results, blockInfo) => {
            try {
                let result;
                switch (blockInfo) {
                case '---': // separator
                    result = '---';
                    break;
                default: // an ExtensionBlockMetadata object
                    result = this._prepareBlockInfo(serviceName, blockInfo);
                    break;
                }
                results.push(result);
            } catch (e) {
                // TODO: more meaningful error reporting
                log.error(`Error processing block: ${e.message}, Block:\n${JSON.stringify(blockInfo)}`);
            }
            return results;
        }, []);
        extensionInfo.menus = extensionInfo.menus || [];
        extensionInfo.menus = this._prepareMenuInfo(serviceName, extensionInfo.menus);
        return extensionInfo;
    }

    /**
     * Prepare extension menus. e.g. setup binding for dynamic menu functions.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {Array.<MenuInfo>} menus - the menu defined by the extension.
     * @returns {Array.<MenuInfo>} - a menuInfo object with all preprocessing done.
     * @private
     */
    _prepareMenuInfo (serviceName, menus) {
        const menuNames = Object.getOwnPropertyNames(menus);
        for (let i = 0; i < menuNames.length; i++) {
            const item = menuNames[i];
            // If the value is a string, it should be the name of a function in the
            // extension object to call to populate the menu whenever it is opened.
            // Set up the binding for the function object here so
            // we can use it later when converting the menu for Scratch Blocks.
            if (typeof menus[item] === 'string') {
                const serviceObject = dispatch.services[serviceName];
                const menuName = menus[item];
                menus[item] = this._getExtensionMenuItems.bind(this, serviceObject, menuName);
            }
        }
        return menus;
    }

    /**
     * Fetch the items for a particular extension menu, providing the target ID for context.
     * @param {object} extensionObject - the extension object providing the menu.
     * @param {string} menuName - the name of the menu function to call.
     * @returns {Array} menu items ready for scratch-blocks.
     * @private
     */
    _getExtensionMenuItems (extensionObject, menuName) {
        // Fetch the items appropriate for the target currently being edited. This assumes that menus only
        // collect items when opened by the user while editing a particular target.
        const editingTarget = this.runtime.getEditingTarget() || this.runtime.getTargetForStage();
        const editingTargetID = editingTarget ? editingTarget.id : null;
        const extensionMessageContext = this.runtime.makeMessageContextForTarget(editingTarget);

        // TODO: Fix this to use dispatch.call when extensions are running in workers.
        const menuFunc = extensionObject[menuName];
        const menuItems = menuFunc.call(extensionObject, editingTargetID).map(
            item => {
                item = maybeFormatMessage(item, extensionMessageContext);
                switch (typeof item) {
                case 'object':
                    return [
                        maybeFormatMessage(item.text, extensionMessageContext),
                        item.value
                    ];
                case 'string':
                    return [item, item];
                default:
                    return item;
                }
            });

        if (!menuItems || menuItems.length < 1) {
            throw new Error(`Extension menu returned no items: ${menuName}`);
        }
        return menuItems;
    }

    /**
     * Apply defaults for optional block fields.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionBlockMetadata} blockInfo - the block info from the extension
     * @returns {ExtensionBlockMetadata} - a new block info object which has values for all relevant optional fields.
     * @private
     */
    _prepareBlockInfo (serviceName, blockInfo) {
        blockInfo = Object.assign({}, {
            blockType: BlockType.COMMAND,
            terminal: false,
            blockAllThreads: false,
            arguments: {}
        }, blockInfo);
        blockInfo.opcode = this._sanitizeID(blockInfo.opcode);
        blockInfo.text = blockInfo.text || blockInfo.opcode;

        if (blockInfo.blockType !== BlockType.EVENT) {
            blockInfo.func = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;

            /**
             * This is only here because the VM performs poorly when blocks return promises.
             * @TODO make it possible for the VM to resolve a promise and continue during the same Scratch "tick"
             */
            if (dispatch._isRemoteService(serviceName)) {
                blockInfo.func = dispatch.call.bind(dispatch, serviceName, blockInfo.func);
            } else {
                const serviceObject = dispatch.services[serviceName];
                const func = serviceObject[blockInfo.func];
                if (func) {
                    blockInfo.func = func.bind(serviceObject);
                } else if (blockInfo.blockType !== BlockType.EVENT) {
                    throw new Error(`Could not find extension block function called ${blockInfo.func}`);
                }
            }
        } else if (blockInfo.func) {
            log.warn(`Ignoring function "${blockInfo.func}" for event block ${blockInfo.opcode}`);
        }

        return blockInfo;
    }
}

module.exports = ExtensionManager;
<|endoftext|>


<commit_before>import $ from "jquery";

module.exports = { hideContent: hideContent};

function hideContent($content, contentText, spoilersArr) {
	const activeSpoilers = spoilersArr.filter(obj => obj["isActive"]);

	let listHidePref, text; // strings
	let tags; // array of strings
	let spoilerObj; // object

	for (let i=0; i<activeSpoilers.length; i++) {
		spoilerObj = activeSpoilers[i];

		console.log("spoilerObj on next line");
		console.log(spoilerObj);

		text = contentText;
		tags = spoilerObj["tags"];
		if (!spoilerObj["isCaseSensitive"]) {
			text = text.toLowerCase();
			tags = tags.map(tag => tag.toLowerCase());
		}

		listHidePref = spoilerObj["hidePref"];

		// TODO: extract method
		for (let j=0; j<tags.length; j++) {
			// If text contains tag
			if (text.indexOf(tags[j]) > -1) {
				if (listHidePref === "remove") {
					$content.remove();
				}
				else if (listHidePref === "overlay") {
					overlay($content, spoilerObj["title"]);
				}
				else {
					console.log("Error in loading hide preference. Found " +
						listHidePref + " instead of 'overlay' or 'remove'. Defaulting to overlay");

					overlay($content, spoilerObj["title"]);
				}
				break;
			}
		}
	}
}

// Adds a translucent opaque div on top of a given elem
function overlay($elem, listTitle) {
	// Add overlay only once
	if ($elem.children().hasClass("spoiler-overlay") === true) {
		return;
	}

	let $newDiv = $(document.createElement("div")).css({
		"position": "absolute",
		"top": 0,
		"left": 0,
		"background-color": "white",
		"opacity": 0.99,
		"display": "flex",
		"justify-content": "center",
		"align-items": "center",
		"text-align": "center",
		"width": "100%",
		"height": "100%",
		"z-index": 99, // arbitrary large z-index to place overlay on top
		"cursor": "pointer",
		"font-size": 30,
		"font-family": "Eczar",
		"color": "black"
	});

	$newDiv.html("This content may contain spoilers for

" + listTitle);

	$newDiv.addClass("spoiler-overlay");

	// Absolutely positioned element needs a relatively positioned ancestor
	$elem.css({
		"position": "relative"
	});

	$newDiv.click( function() {
		$newDiv.hide();
	});

	$elem.append($newDiv);
}<commit_msg>fix for #5<commit_after>import $ from "jquery";

module.exports = { hideContent: hideContent};

function hideContent($content, contentText, spoilersArr) {
	// Ignore tweets that have been hidden by adblock
	if ($content.height() <= 2) return;

	const activeSpoilers = spoilersArr.filter(obj => obj["isActive"]);

	let listHidePref, text; // strings
	let tags; // array of strings
	let spoilerObj; // object

	for (let i=0; i<activeSpoilers.length; i++) {
		spoilerObj = activeSpoilers[i];

		console.log("spoilerObj on next line");
		console.log(spoilerObj);

		text = contentText;
		tags = spoilerObj["tags"];
		if (!spoilerObj["isCaseSensitive"]) {
			text = text.toLowerCase();
			tags = tags.map(tag => tag.toLowerCase());
		}

		listHidePref = spoilerObj["hidePref"];

		// TODO: extract method
		for (let j=0; j<tags.length; j++) {
			// If text contains tag
			if (text.indexOf(tags[j]) > -1) {
				if (listHidePref === "remove") {
					$content.remove();
				}
				else if (listHidePref === "overlay") {
					overlay($content, spoilerObj["title"]);
				}
				else {
					console.log("Error in loading hide preference. Found " +
						listHidePref + " instead of 'overlay' or 'remove'. Defaulting to overlay");

					overlay($content, spoilerObj["title"]);
				}
				break;
			}
		}
	}
}

// Adds a translucent opaque div on top of a given elem
function overlay($elem, listTitle) {
	// Add overlay only once
	if ($elem.children().hasClass("spoiler-overlay") === true) {
		return;
	}

	let $newDiv = $(document.createElement("div")).css({
		"position": "absolute",
		"top": 0,
		"left": 0,
		"background-color": "white",
		"opacity": 0.99,
		"display": "flex",
		"justify-content": "center",
		"align-items": "center",
		"text-align": "center",
		"width": "100%",
		"height": "100%",
		"z-index": 99, // arbitrary large z-index to place overlay on top
		"cursor": "pointer",
		"font-size": 30,
		"font-family": "Eczar",
		"color": "black"
	});

	$newDiv.html("This content may contain spoilers for

" + listTitle);

	$newDiv.addClass("spoiler-overlay");

	// Absolutely positioned element needs a relatively positioned ancestor
	$elem.css({
		"position": "relative"
	});

	$newDiv.click( function() {
		$newDiv.hide();
	});

	$elem.append($newDiv);
}<|endoftext|>


<commit_before>import test from 'ava'
import { date } from '../..'

test('isostring returns a valid date', t => {
  t.deepEqual(date.isostring('1460303444338'), '2016-04-10T15:50:44.338Z')
})

test('format returns a date in DD-MM-YYYY format by default (t1)', t => {
  t.deepEqual(date.format(new Date('2013/06/13')), '13-06-2013')
})

test('format returns a date in DD-MM-YYYY format by default (t2)', t => {
  t.deepEqual(date.format('06-27-13'), '27-06-2013')
})

test('format returns "Invalid Date" message when passed date is invalid (t1)', t => {
  t.deepEqual(date.format(new Date('2018/15/13'), 'DD/MM/YYYY'), 'Invalid Date')
})

test('format returns "Invalid Date" message when passed date is invalid (t2)', t => {
  t.deepEqual(date.format('27-06-2013', 'DD/MM/YYYY'), 'Invalid Date')
})

test('format returns "Invalid Format" message when format is invalid', t => {
  t.deepEqual(date.format('2013-06-27', 'DD!MM!YYYY'), 'Invalid Format')
})

test('format returns a date in given format (t1)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM'), '05')
})

test('format returns number of the month (t1)', t => {
  t.deepEqual(date.format(new Date('2018-05-23'), 'MM'), '05')
})

test('format returns number of the month (t2)', t => {
  t.deepEqual(date.format(new Date('2018/05/23'), 'MM'), '05')
})

test('format returns number of the month (t3)', t => {
  t.deepEqual(date.format(new Date('2018-05'), 'MM'), '05')
})

test('format returns number of the month (t4)', t => {
  t.deepEqual(date.format(new Date('2018'), 'MM'), '01')
})

test('format returns number of the day', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'DD'), '23')
})

test('format returns number of the year', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'YYYY'), '2018')
})

test('format returns a date in given format (t2)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'DD/MM/YYYY'), '23/05/2018')
})

test('format returns a date in given format (t3)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM/DD/YYYY'), '05/23/2018')
})

test('format returns a date in given format (t4)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM.DD.YYYY'), '05.23.2018')
})

test('format returns a date in given format (t5)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM-DD-YYYY'), '05-23-2018')
})

test('format returns a date in given format (t6)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'YYYY/MM/DD'), '2018/05/23')
})

test('format returns a date in given format (t7)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM YYYY'), '05 2018')
})

test('format returns a date in given format (t8)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM-YYYY'), '05-2018')
})

test('day returns the day of the month (t1)', t => {
  t.deepEqual(date.day(new Date(2018, 4, 28)), 28)
})

test('day returns the day of the month (t2)', t => {
  t.deepEqual(date.day(new Date(2017, 1, 11)), 11)
})

test('day returns the day of the month (t3)', t => {
  t.deepEqual(date.day('27-06-2013'), 'Invalid Date')
})

test('weekday returns the day of the week (t1)', t => {
  t.deepEqual(date.weekday(new Date(2018, 4, 28)), 1)
})

test('weekday returns the day of the week (t2)', t => {
  t.deepEqual(date.weekday(new Date(2018, 4, 27)), 0)
})

test('weekday returns the day of the week (t3)', t => {
  t.deepEqual(date.weekday(new Date(2017, 1, 11)), 6)
})

test('weekday returns the day of the week (t4)', t => {
  t.deepEqual(date.weekday('27-06-2013'), 'Invalid Date')
})

test('month returns the number of the month (t1)', t => {
  t.deepEqual(date.month(new Date(2018, 4, 28)), 4)
})

test('month returns the number of the month (t2)', t => {
  t.deepEqual(date.month(new Date(2018, 1, 27)), 1)
})

test('year returns the number of the year (t1)', t => {
  t.deepEqual(date.year(new Date(2018, 4, 28)), 2018)
})

test('year returns the number of the year (t2)', t => {
  t.deepEqual(date.year(new Date(1995, 2, 13)), 1995)
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t1)', t => {
  t.deepEqual(date.prettydate(new Date(2043, 9, 25)), 'Sunday, 25th of October 2043')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t2)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 5, 15)), 'Friday, 15th of June 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t3)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 5, 1)), 'Friday, 1st of June 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t4)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 2, 21)), 'Wednesday, 21st of March 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t5)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 2, 21), 'pl-Pl'), 'środa, 21 marca 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t6)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 5, 21), 'pl-Pl'), 'czwartek, 21 czerwca 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t7)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 8, 8), 'pl-Pl'), 'sobota, 8 września 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t8)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 11, 30), 'pl-Pl'), 'niedziela, 30 grudnia 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t9)', t => {
  const error = t.throws(() => date.prettydate(new Date(2018, 11, 30), 'de-De'))
  t.is(error.message, 'de-De localization is not supported.')
})

test('timestamp can return the year', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28), 'YYYY'), '2018')
})

test('timestamp can return the month', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28), 'YYYY/MM'), '2018/05')
})

test('timestamp can return the day', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28), 'YYYY/MM/DD'), '2018/05/28')
})

test('timestamp has a default pattern', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28)), '2018-05-28')
})
<commit_msg>added new test case for 'format' method<commit_after>import test from 'ava'
import { date } from '../..'

test('isostring returns a valid date', t => {
  t.deepEqual(date.isostring('1460303444338'), '2016-04-10T15:50:44.338Z')
})

test('format returns a date in DD-MM-YYYY format by default (t1)', t => {
  t.deepEqual(date.format(new Date('2013/06/13')), '13-06-2013')
})

test('format returns a date in DD-MM-YYYY format by default (t2)', t => {
  t.deepEqual(date.format(new Date('2013/06/02')), '02-06-2013')
})

test('format returns a date in DD-MM-YYYY format by default (t3)', t => {
  t.deepEqual(date.format('06-27-13'), '27-06-2013')
})

test('format returns "Invalid Date" message when passed date is invalid (t1)', t => {
  t.deepEqual(date.format(new Date('2018/15/13'), 'DD/MM/YYYY'), 'Invalid Date')
})

test('format returns "Invalid Date" message when passed date is invalid (t2)', t => {
  t.deepEqual(date.format('27-06-2013', 'DD/MM/YYYY'), 'Invalid Date')
})

test('format returns "Invalid Format" message when format is invalid', t => {
  t.deepEqual(date.format('2013-06-27', 'DD!MM!YYYY'), 'Invalid Format')
})

test('format returns a date in given format (t1)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM'), '05')
})

test('format returns number of the month (t1)', t => {
  t.deepEqual(date.format(new Date('2018-05-23'), 'MM'), '05')
})

test('format returns number of the month (t2)', t => {
  t.deepEqual(date.format(new Date('2018/05/23'), 'MM'), '05')
})

test('format returns number of the month (t3)', t => {
  t.deepEqual(date.format(new Date('2018-05'), 'MM'), '05')
})

test('format returns number of the month (t4)', t => {
  t.deepEqual(date.format(new Date('2018'), 'MM'), '01')
})

test('format returns number of the day', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'DD'), '23')
})

test('format returns number of the year', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'YYYY'), '2018')
})

test('format returns a date in given format (t2)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'DD/MM/YYYY'), '23/05/2018')
})

test('format returns a date in given format (t3)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM/DD/YYYY'), '05/23/2018')
})

test('format returns a date in given format (t4)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM.DD.YYYY'), '05.23.2018')
})

test('format returns a date in given format (t5)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM-DD-YYYY'), '05-23-2018')
})

test('format returns a date in given format (t6)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'YYYY/MM/DD'), '2018/05/23')
})

test('format returns a date in given format (t7)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM YYYY'), '05 2018')
})

test('format returns a date in given format (t8)', t => {
  t.deepEqual(date.format(new Date(2018, 4, 23), 'MM-YYYY'), '05-2018')
})

test('day returns the day of the month (t1)', t => {
  t.deepEqual(date.day(new Date(2018, 4, 28)), 28)
})

test('day returns the day of the month (t2)', t => {
  t.deepEqual(date.day(new Date(2017, 1, 11)), 11)
})

test('day returns the day of the month (t3)', t => {
  t.deepEqual(date.day('27-06-2013'), 'Invalid Date')
})

test('weekday returns the day of the week (t1)', t => {
  t.deepEqual(date.weekday(new Date(2018, 4, 28)), 1)
})

test('weekday returns the day of the week (t2)', t => {
  t.deepEqual(date.weekday(new Date(2018, 4, 27)), 0)
})

test('weekday returns the day of the week (t3)', t => {
  t.deepEqual(date.weekday(new Date(2017, 1, 11)), 6)
})

test('weekday returns the day of the week (t4)', t => {
  t.deepEqual(date.weekday('27-06-2013'), 'Invalid Date')
})

test('month returns the number of the month (t1)', t => {
  t.deepEqual(date.month(new Date(2018, 4, 28)), 4)
})

test('month returns the number of the month (t2)', t => {
  t.deepEqual(date.month(new Date(2018, 1, 27)), 1)
})

test('year returns the number of the year (t1)', t => {
  t.deepEqual(date.year(new Date(2018, 4, 28)), 2018)
})

test('year returns the number of the year (t2)', t => {
  t.deepEqual(date.year(new Date(1995, 2, 13)), 1995)
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t1)', t => {
  t.deepEqual(date.prettydate(new Date(2043, 9, 25)), 'Sunday, 25th of October 2043')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t2)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 5, 15)), 'Friday, 15th of June 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t3)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 5, 1)), 'Friday, 1st of June 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t4)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 2, 21)), 'Wednesday, 21st of March 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t5)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 2, 21), 'pl-Pl'), 'środa, 21 marca 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t6)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 5, 21), 'pl-Pl'), 'czwartek, 21 czerwca 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t7)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 8, 8), 'pl-Pl'), 'sobota, 8 września 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t8)', t => {
  t.deepEqual(date.prettydate(new Date(2018, 11, 30), 'pl-Pl'), 'niedziela, 30 grudnia 2018')
})

test('prettydate returns date string containing name of the day, number of the day, name of the month and year (t9)', t => {
  const error = t.throws(() => date.prettydate(new Date(2018, 11, 30), 'de-De'))
  t.is(error.message, 'de-De localization is not supported.')
})

test('timestamp can return the year', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28), 'YYYY'), '2018')
})

test('timestamp can return the month', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28), 'YYYY/MM'), '2018/05')
})

test('timestamp can return the day', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28), 'YYYY/MM/DD'), '2018/05/28')
})

test('timestamp has a default pattern', t => {
  t.deepEqual(date.timestamp(new Date(2018, 4, 28)), '2018-05-28')
})
<|endoftext|>


<commit_before>/* global describe, it */
define([

  'chai'
  ,'underscore'
  ,'backbone'
  ,'lateralus'

], function (

  chai
  ,_
  ,Backbone
  ,Lateralus

) {
  'use strict';

  var assert = chai.assert;

  /**
   * @param {Function} [extraConstructorCode]
   */
  function getLateraralusApp (extraConstructorCode) {
    return Lateralus.beget(function () {
      Lateralus.apply(this, arguments);

      if (extraConstructorCode) {
        extraConstructorCode.call(this);
      }
    });
  }

  describe('Bootup', function () {
    it('Framework defines Lateralus constructor', function () {
      assert.isFunction(Lateralus);
    });

    it('Framework defines Lateralus.Component', function () {
      assert.isFunction(Lateralus.Component);
    });

    it('Framework defines Lateralus.Model', function () {
      assert.isFunction(Lateralus.Model);
    });

    it('Framework defines Lateralus.Router', function () {
      assert.isFunction(Lateralus.Router);
    });
  });

  describe('Initialization', function () {
    var App = getLateraralusApp();
    var el = document.createElement('div');
    var app = new App(el);

    it('App has root element', function () {
      assert.equal(app.el, el);
    });

    it('App has jQuery reference to root element', function () {
      assert.equal(app.$el[0], el);
    });
  });

  describe('Lateralus teardown', function () {
    var App = getLateraralusApp();
    var app = new App(document.createElement('div'));
    var model = new Backbone.Model();
    app.listenTo(model, 'test', _.noop);
    var component = app.addComponent(Lateralus.Component);
    app.dispose();

    it('Stopped listening to other objects', function () {
      assert.typeOf(model._events.test, 'undefined');
    });

    it('Component is disposed', function () {
      assert.equal(_.keys(component).length, 0);
    });

    it('Has all properties removed', function () {
      assert.equal(_.keys(app).length, 0);
    });

    it('Has fun alias', function () {
      assert.equal(app.dispose, app.spiralOut);
    });
  });

  describe('lateralusEvents', function () {
    var App = getLateraralusApp();
    var testWasCalled = false;

    _.extend(App.prototype, {
      lateralusEvents: {
        test: function () {
          testWasCalled = true;
        }
      }
    });

    var app = new App(document.createElement('div'));

    it('Captures top-level events', function () {
      app.emit('test');
      assert.isTrue(testWasCalled);
    });

    testWasCalled = false;

    it('Captures Lateralus.Model-level events', function () {
      app.model.emit('test');
      assert.isTrue(testWasCalled);
    });

    testWasCalled = false;

    it('Captures Component-level events', function () {
      var component = app.addComponent(Lateralus.Component);
      component.emit('test');
      assert.isTrue(testWasCalled);
      component.dispose();
    });

    testWasCalled = false;

    it('Captures Component.View-level events', function () {
      var ExtendedComponent = Lateralus.Component.extend({
        name: 'extended'
        ,View: Lateralus.Component.View
      });

      var component = app.addComponent(ExtendedComponent);
      component.view.emit('test');
      assert.isTrue(testWasCalled);
      component.dispose();
    });

    testWasCalled = false;

    it('Captures Component.Model-level events', function () {
      var ExtendedComponent = Lateralus.Component.extend({
        name: 'extended'
        ,View: Lateralus.Component.View
        ,Model: Lateralus.Component.Model
      });

      var component = app.addComponent(ExtendedComponent);
      component.view.model.emit('test');
      assert.isTrue(testWasCalled);
      component.dispose();
    });
  });

  describe('Lateralus.inherit', function () {
    function Parent () {}
    function Child () {}
    Parent.prototype.foo = function () {};
    Lateralus.inherit(Child, Parent);
    var child = new Child();

    it('Sets up prototype chain between Parent and Child', function () {
      assert.instanceOf(child, Parent);
    });

    it('Passes Parent methods to child', function () {
      assert.equal(child.foo, Parent.prototype.foo);
    });
  });

  describe('Lateralus#initRouter', function () {
    var App = getLateraralusApp();
    var app = new App();

    var ExtendedRouter = Lateralus.Router.extend({
      initialize: function (options) {
        this.gotOptions = !!options;
      }
    });

    var router = app.initRouter(ExtendedRouter, {});

    it('Instantiates a Lateralus.Router', function () {
      assert.instanceOf(router, Lateralus.Router);
    });

    it('Passes options to instantiated Lateralus.Router', function () {
      assert.isTrue(router.gotOptions);
    });
  });

  describe('mixin.amplify', function () {
    var App = getLateraralusApp();
    var testWasAmplified = false;
    _.extend(App.prototype, {
      lateralusEvents: {
        'test': function () {
          testWasAmplified = true;
        }
      }
    });

    var app = new App(document.createElement('div'));

    it('Broadcasts a Backbone.Model\'s events globally', function () {
      var model = new Backbone.Model();
      app.amplify(model, 'test');
      model.trigger('test');
      assert.isTrue(testWasAmplified);
    });
  });

  describe('Preventing redundant global model change events', function () {
    var count = 0;
    var App = getLateraralusApp(function () {
      this.model.set('prop1', 'foo');
    });

    _.extend(App.prototype, {
      lateralusEvents: {
        'change:prop1': function () {
          count++;
          this.model.set('prop2', 'bar');
        }
      }
    });

    new App(document.createElement('div'));

    it('Only called the global handler once', function () {
      assert.equal(count, 1);
    });
  });

});
<commit_msg>Add test for Lateraralus#toString.<commit_after>/* global describe, it */
define([

  'chai'
  ,'underscore'
  ,'backbone'
  ,'lateralus'

], function (

  chai
  ,_
  ,Backbone
  ,Lateralus

) {
  'use strict';

  var assert = chai.assert;

  /**
   * @param {Function} [extraConstructorCode]
   */
  function getLateraralusApp (extraConstructorCode) {
    return Lateralus.beget(function () {
      Lateralus.apply(this, arguments);

      if (extraConstructorCode) {
        extraConstructorCode.call(this);
      }
    });
  }

  describe('Bootup', function () {
    it('Framework defines Lateralus constructor', function () {
      assert.isFunction(Lateralus);
    });

    it('Framework defines Lateralus.Component', function () {
      assert.isFunction(Lateralus.Component);
    });

    it('Framework defines Lateralus.Model', function () {
      assert.isFunction(Lateralus.Model);
    });

    it('Framework defines Lateralus.Router', function () {
      assert.isFunction(Lateralus.Router);
    });
  });

  describe('Initialization', function () {
    var App = getLateraralusApp();
    var el = document.createElement('div');
    var app = new App(el);

    it('App has root element', function () {
      assert.equal(app.el, el);
    });

    it('App has jQuery reference to root element', function () {
      assert.equal(app.$el[0], el);
    });
  });

  describe('Lateralus teardown', function () {
    var App = getLateraralusApp();
    var app = new App(document.createElement('div'));
    var model = new Backbone.Model();
    app.listenTo(model, 'test', _.noop);
    var component = app.addComponent(Lateralus.Component);
    app.dispose();

    it('Stopped listening to other objects', function () {
      assert.typeOf(model._events.test, 'undefined');
    });

    it('Component is disposed', function () {
      assert.equal(_.keys(component).length, 0);
    });

    it('Has all properties removed', function () {
      assert.equal(_.keys(app).length, 0);
    });

    it('Has fun alias', function () {
      assert.equal(app.dispose, app.spiralOut);
    });
  });

  describe('lateralusEvents', function () {
    var App = getLateraralusApp();
    var testWasCalled = false;

    _.extend(App.prototype, {
      lateralusEvents: {
        test: function () {
          testWasCalled = true;
        }
      }
    });

    var app = new App(document.createElement('div'));

    it('Captures top-level events', function () {
      app.emit('test');
      assert.isTrue(testWasCalled);
    });

    testWasCalled = false;

    it('Captures Lateralus.Model-level events', function () {
      app.model.emit('test');
      assert.isTrue(testWasCalled);
    });

    testWasCalled = false;

    it('Captures Component-level events', function () {
      var component = app.addComponent(Lateralus.Component);
      component.emit('test');
      assert.isTrue(testWasCalled);
      component.dispose();
    });

    testWasCalled = false;

    it('Captures Component.View-level events', function () {
      var ExtendedComponent = Lateralus.Component.extend({
        name: 'extended'
        ,View: Lateralus.Component.View
      });

      var component = app.addComponent(ExtendedComponent);
      component.view.emit('test');
      assert.isTrue(testWasCalled);
      component.dispose();
    });

    testWasCalled = false;

    it('Captures Component.Model-level events', function () {
      var ExtendedComponent = Lateralus.Component.extend({
        name: 'extended'
        ,View: Lateralus.Component.View
        ,Model: Lateralus.Component.Model
      });

      var component = app.addComponent(ExtendedComponent);
      component.view.model.emit('test');
      assert.isTrue(testWasCalled);
      component.dispose();
    });
  });

  describe('Lateralus.inherit', function () {
    function Parent () {}
    function Child () {}
    Parent.prototype.foo = function () {};
    Lateralus.inherit(Child, Parent);
    var child = new Child();

    it('Sets up prototype chain between Parent and Child', function () {
      assert.instanceOf(child, Parent);
    });

    it('Passes Parent methods to child', function () {
      assert.equal(child.foo, Parent.prototype.foo);
    });
  });

  describe('Lateralus#initRouter', function () {
    var App = getLateraralusApp();
    var app = new App();

    var ExtendedRouter = Lateralus.Router.extend({
      initialize: function (options) {
        this.gotOptions = !!options;
      }
    });

    var router = app.initRouter(ExtendedRouter, {});

    it('Instantiates a Lateralus.Router', function () {
      assert.instanceOf(router, Lateralus.Router);
    });

    it('Passes options to instantiated Lateralus.Router', function () {
      assert.isTrue(router.gotOptions);
    });
  });

  describe('Lateraralus#toString', function () {
    var App = getLateraralusApp();
    var app = new App();

    it('Returns "lateralus"', function () {
      assert.equal(app.toString(), 'lateralus');
    });
  });

  describe('mixin.amplify', function () {
    var App = getLateraralusApp();
    var testWasAmplified = false;
    _.extend(App.prototype, {
      lateralusEvents: {
        'test': function () {
          testWasAmplified = true;
        }
      }
    });

    var app = new App(document.createElement('div'));

    it('Broadcasts a Backbone.Model\'s events globally', function () {
      var model = new Backbone.Model();
      app.amplify(model, 'test');
      model.trigger('test');
      assert.isTrue(testWasAmplified);
    });
  });

  describe('Preventing redundant global model change events', function () {
    var count = 0;
    var App = getLateraralusApp(function () {
      this.model.set('prop1', 'foo');
    });

    _.extend(App.prototype, {
      lateralusEvents: {
        'change:prop1': function () {
          count++;
          this.model.set('prop2', 'bar');
        }
      }
    });

    new App(document.createElement('div'));

    it('Only called the global handler once', function () {
      assert.equal(count, 1);
    });
  });

});
<|endoftext|>


<commit_before>(function() {
  module("Help");

  function helpAt(index, searchString, html) {
    return index.get(html.indexOf(searchString));
  }

  function assertHighlightIntervals(index, searchString, strings, html) {
    var highlights = helpAt(index, searchString, html).highlights;
    var prefix = "in " + JSON.stringify(html) + ", when cursor is at " +
                 JSON.stringify(searchString) + ", ";
    equal(highlights.length, strings.length,
          prefix + "there are " + strings.length + " highlight(s)");
    for (var i = 0; i < highlights.length; i++)
      equal(html.slice(highlights[i].start, highlights[i].end), strings[i],
            prefix + "highlight interval " + i + " is " +
            JSON.stringify(strings[i]));
  }

  function buildIndex(html) {
    var index = Help.Index();
    var doc = Slowparse.HTML(document, html).document;

    index.build(doc, html);
    return index;
  }

  test("Index.get() returns undefined when in text node", function() {
    equal(buildIndex('hi').get(4), undefined);
  });

  test("Index.get() returns help for HTML elements", function() {
    var html = '
he
llo
';
    var index = buildIndex(html);
    var help = helpAt(index, "
", html);

    ok(help.html.match(/paragraph/i),
       "Index contains HTML help from hacktionary");
    equal(help.url, Help.MDN_URLS.html + 'p',
          "Index contains MDN URL for HTML elements");
    assertHighlightIntervals(index, "
", ["
", "
"], html);
    assertHighlightIntervals(index, "
", ["
"], html);
  });

  test("Index.get() returns help for CSS properties", function() {
    var html = '';
    var index = buildIndex(html);
    var help = helpAt(index, "color", html);

    ok(help.html.match(/foreground/i),
       "Index contains CSS property help from hacktionary");
    equal(help.url, Help.MDN_URLS.css + "color",
          "Index contains MDN URL for CSS properties");
    assertHighlightIntervals(index, "color", ["color"], html);
  });

  test("Index.get() returns help for CSS selectors", function() {
    var html = '';
    var index = buildIndex(html);
    var help = helpAt(index, "body", html);

    ok(help.html.match(/selector/i),
       "Index contains CSS selector help from hacktionary");
    ok(help.url, Help.MDN_URLS.cssSelectors,
       "Index contains MDN URL for CSS selectors");
    assertHighlightIntervals(buildIndex(html), "body", ["body"], html);
  });
})();
<commit_msg>added test for Index.clear()<commit_after>(function() {
  module("Help");

  function helpAt(index, searchString, html) {
    return index.get(html.indexOf(searchString));
  }

  function assertHighlightIntervals(index, searchString, strings, html) {
    var highlights = helpAt(index, searchString, html).highlights;
    var prefix = "in " + JSON.stringify(html) + ", when cursor is at " +
                 JSON.stringify(searchString) + ", ";
    equal(highlights.length, strings.length,
          prefix + "there are " + strings.length + " highlight(s)");
    for (var i = 0; i < highlights.length; i++)
      equal(html.slice(highlights[i].start, highlights[i].end), strings[i],
            prefix + "highlight interval " + i + " is " +
            JSON.stringify(strings[i]));
  }

  function buildIndex(html) {
    var index = Help.Index();
    var doc = Slowparse.HTML(document, html).document;

    index.build(doc, html);
    return index;
  }

  test("Index.clear() works", function() {
    var index = buildIndex('
hi
');
    ok(index.get(0));
    index.clear();
    equal(index.get(0), undefined);
  });

  test("Index.get() returns undefined when in text node", function() {
    equal(buildIndex('hi').get(4), undefined);
  });

  test("Index.get() returns help for HTML elements", function() {
    var html = '
he
llo
';
    var index = buildIndex(html);
    var help = helpAt(index, "
", html);

    ok(help.html.match(/paragraph/i),
       "Index contains HTML help from hacktionary");
    equal(help.url, Help.MDN_URLS.html + 'p',
          "Index contains MDN URL for HTML elements");
    assertHighlightIntervals(index, "
", ["
", "
"], html);
    assertHighlightIntervals(index, "
", ["
"], html);
  });

  test("Index.get() returns help for CSS properties", function() {
    var html = '';
    var index = buildIndex(html);
    var help = helpAt(index, "color", html);

    ok(help.html.match(/foreground/i),
       "Index contains CSS property help from hacktionary");
    equal(help.url, Help.MDN_URLS.css + "color",
          "Index contains MDN URL for CSS properties");
    assertHighlightIntervals(index, "color", ["color"], html);
  });

  test("Index.get() returns help for CSS selectors", function() {
    var html = '';
    var index = buildIndex(html);
    var help = helpAt(index, "body", html);

    ok(help.html.match(/selector/i),
       "Index contains CSS selector help from hacktionary");
    ok(help.url, Help.MDN_URLS.cssSelectors,
       "Index contains MDN URL for CSS selectors");
    assertHighlightIntervals(buildIndex(html), "body", ["body"], html);
  });
})();
<|endoftext|>


<commit_before>var testEngine = Nehan.setup({
  test:true,
  layout:{
    fontSize:16
  }
});

var style = testEngine.style;
var config = testEngine.config;
var env = testEngine.env;
var Class = testEngine.Class;
var Utils = testEngine.Utils;
var Obj = testEngine.Obj;
var Css = testEngine.Css;
var Html = testEngine.Html;
var Closure = testEngine.Closure;
var Args = testEngine.Args;
var List = testEngine.List;
var Flow = testEngine.Flow;
var InlineFlow = testEngine.InlineFlow;
var BlockFlow = testEngine.BlockFlow;
var BoxFlow = testEngine.BoxFlow;
var BoxFlows = testEngine.BoxFlows;
var Edge = testEngine.Edge;
var EdgeParser = testEngine.EdgeParser;
var CornerParser = testEngine.CornerParser;
var CssParser = testEngine.CssParser;
var Padding = testEngine.Padding;
var Margin = testEngine.Margin;
var Border = testEngine.Border;
var BorderRadius = testEngine.BorderRadius;
var BorderColor = testEngine.BorderColor;
var TextAlign = testEngine.TextAlign;
var Radius2d = testEngine.Radius2d;
var BoxEdge = testEngine.BoxEdge;
var BoxSize = testEngine.BoxSize;
var BoxSizing = testEngine.BoxSizing;
var BoxSizings = testEngine.BoxSizings;
var UnitSize = testEngine.UnitSize;
var BoxChild = testEngine.BoxChild;
var Box = testEngine.Box;
var Color = testEngine.Color;
var Colors = testEngine.Colors;
var Selector = testEngine.Selector;
var SelectorLexer = testEngine.SelectorLexer;
var SelectorAttr = testEngine.SelectorAttr;
var SelectorPseudo = testEngine.SelectorPseudo;
var SelectorType = testEngine.SelectorType;
var SelectorCombinator = testEngine.SelectorCombinator;
var SelectorStateMachine= testEngine.SelectorStateMachine;
var Tag = testEngine.Tag;
var Char = testEngine.Char;
var Word = testEngine.Word;
var Tcy = testEngine.Tcy;
var Text = testEngine.Text;
var Icon = testEngine.Icon;
var RubyLabel = testEngine.RubyLabel;
var HtmlLexer = testEngine.HtmlLexer;
var Token = testEngine.Token;
var TokenStream = testEngine.TokenStream;
var TagStack = testEngine.TagStack;
var InlineContext = testEngine.InlineContext;
var BlockContext = testEngine.BlockContext;
var EvalResult = testEngine.EvalResult;
var DocumentContext = testEngine.DocumentContext;
var DocumentTagStream = testEngine.DocumentTagStream;
var HtmlTagStream = testEngine.HtmlTagStream;
var TableTagStream = testEngine.TableTagStream;
var CaptBoxTagStream = testEngine.CaptBoxTagStream;
var ListTagStream = testEngine.ListTagStream;
var OrderedListTagStream = testEngine.OrderedListTagStream;
var DefListTagStream = testEngine.DefListTagStream;
var TocContext = testEngine.TocContext;
var PageGroup = testEngine.PageGroup;
var Partition = testEngine.Partition;
var Cardinal = testEngine.Cardinal;

<commit_msg>modify unit-test<commit_after>var testEngine = Nehan.setup({
  test:true,
  config:{
    allowExternalClassName:true
  },
  layout:{
    fontSize:16
  }
});

var style = testEngine.style;
var config = testEngine.config;
var env = testEngine.env;
var Class = testEngine.Class;
var Utils = testEngine.Utils;
var Obj = testEngine.Obj;
var Css = testEngine.Css;
var Html = testEngine.Html;
var Closure = testEngine.Closure;
var Args = testEngine.Args;
var List = testEngine.List;
var Flow = testEngine.Flow;
var InlineFlow = testEngine.InlineFlow;
var BlockFlow = testEngine.BlockFlow;
var BoxFlow = testEngine.BoxFlow;
var BoxFlows = testEngine.BoxFlows;
var Edge = testEngine.Edge;
var EdgeParser = testEngine.EdgeParser;
var CornerParser = testEngine.CornerParser;
var CssParser = testEngine.CssParser;
var Padding = testEngine.Padding;
var Margin = testEngine.Margin;
var Border = testEngine.Border;
var BorderRadius = testEngine.BorderRadius;
var BorderColor = testEngine.BorderColor;
var TextAlign = testEngine.TextAlign;
var Radius2d = testEngine.Radius2d;
var BoxEdge = testEngine.BoxEdge;
var BoxSize = testEngine.BoxSize;
var BoxSizing = testEngine.BoxSizing;
var BoxSizings = testEngine.BoxSizings;
var UnitSize = testEngine.UnitSize;
var BoxChild = testEngine.BoxChild;
var Box = testEngine.Box;
var Color = testEngine.Color;
var Colors = testEngine.Colors;
var Selector = testEngine.Selector;
var SelectorLexer = testEngine.SelectorLexer;
var SelectorAttr = testEngine.SelectorAttr;
var SelectorPseudo = testEngine.SelectorPseudo;
var SelectorType = testEngine.SelectorType;
var SelectorCombinator = testEngine.SelectorCombinator;
var SelectorStateMachine= testEngine.SelectorStateMachine;
var Tag = testEngine.Tag;
var Char = testEngine.Char;
var Word = testEngine.Word;
var Tcy = testEngine.Tcy;
var Text = testEngine.Text;
var Icon = testEngine.Icon;
var RubyLabel = testEngine.RubyLabel;
var HtmlLexer = testEngine.HtmlLexer;
var Token = testEngine.Token;
var TokenStream = testEngine.TokenStream;
var TagStack = testEngine.TagStack;
var InlineContext = testEngine.InlineContext;
var BlockContext = testEngine.BlockContext;
var EvalResult = testEngine.EvalResult;
var DocumentContext = testEngine.DocumentContext;
var DocumentTagStream = testEngine.DocumentTagStream;
var HtmlTagStream = testEngine.HtmlTagStream;
var TableTagStream = testEngine.TableTagStream;
var CaptBoxTagStream = testEngine.CaptBoxTagStream;
var ListTagStream = testEngine.ListTagStream;
var OrderedListTagStream = testEngine.OrderedListTagStream;
var DefListTagStream = testEngine.DefListTagStream;
var TocContext = testEngine.TocContext;
var PageGroup = testEngine.PageGroup;
var Partition = testEngine.Partition;
var Cardinal = testEngine.Cardinal;

<|endoftext|>


<commit_before>// Karma configuration
// Generated on Fri May 30 2014 08:38:23 GMT+0100 (BST)

module.exports = {

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha'],

    // list of files / patterns to load in the browser
    files: [
        '../lib/*.js',
        'vendor/assert.js',
        'specs/*.js'
    ],

    // list of files to exclude
    exclude: [

    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {

    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
};
<commit_msg>Updated CI<commit_after>// Karma configuration
// Generated on Fri May 30 2014 08:38:23 GMT+0100 (BST)

module.exports = {

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha'],

    // list of files / patterns to load in the browser
    files: [
        '../lib/*.js',
        'vendor/assert.js',
        'specs/*.js'
    ],

    // list of files to exclude
    exclude: [

    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {

    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
};
<|endoftext|>


<commit_before><commit_msg>Fix tests<commit_after><|endoftext|>


<commit_before>const LoggedInHandler = require('./logged_in');

const Careers            = require('../static_pages/careers');
const Charity            = require('../static_pages/charity');
const Contact            = require('../static_pages/contact');
const Endpoint           = require('../static_pages/endpoint');
const Home               = require('../static_pages/home');
const GetStarted         = require('../static_pages/get_started');
const GetStartedJP       = require('../static_pages/get_started_jp');
const JobDetails         = require('../static_pages/job_details');
const Platforms          = require('../static_pages/platforms');
const Regulation         = require('../static_pages/regulation');
const StaticPages        = require('../static_pages/static_pages');
const TermsAndConditions = require('../static_pages/tnc');
const WhyUs              = require('../static_pages/why_us');

const AccountTransfer            = require('../websocket_pages/cashier/account_transfer');
const Cashier                    = require('../websocket_pages/cashier/cashier');
const DepositWithdraw            = require('../websocket_pages/cashier/deposit_withdraw');
const PaymentAgentList           = require('../websocket_pages/cashier/payment_agent_list');
const PaymentAgentWithdraw       = require('../websocket_pages/cashier/payment_agent_withdraw');
const MBTradePage                = require('../websocket_pages/mb_trade/mb_tradepage');
const AssetIndexUI               = require('../websocket_pages/resources/asset_index/asset_indexws.ui');
const MarketTimesUI              = require('../websocket_pages/resources/market_times/market_timesws.ui');
const TradePage_Beta             = require('../websocket_pages/trade/beta/tradepage');
const TradePage                  = require('../websocket_pages/trade/tradepage');
const Authenticate               = require('../websocket_pages/user/account/authenticate');
const ChangePassword             = require('../websocket_pages/user/account/change_password');
const PaymentAgentTransfer       = require('../websocket_pages/user/account/payment_agent_transfer');
const Portfolio                  = require('../websocket_pages/user/account/portfolio/portfolio.init');
const ProfitTable                = require('../websocket_pages/user/account/profit_table/profit_table.init');
const APITokenWS                 = require('../websocket_pages/user/account/settings/api_token');
const AuthorisedApps             = require('../websocket_pages/user/account/settings/authorised_apps');
const CashierPassword            = require('../websocket_pages/user/account/settings/cashier_password');
const FinancialAssessment        = require('../websocket_pages/user/account/settings/financial_assessment');
const IPHistory                  = require('../websocket_pages/user/account/settings/iphistory');
const Limits                     = require('../websocket_pages/user/account/settings/limits');
const Settings                   = require('../websocket_pages/user/account/settings');
const SelfExclusionWS            = require('../websocket_pages/user/account/settings/self_exclusion');
const SettingsDetailsWS          = require('../websocket_pages/user/account/settings/settings_detailsws');
const Statement                  = require('../websocket_pages/user/account/statement/statement.init');
const TopUpVirtual               = require('../websocket_pages/user/account/top_up_virtual');
const LostPassword               = require('../websocket_pages/user/lost_password');
const MetaTrader                 = require('../websocket_pages/user/metatrader/metatrader');
const FinancialAccOpening        = require('../websocket_pages/user/new_account/financial_acc_opening');
const JapanAccOpening            = require('../websocket_pages/user/new_account/japan_acc_opening');
const RealAccOpening             = require('../websocket_pages/user/new_account/real_acc_opening');
const VirtualAccOpening          = require('../websocket_pages/user/new_account/virtual_acc_opening');
const ResetPassword              = require('../websocket_pages/user/reset_password');
const TNCApproval                = require('../websocket_pages/user/tnc_approval');

const CashierJP     = require('../../binary_japan/cashier');
const KnowledgeTest = require('../../binary_japan/knowledge_test/knowledge_test');

const pages_config = {
    account_transfer         : { module: AccountTransfer,            is_authenticated: true, only_real: true },
    api_tokenws              : { module: APITokenWS,                 is_authenticated: true },
    assessmentws             : { module: FinancialAssessment,        is_authenticated: true, only_real: true },
    asset_indexws            : { module: AssetIndexUI },
    authenticate             : { module: Authenticate,               is_authenticated: true, only_real: true },
    authorised_appsws        : { module: AuthorisedApps,             is_authenticated: true },
    careers                  : { module: Careers },
    cashier                  : { module: Cashier },
    cashier_passwordws       : { module: CashierPassword,            is_authenticated: true, only_real: true },
    change_passwordws        : { module: ChangePassword,             is_authenticated: true },
    charity                  : { module: Charity },
    contact                  : { module: Contact },
    detailsws                : { module: SettingsDetailsWS,          is_authenticated: true },
    endpoint                 : { module: Endpoint },
    epg_forwardws            : { module: DepositWithdraw,            is_authenticated: true, only_real: true },
    forwardws                : { module: DepositWithdraw,            is_authenticated: true, only_real: true },
    home                     : { module: Home,                       not_authenticated: true },
    iphistoryws              : { module: IPHistory,                  is_authenticated: true },
    japanws                  : { module: JapanAccOpening,            is_authenticated: true, only_virtual: true },
    knowledge_testws         : { module: KnowledgeTest,              is_authenticated: true, only_virtual: true },
    limitsws                 : { module: Limits,                     is_authenticated: true, only_real: true },
    logged_inws              : { module: LoggedInHandler },
    lost_passwordws          : { module: LostPassword,               not_authenticated: true },
    maltainvestws            : { module: FinancialAccOpening,        is_authenticated: true },
    market_timesws           : { module: MarketTimesUI },
    metatrader               : { module: MetaTrader,                 is_authenticated: true },
    multi_barriers_trading   : { module: MBTradePage },
    payment_agent_listws     : { module: PaymentAgentList },
    payment_methods          : { module: Cashier.PaymentMethods },
    platforms                : { module: Platforms },
    portfoliows              : { module: Portfolio,                is_authenticated: true },
    profit_tablews           : { module: ProfitTable,              is_authenticated: true },
    realws                   : { module: RealAccOpening,             is_authenticated: true, only_virtual: true },
    regulation               : { module: Regulation },
    reset_passwordws         : { module: ResetPassword,              not_authenticated: true },
    securityws               : { module: Settings,                   is_authenticated: true },
    self_exclusionws         : { module: SelfExclusionWS,            is_authenticated: true, only_real: true },
    settingsws               : { module: Settings,                   is_authenticated: true },
    signup                   : { module: StaticPages.AffiliateSignup },
    statementws              : { module: Statement,                is_authenticated: true },
    tnc_approvalws           : { module: TNCApproval,                is_authenticated: true, only_real: true },
    top_up_virtualws         : { module: TopUpVirtual,              is_authenticated: true, only_virtual: true },
    trading                  : { module: TradePage },
    trading_beta             : { module: TradePage_Beta },
    transferws               : { module: PaymentAgentTransfer,       is_authenticated: true, only_real: true },
    virtualws                : { module: VirtualAccOpening,          not_authenticated: true },
    withdrawws               : { module: PaymentAgentWithdraw,       is_authenticated: true, only_real: true },
    'deposit-jp'             : { module: CashierJP.Deposit,          is_authenticated: true, only_real: true },
    'get-started'            : { module: GetStarted },
    'get-started-jp'         : { module: GetStartedJP },
    'home-jp'                : { module: Home,                       not_authenticated: true },
    'job-details'            : { module: JobDetails },
    'open-positions'         : { module: StaticPages.OpenPositions },
    'open-source-projects'   : { module: StaticPages.OpenSourceProjects },
    'payment-agent'          : { module: StaticPages.PaymentAgent },
    'terms-and-conditions'   : { module: TermsAndConditions },
    'terms-and-conditions-jp': { module: TermsAndConditions },
    'volidx-markets'         : { module: StaticPages.VolidxMarkets },
    'why-us'                 : { module: WhyUs },
    'why-us-jp'              : { module: WhyUs },
    'withdraw-jp'            : { module: CashierJP.Withdraw,         is_authenticated: true, only_real: true },
};

module.exports = pages_config;
<commit_msg>Load careers for americans<commit_after>const LoggedInHandler = require('./logged_in');

const Careers            = require('../static_pages/careers');
const Charity            = require('../static_pages/charity');
const Contact            = require('../static_pages/contact');
const Endpoint           = require('../static_pages/endpoint');
const Home               = require('../static_pages/home');
const GetStarted         = require('../static_pages/get_started');
const GetStartedJP       = require('../static_pages/get_started_jp');
const JobDetails         = require('../static_pages/job_details');
const Platforms          = require('../static_pages/platforms');
const Regulation         = require('../static_pages/regulation');
const StaticPages        = require('../static_pages/static_pages');
const TermsAndConditions = require('../static_pages/tnc');
const WhyUs              = require('../static_pages/why_us');

const AccountTransfer            = require('../websocket_pages/cashier/account_transfer');
const Cashier                    = require('../websocket_pages/cashier/cashier');
const DepositWithdraw            = require('../websocket_pages/cashier/deposit_withdraw');
const PaymentAgentList           = require('../websocket_pages/cashier/payment_agent_list');
const PaymentAgentWithdraw       = require('../websocket_pages/cashier/payment_agent_withdraw');
const MBTradePage                = require('../websocket_pages/mb_trade/mb_tradepage');
const AssetIndexUI               = require('../websocket_pages/resources/asset_index/asset_indexws.ui');
const MarketTimesUI              = require('../websocket_pages/resources/market_times/market_timesws.ui');
const TradePage_Beta             = require('../websocket_pages/trade/beta/tradepage');
const TradePage                  = require('../websocket_pages/trade/tradepage');
const Authenticate               = require('../websocket_pages/user/account/authenticate');
const ChangePassword             = require('../websocket_pages/user/account/change_password');
const PaymentAgentTransfer       = require('../websocket_pages/user/account/payment_agent_transfer');
const Portfolio                  = require('../websocket_pages/user/account/portfolio/portfolio.init');
const ProfitTable                = require('../websocket_pages/user/account/profit_table/profit_table.init');
const APITokenWS                 = require('../websocket_pages/user/account/settings/api_token');
const AuthorisedApps             = require('../websocket_pages/user/account/settings/authorised_apps');
const CashierPassword            = require('../websocket_pages/user/account/settings/cashier_password');
const FinancialAssessment        = require('../websocket_pages/user/account/settings/financial_assessment');
const IPHistory                  = require('../websocket_pages/user/account/settings/iphistory');
const Limits                     = require('../websocket_pages/user/account/settings/limits');
const Settings                   = require('../websocket_pages/user/account/settings');
const SelfExclusionWS            = require('../websocket_pages/user/account/settings/self_exclusion');
const SettingsDetailsWS          = require('../websocket_pages/user/account/settings/settings_detailsws');
const Statement                  = require('../websocket_pages/user/account/statement/statement.init');
const TopUpVirtual               = require('../websocket_pages/user/account/top_up_virtual');
const LostPassword               = require('../websocket_pages/user/lost_password');
const MetaTrader                 = require('../websocket_pages/user/metatrader/metatrader');
const FinancialAccOpening        = require('../websocket_pages/user/new_account/financial_acc_opening');
const JapanAccOpening            = require('../websocket_pages/user/new_account/japan_acc_opening');
const RealAccOpening             = require('../websocket_pages/user/new_account/real_acc_opening');
const VirtualAccOpening          = require('../websocket_pages/user/new_account/virtual_acc_opening');
const ResetPassword              = require('../websocket_pages/user/reset_password');
const TNCApproval                = require('../websocket_pages/user/tnc_approval');

const CashierJP     = require('../../binary_japan/cashier');
const KnowledgeTest = require('../../binary_japan/knowledge_test/knowledge_test');

const pages_config = {
    account_transfer         : { module: AccountTransfer,            is_authenticated: true, only_real: true },
    api_tokenws              : { module: APITokenWS,                 is_authenticated: true },
    assessmentws             : { module: FinancialAssessment,        is_authenticated: true, only_real: true },
    asset_indexws            : { module: AssetIndexUI },
    authenticate             : { module: Authenticate,               is_authenticated: true, only_real: true },
    authorised_appsws        : { module: AuthorisedApps,             is_authenticated: true },
    careers                  : { module: Careers },
    cashier                  : { module: Cashier },
    cashier_passwordws       : { module: CashierPassword,            is_authenticated: true, only_real: true },
    change_passwordws        : { module: ChangePassword,             is_authenticated: true },
    charity                  : { module: Charity },
    contact                  : { module: Contact },
    detailsws                : { module: SettingsDetailsWS,          is_authenticated: true },
    endpoint                 : { module: Endpoint },
    epg_forwardws            : { module: DepositWithdraw,            is_authenticated: true, only_real: true },
    forwardws                : { module: DepositWithdraw,            is_authenticated: true, only_real: true },
    home                     : { module: Home,                       not_authenticated: true },
    iphistoryws              : { module: IPHistory,                  is_authenticated: true },
    japanws                  : { module: JapanAccOpening,            is_authenticated: true, only_virtual: true },
    knowledge_testws         : { module: KnowledgeTest,              is_authenticated: true, only_virtual: true },
    limitsws                 : { module: Limits,                     is_authenticated: true, only_real: true },
    logged_inws              : { module: LoggedInHandler },
    lost_passwordws          : { module: LostPassword,               not_authenticated: true },
    maltainvestws            : { module: FinancialAccOpening,        is_authenticated: true },
    market_timesws           : { module: MarketTimesUI },
    metatrader               : { module: MetaTrader,                 is_authenticated: true },
    multi_barriers_trading   : { module: MBTradePage },
    payment_agent_listws     : { module: PaymentAgentList },
    payment_methods          : { module: Cashier.PaymentMethods },
    platforms                : { module: Platforms },
    portfoliows              : { module: Portfolio,                is_authenticated: true },
    profit_tablews           : { module: ProfitTable,              is_authenticated: true },
    realws                   : { module: RealAccOpening,             is_authenticated: true, only_virtual: true },
    regulation               : { module: Regulation },
    reset_passwordws         : { module: ResetPassword,              not_authenticated: true },
    securityws               : { module: Settings,                   is_authenticated: true },
    self_exclusionws         : { module: SelfExclusionWS,            is_authenticated: true, only_real: true },
    settingsws               : { module: Settings,                   is_authenticated: true },
    signup                   : { module: StaticPages.AffiliateSignup },
    statementws              : { module: Statement,                is_authenticated: true },
    tnc_approvalws           : { module: TNCApproval,                is_authenticated: true, only_real: true },
    top_up_virtualws         : { module: TopUpVirtual,              is_authenticated: true, only_virtual: true },
    trading                  : { module: TradePage },
    trading_beta             : { module: TradePage_Beta },
    transferws               : { module: PaymentAgentTransfer,       is_authenticated: true, only_real: true },
    virtualws                : { module: VirtualAccOpening,          not_authenticated: true },
    withdrawws               : { module: PaymentAgentWithdraw,       is_authenticated: true, only_real: true },
    'careers-for-americans'  : { module: Careers },
    'deposit-jp'             : { module: CashierJP.Deposit,          is_authenticated: true, only_real: true },
    'get-started'            : { module: GetStarted },
    'get-started-jp'         : { module: GetStartedJP },
    'home-jp'                : { module: Home,                       not_authenticated: true },
    'job-details'            : { module: JobDetails },
    'open-positions'         : { module: StaticPages.OpenPositions },
    'open-source-projects'   : { module: StaticPages.OpenSourceProjects },
    'payment-agent'          : { module: StaticPages.PaymentAgent },
    'terms-and-conditions'   : { module: TermsAndConditions },
    'terms-and-conditions-jp': { module: TermsAndConditions },
    'volidx-markets'         : { module: StaticPages.VolidxMarkets },
    'why-us'                 : { module: WhyUs },
    'why-us-jp'              : { module: WhyUs },
    'withdraw-jp'            : { module: CashierJP.Withdraw,         is_authenticated: true, only_real: true },
};

module.exports = pages_config;
<|endoftext|>