<commit_before>/**
 * Brackets Themes Copyright (c) 2014 Miguel Castillo.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global $, define, require, less */

define(function (require, exports, module) {
    "use strict";

    // FontsCommandsManager will be going away soon when we add font size management in Brackets.
    require("themes/FontCommandsManager");

    var _                  = require("thirdparty/lodash"),
        FileSystem         = require("filesystem/FileSystem"),
        FileUtils          = require("file/FileUtils"),
        EditorManager      = require("editor/EditorManager"),
        ExtensionUtils     = require("utils/ExtensionUtils"),
        ThemeSettings      = require("view/ThemeSettings"),
        ThemeView          = require("view/ThemeView"),
        AppInit            = require("utils/AppInit"),
        PreferencesManager = require("preferences/PreferencesManager"),
        prefs              = PreferencesManager.getExtensionPrefs("brackets-themes");

    var loadedThemes    = {},
        defaultTheme    = "thor-light-theme",
        commentRegex    = /\/\*([\s\S]*?)\*\//mg,
        scrollbarsRegex = /::-webkit-scrollbar(?:[\s\S]*?)\{(?:[\s\S]*?)\}/mg,
        stylesPath      = FileUtils.getNativeBracketsDirectoryPath() + "/styles/",
        validExtensions = ["css", "less"];

    /**
    * @private
    * Takes all dashes and converts them to white spaces. Then takes all first letters
    * and capitalizes them.
    *
    * @param {string} name is what needs to be procseed to generate a display name
    * @return {string} theme name properly formatted for display
    */
    function toDisplayName(name) {
        var extIndex = name.lastIndexOf('.');
        name = name.substring(0, extIndex !== -1 ? extIndex : undefined).replace(/-/g, ' ');

        return name.split(" ").map(function (part) {
            return part[0].toUpperCase() + part.substring(1);
        }).join(" ");
    }

    /**
    * @constructor
    *
    * @param {File} file for the theme
    * @param {string} displayName is an optional parameter used as the display name for the theme
    */
    function Theme(file, options) {
        options = options || {};
        var fileName = file.name;

        // The name is used to map the loaded themes to the list in the settings dialog. So we want
        // a unique name if one is not provided.  This is particularly important when loading just
        // files where there is no other way to feed in meta data to provide unique names.  Say, there
        // is a theme1.css and a theme1.less that are entirely different themes...

        this.file        = file;
        this.name        = options.name      || (options.title || fileName).toLocaleLowerCase().replace(/[\W]/g, '-');
        this.className   = options.className || "theme-" + this.name;
        this.displayName = options.title     || toDisplayName(fileName);
    }

    /**
    * @private
    * Extracts the scrollbar text from the css/less content so that it can be treated
    * as a separate styling component that can be anabled/disabled independently from
    * the theme.
    *
    * @param {string} content is the css/less input string to be processed
    * @return {{content: string, scrollbar: string}} content is the new css/less content
    *    with the scrollbar rules extracted out and put in scrollbar
    */
    function extractScrollbars(content) {
        var scrollbar = [];

        // Go through and extract out scrollbar customizations so that we can
        // enable/disable via settings.
        content = content
            .replace(commentRegex, "")
            .replace(scrollbarsRegex, function (match) {
                scrollbar.push(match);
                return "";
            });

        return {
            content: content,
            scrollbar: scrollbar
        };
    }

    /**
    * @private
    * Function will process a string and figure out if it looks like window path with a
    * a drive.  If that's the case, then we lower case everything.
    * --- NOTE: There is a bug in less that only checks for lowercase in order to handle
    * the rootPath configuration...  Hopefully a PR will be coming their way soon.
    *
    * @return {string} Windows Drive letter in lowercase.
    */
    function fixPath(path) {
        return path.replace(/^([A-Z]+:)?\//, function (match) {
            return match.toLocaleLowerCase();
        });
    }

    /**
    * @private
    * Takes the content of a file and feeds it through the less processor in order
    * to provide support for less files.
    *
    * @param {string} content is the css/less string to be processed
    * @param {Theme} theme is the object the css/less corresponds to
    * @return {$.Promise} promsie with the processed css/less as the resolved value
    */
    function lessifyTheme(content, theme) {
        var deferred = new $.Deferred();
        var parser   = new less.Parser({
            rootpath: fixPath(stylesPath),
            filename: fixPath(theme.file._path)
        });

        parser.parse("." + theme.className + "{" + content + "}", function (err, tree) {
            if (err) {
                deferred.reject(err);
            } else {
                deferred.resolve(tree.toCSS());
            }
        });

        return deferred.promise();
    }

    /**
    * @private
    * Verifies that the file passed in is a valid theme file type.
    *
    * @param {File} file is object to verify if it is a valid theme file type
    * @return {boolean} to confirm if the file is a valid theme file type
    */
    function isFileTypeValid(file) {
        return file.isFile &&
            validExtensions.indexOf(FileUtils.getFileExtension(file.name)) !== -1;
    }

    /**
    * @private
    * Will search all loaded themes for one the matches the file passed in
    *
    * @param {File} file is the search criteria
    * @return {Theme} theme that matches the file
    */
    function getThemeByFile(file) {
        var path = file._path;
        return _.find(loadedThemes, function (item) {
            return item.file._path === path;
        });
    }

    /**
    * @private
    * Will trigger a refresh of codemirror instance and editor resize so that
    * inline widgets get properly rendered
    *
    * @param {CodeMiror} cm code mirror instance to refresh
    */
    function refreshEditor(cm) {
        // Really dislike timing issues with CodeMirror.  I have to refresh
        // the editor after a little bit of time to make sure that themes
        // are properly applied to quick edit widgets
        setTimeout(function () {
            cm.refresh();
            EditorManager.resizeEditor();
        }, 100);
    }

    /**
    * @private
    * Get all current theme objects
    *
    * @return {array} collection of the current theme instances
    */
    function getCurrentThemes() {
        return _.map(prefs.get("themes").slice(0), function (item) {
            return loadedThemes[item] || loadedThemes[defaultTheme];
        });
    }

    /**
     * Provides quick access to all available themes
     * @return {array} collection of all available themes
     */
    function getAllThemes() {
        return _.map(loadedThemes, function (item) {
            return item;
        });
    }

    /**
    * @private
    * Loads all current themes
    *
    * @return {$.Promise} promise object resolved with the theme object and all
    *    corresponding new css/less and scrollbar information
    */
    function loadCurrentThemes() {
        var pendingThemes = _.map(getCurrentThemes(), function (theme) {

            return theme && FileUtils.readAsText(theme.file)
                .then(function (content) {
                    var result = extractScrollbars(content);
                    theme.scrollbar = result.scrollbar;
                    return result.content;
                })
                .then(function (content) {
                    return lessifyTheme(content, theme);
                })
                .then(function (style) {
                    return ExtensionUtils.addEmbeddedStyleSheet(style);
                })
                .then(function (styleNode) {
                    // Remove after the style has been applied to avoid weird flashes
                    if (theme.css) {
                        $(theme.css).remove();
                    }

                    theme.css = styleNode;
                    return theme;
                });
        });

        return $.when.apply(undefined, pendingThemes);
    }

    /**
    * Refresh currently loaded themes
    *
    * @param <boolean> force is to force reload the current themes
    */
    function refresh(force) {
        $.when(force && loadCurrentThemes()).done(function () {
            var editor = EditorManager.getActiveEditor();
            if (!editor || !editor._codeMirror) {
                return;
            }

            var cm =  editor._codeMirror;
            ThemeView.setDocumentMode(cm);
            ThemeView.updateThemes(cm);
            refreshEditor(cm);
        });
    }

    /**
    * Loads a theme from a file.
    *
    * @param {string} fileName is the full path to the file to be opened
    * @param {object} options is an optional parameter to specify metadata
    *    for the theme.
    * @return {$.Promise} promise object resolved with the theme to be loaded from fileName
    */
    function loadFile(fileName, options) {
        options = options || {};
        var deferred      = new $.Deferred(),
            file          = FileSystem.getFileForPath(fileName),
            currentThemes = (prefs.get("themes") || []);

        file.exists(function (err, exists) {
            var theme;

            if (exists) {
                theme = new Theme(file, options);
                loadedThemes[theme.name] = theme;
                ThemeSettings._setThemes(loadedThemes);

                // For themes that are loaded after ThemeManager has been loaded,
                // we should check if it's the current theme.  It is, then we just
                // load it.
                if (currentThemes.indexOf(theme.name) !== -1) {
                    refresh(true);
                }

                deferred.resolve(theme);
            } else if (err) {
                deferred.reject(err);
            }
        });

        return deferred.promise();
    }

    /**
    * Loads a theme from an extension package.
    *
    * @param {package} themePackage is a package for the theme to be loaded.
    * @return {$.Promise} promise object resolved with the theme to be loaded from the pacakge
    */
    function loadPackage(themePackage) {
        var fileName = themePackage.path + "/" + themePackage.metadata.theme;
        return loadFile(fileName, themePackage.metadata);
    }

    /**
    * Load css/less files from a directory to be treated as themes
    *
    * @param {string} path where theme files are to be loaded from
    * @return {$.Promise} promise object resolved with the themes to be loaded from the directory
    */
    function loadDirectory(path) {
        var result = new $.Deferred();

        if (!path) {
            return result.reject({
                path: path,
                error: "Path not defined"
            });
        }

        function readContent(err, entries) {
            var i, files = [];
            entries = entries || [];

            for (i = 0; i < entries.length; i++) {
                if (isFileTypeValid(entries[i])) {
                    files.push(entries[i].name);
                }
            }

            if (err) {
                result.reject({
                    path: path,
                    error: err
                });
            } else {
                result.resolve({
                    files: files,
                    path: path
                });
            }
        }

        function loadThemesFiles(themes) {
            // Iterate through each name in the themes and make them theme objects
            var deferred = _.map(themes.files, function (themeFile) {
                return loadFile(themes.path + "/" + themeFile);
            });

            return $.when.apply(undefined, deferred);
        }

        FileSystem.getDirectoryForPath(path).getContents(readContent);
        return result.then(loadThemesFiles);
    }

    function init() {
        prefs.on("change", "themes", function () {
            refresh(true);
            ThemeView.updateScrollbars(getCurrentThemes()[0]);

            // Expose event for theme changes
            $(exports).trigger("themeChange", getCurrentThemes());
        });

        prefs.on("change", "customScrollbars", function () {
            refresh();
            ThemeView.updateScrollbars(getCurrentThemes()[0]);
        });

        prefs.on("change", "fontSize", function () {
            refresh();
            ThemeView.updateFontSize();
        });

        prefs.on("change", "lineHeight", function () {
            refresh();
            ThemeView.updateLineHeight();
        });

        prefs.on("change", "fontFamily", function () {
            refresh();
            ThemeView.updateFontFamily();
        });

        FileSystem.on("change", function (evt, file) {
            if (file.isDirectory) {
                return;
            }

            if (getThemeByFile(file)) {
                refresh(true);
            }
        });

        $(EditorManager).on("activeEditorChange", function () {
            refresh();
        });

        refresh(true);
        ThemeView.updateFonts();
        ThemeView.updateScrollbars();
    }

    // When the app is ready, we need to try to load whatever theme needs to be processed
    AppInit.appReady(init);

    exports.refresh          = refresh;
    exports.loadFile         = loadFile;
    exports.loadPackage      = loadPackage;
    exports.loadDirectory    = loadDirectory;
    exports.getCurrentThemes = getCurrentThemes;
    exports.getAllThemes     = getAllThemes;

    // Exposed for testing purposes
    exports._toDisplayName     = toDisplayName;
    exports._extractScrollbars = extractScrollbars;
});
<commit_msg>Removed code that's causing issue closing inline widgets<commit_after>/**
 * Brackets Themes Copyright (c) 2014 Miguel Castillo.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global $, define, require, less */

define(function (require, exports, module) {
    "use strict";

    // FontsCommandsManager will be going away soon when we add font size management in Brackets.
    require("themes/FontCommandsManager");

    var _                  = require("thirdparty/lodash"),
        FileSystem         = require("filesystem/FileSystem"),
        FileUtils          = require("file/FileUtils"),
        EditorManager      = require("editor/EditorManager"),
        ExtensionUtils     = require("utils/ExtensionUtils"),
        ThemeSettings      = require("view/ThemeSettings"),
        ThemeView          = require("view/ThemeView"),
        AppInit            = require("utils/AppInit"),
        PreferencesManager = require("preferences/PreferencesManager"),
        prefs              = PreferencesManager.getExtensionPrefs("brackets-themes");

    var loadedThemes    = {},
        defaultTheme    = "thor-light-theme",
        commentRegex    = /\/\*([\s\S]*?)\*\//mg,
        scrollbarsRegex = /::-webkit-scrollbar(?:[\s\S]*?)\{(?:[\s\S]*?)\}/mg,
        stylesPath      = FileUtils.getNativeBracketsDirectoryPath() + "/styles/",
        validExtensions = ["css", "less"];

    /**
    * @private
    * Takes all dashes and converts them to white spaces. Then takes all first letters
    * and capitalizes them.
    *
    * @param {string} name is what needs to be procseed to generate a display name
    * @return {string} theme name properly formatted for display
    */
    function toDisplayName(name) {
        var extIndex = name.lastIndexOf('.');
        name = name.substring(0, extIndex !== -1 ? extIndex : undefined).replace(/-/g, ' ');

        return name.split(" ").map(function (part) {
            return part[0].toUpperCase() + part.substring(1);
        }).join(" ");
    }

    /**
    * @constructor
    *
    * @param {File} file for the theme
    * @param {string} displayName is an optional parameter used as the display name for the theme
    */
    function Theme(file, options) {
        options = options || {};
        var fileName = file.name;

        // The name is used to map the loaded themes to the list in the settings dialog. So we want
        // a unique name if one is not provided.  This is particularly important when loading just
        // files where there is no other way to feed in meta data to provide unique names.  Say, there
        // is a theme1.css and a theme1.less that are entirely different themes...

        this.file        = file;
        this.name        = options.name      || (options.title || fileName).toLocaleLowerCase().replace(/[\W]/g, '-');
        this.className   = options.className || "theme-" + this.name;
        this.displayName = options.title     || toDisplayName(fileName);
    }

    /**
    * @private
    * Extracts the scrollbar text from the css/less content so that it can be treated
    * as a separate styling component that can be anabled/disabled independently from
    * the theme.
    *
    * @param {string} content is the css/less input string to be processed
    * @return {{content: string, scrollbar: string}} content is the new css/less content
    *    with the scrollbar rules extracted out and put in scrollbar
    */
    function extractScrollbars(content) {
        var scrollbar = [];

        // Go through and extract out scrollbar customizations so that we can
        // enable/disable via settings.
        content = content
            .replace(commentRegex, "")
            .replace(scrollbarsRegex, function (match) {
                scrollbar.push(match);
                return "";
            });

        return {
            content: content,
            scrollbar: scrollbar
        };
    }

    /**
    * @private
    * Function will process a string and figure out if it looks like window path with a
    * a drive.  If that's the case, then we lower case everything.
    * --- NOTE: There is a bug in less that only checks for lowercase in order to handle
    * the rootPath configuration...  Hopefully a PR will be coming their way soon.
    *
    * @return {string} Windows Drive letter in lowercase.
    */
    function fixPath(path) {
        return path.replace(/^([A-Z]+:)?\//, function (match) {
            return match.toLocaleLowerCase();
        });
    }

    /**
    * @private
    * Takes the content of a file and feeds it through the less processor in order
    * to provide support for less files.
    *
    * @param {string} content is the css/less string to be processed
    * @param {Theme} theme is the object the css/less corresponds to
    * @return {$.Promise} promsie with the processed css/less as the resolved value
    */
    function lessifyTheme(content, theme) {
        var deferred = new $.Deferred();
        var parser   = new less.Parser({
            rootpath: fixPath(stylesPath),
            filename: fixPath(theme.file._path)
        });

        parser.parse("." + theme.className + "{" + content + "}", function (err, tree) {
            if (err) {
                deferred.reject(err);
            } else {
                deferred.resolve(tree.toCSS());
            }
        });

        return deferred.promise();
    }

    /**
    * @private
    * Verifies that the file passed in is a valid theme file type.
    *
    * @param {File} file is object to verify if it is a valid theme file type
    * @return {boolean} to confirm if the file is a valid theme file type
    */
    function isFileTypeValid(file) {
        return file.isFile &&
            validExtensions.indexOf(FileUtils.getFileExtension(file.name)) !== -1;
    }

    /**
    * @private
    * Will search all loaded themes for one the matches the file passed in
    *
    * @param {File} file is the search criteria
    * @return {Theme} theme that matches the file
    */
    function getThemeByFile(file) {
        var path = file._path;
        return _.find(loadedThemes, function (item) {
            return item.file._path === path;
        });
    }

    /**
    * @private
    * Get all current theme objects
    *
    * @return {array} collection of the current theme instances
    */
    function getCurrentThemes() {
        return _.map(prefs.get("themes").slice(0), function (item) {
            return loadedThemes[item] || loadedThemes[defaultTheme];
        });
    }

    /**
     * Provides quick access to all available themes
     * @return {array} collection of all available themes
     */
    function getAllThemes() {
        return _.map(loadedThemes, function (item) {
            return item;
        });
    }

    /**
    * @private
    * Loads all current themes
    *
    * @return {$.Promise} promise object resolved with the theme object and all
    *    corresponding new css/less and scrollbar information
    */
    function loadCurrentThemes() {
        var pendingThemes = _.map(getCurrentThemes(), function (theme) {

            return theme && FileUtils.readAsText(theme.file)
                .then(function (content) {
                    var result = extractScrollbars(content);
                    theme.scrollbar = result.scrollbar;
                    return result.content;
                })
                .then(function (content) {
                    return lessifyTheme(content, theme);
                })
                .then(function (style) {
                    return ExtensionUtils.addEmbeddedStyleSheet(style);
                })
                .then(function (styleNode) {
                    // Remove after the style has been applied to avoid weird flashes
                    if (theme.css) {
                        $(theme.css).remove();
                    }

                    theme.css = styleNode;
                    return theme;
                });
        });

        return $.when.apply(undefined, pendingThemes);
    }

    /**
    * Refresh currently loaded themes
    *
    * @param <boolean> force is to force reload the current themes
    */
    function refresh(force) {
        $.when(force && loadCurrentThemes()).done(function () {
            var editor = EditorManager.getActiveEditor();
            if (!editor || !editor._codeMirror) {
                return;
            }

            var cm =  editor._codeMirror;
            ThemeView.setDocumentMode(cm);
            ThemeView.updateThemes(cm);
        });
    }

    /**
    * Loads a theme from a file.
    *
    * @param {string} fileName is the full path to the file to be opened
    * @param {object} options is an optional parameter to specify metadata
    *    for the theme.
    * @return {$.Promise} promise object resolved with the theme to be loaded from fileName
    */
    function loadFile(fileName, options) {
        options = options || {};
        var deferred      = new $.Deferred(),
            file          = FileSystem.getFileForPath(fileName),
            currentThemes = (prefs.get("themes") || []);

        file.exists(function (err, exists) {
            var theme;

            if (exists) {
                theme = new Theme(file, options);
                loadedThemes[theme.name] = theme;
                ThemeSettings._setThemes(loadedThemes);

                // For themes that are loaded after ThemeManager has been loaded,
                // we should check if it's the current theme.  It is, then we just
                // load it.
                if (currentThemes.indexOf(theme.name) !== -1) {
                    refresh(true);
                }

                deferred.resolve(theme);
            } else if (err) {
                deferred.reject(err);
            }
        });

        return deferred.promise();
    }

    /**
    * Loads a theme from an extension package.
    *
    * @param {package} themePackage is a package for the theme to be loaded.
    * @return {$.Promise} promise object resolved with the theme to be loaded from the pacakge
    */
    function loadPackage(themePackage) {
        var fileName = themePackage.path + "/" + themePackage.metadata.theme;
        return loadFile(fileName, themePackage.metadata);
    }

    /**
    * Load css/less files from a directory to be treated as themes
    *
    * @param {string} path where theme files are to be loaded from
    * @return {$.Promise} promise object resolved with the themes to be loaded from the directory
    */
    function loadDirectory(path) {
        var result = new $.Deferred();

        if (!path) {
            return result.reject({
                path: path,
                error: "Path not defined"
            });
        }

        function readContent(err, entries) {
            var i, files = [];
            entries = entries || [];

            for (i = 0; i < entries.length; i++) {
                if (isFileTypeValid(entries[i])) {
                    files.push(entries[i].name);
                }
            }

            if (err) {
                result.reject({
                    path: path,
                    error: err
                });
            } else {
                result.resolve({
                    files: files,
                    path: path
                });
            }
        }

        function loadThemesFiles(themes) {
            // Iterate through each name in the themes and make them theme objects
            var deferred = _.map(themes.files, function (themeFile) {
                return loadFile(themes.path + "/" + themeFile);
            });

            return $.when.apply(undefined, deferred);
        }

        FileSystem.getDirectoryForPath(path).getContents(readContent);
        return result.then(loadThemesFiles);
    }

    function init() {
        prefs.on("change", "themes", function () {
            refresh(true);
            ThemeView.updateScrollbars(getCurrentThemes()[0]);

            // Expose event for theme changes
            $(exports).trigger("themeChange", getCurrentThemes());
        });

        prefs.on("change", "customScrollbars", function () {
            refresh();
            ThemeView.updateScrollbars(getCurrentThemes()[0]);
        });

        prefs.on("change", "fontSize", function () {
            refresh();
            ThemeView.updateFontSize();
        });

        prefs.on("change", "lineHeight", function () {
            refresh();
            ThemeView.updateLineHeight();
        });

        prefs.on("change", "fontFamily", function () {
            refresh();
            ThemeView.updateFontFamily();
        });

        FileSystem.on("change", function (evt, file) {
            if (file.isDirectory) {
                return;
            }

            if (getThemeByFile(file)) {
                refresh(true);
            }
        });

        $(EditorManager).on("activeEditorChange", function () {
            refresh();
        });

        refresh(true);
        ThemeView.updateFonts();
        ThemeView.updateScrollbars();
    }

    // When the app is ready, we need to try to load whatever theme needs to be processed
    AppInit.appReady(init);

    exports.refresh          = refresh;
    exports.loadFile         = loadFile;
    exports.loadPackage      = loadPackage;
    exports.loadDirectory    = loadDirectory;
    exports.getCurrentThemes = getCurrentThemes;
    exports.getAllThemes     = getAllThemes;

    // Exposed for testing purposes
    exports._toDisplayName     = toDisplayName;
    exports._extractScrollbars = extractScrollbars;
});
<|endoftext|>


<commit_before>var testingInstanceGet = false;
var testingInstanceParent = false;
var testingData = false;

Template.noop.created = function () {
  this._testTemplateFieldNoop = 50;
};

Template.testTemplate.created = function () {
  this._testTemplateField = 42;
};

Template.testTemplate.helpers({
  data: function () {
    return _.extend({}, this, {data1: 'foo'});
  }
});

Template.testTemplate1.created = function () {
  this._testTemplateField1 = 43;
};

Template.testTemplate1.helpers({
  data: function () {
    // We add data2, but remove data1.
    return _.omit(_.extend({}, this, {data2: 'bar'}), 'data1');
  }
});

Template.testTemplate2.created = function () {
  this._testTemplateField3 = 44;
};

Template.testTemplate2.helpers({
  testInstanceGet: function () {
    if (testingInstanceGet) return EJSON.stringify(Template.instance().get(this.fieldName));
  },

  testInstanceParent: function () {
    if (!testingInstanceParent) return;

    var ancestors = [];
    var template = Template.instance();
    while (template) {
      // Only fields which start with _.
      ancestors.push(_.pick(template, _.filter(_.keys(template), function (key) {return key.substr(0, 1) === '_';})));
      template = template.parent(this.numLevels, this.includeBlockHelpers);
    }
    return EJSON.stringify(ancestors);
  },

  testData: function () {
    if (testingData) return EJSON.stringify(Template.parentData(this.numLevels));
  }
});

Template.testTemplate3.events({
  'click #button': function () {
    return true;
  }
});

Template.testTemplate4.events({
  'mousemove .current': function () {
    return true;
  }
});

Template.testTemplate8.hooks({
  rendered: function () {
    this._testTemplateField4 = 14;
  }
});

Template.testTemplate9.hooks({
  created: function () {
    this._testTemplateField5 = 14;
  }
});

Template.testTemplate9.helpers({
  copyAsHelper: function () {
    return 'copyAs';
  }
});

Template.testTemplate17.hooks({
  created: function () {
    this._testTemplateField7 = 14;
  },
  rendered: function () {
    this._testTemplateField7 = 15;
  },
  destroyed: function () {
    this._testTemplateField7 = 16;
  },
});

Template.onCreated(function () {
  this.testTemplateField14 = 14;
});

Template.onRendered(function () {
  this.testTemplateField15 = 15;
});

Template.onDestroyed(function () {
  this.testTemplateField16 = 16;
});

Tinytest.add('template-extension - get', function (test) {
  testingInstanceGet = true;
  try {
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateField'}), '42');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateField1'}), '43');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateField3'}), '44');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateFieldNoop'}), '50');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_nonexistent'}), '');
  }
  finally {
    testingInstanceGet = false;
  }
});

Tinytest.add('template-extension - parent', function (test) {
  testingInstanceParent = true;
  try {
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 1, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 1, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: undefined, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: undefined, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: null, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: null, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 2, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 2, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateField1":43}]');
  }
  finally {
    testingInstanceParent = false;
  }
});

Tinytest.add('template-extension - parentData', function (test) {
  testingData = true;
  try {
    // Testing default behavior.
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {}), '{"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: undefined}), '{"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: null}), '{"numLevels":null,"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 0}), '{"numLevels":0,"data2":"bar"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 1}), '{"numLevels":1,"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 2}), '{"numLevels":2}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 3}), 'null');

    // Testing a function.
    var hasField = function (fieldName) {
      return function (data) {
        return fieldName in data;
      };
    };

    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: hasField('data1')}), '{"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: hasField('data2')}), '{"data2":"bar"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: hasField('data3')}), 'null');
  }
  finally {
    testingData = false;
  }
});

Tinytest.add('template-extension - inheritsHelpersFrom', function (test) {
  Template.testTemplate3.inheritsHelpersFrom('testTemplate2');
  test.equal(Template.testTemplate2.__helpers[' testInstanceGet'], Template.testTemplate3.__helpers[' testInstanceGet']);
});

Tinytest.add('template-extension - inheritsHelpersFrom array', function (test) {
  Template.testTemplate4.inheritsHelpersFrom(['testTemplate1', 'testTemplate2']);
  test.equal(Template.testTemplate2.__helpers[' testInstanceGet'], Template.testTemplate4.__helpers[' testInstanceGet']);
  test.equal(Template.testTemplate1.__helpers[' data'], Template.testTemplate4.__helpers[' data']);
});

Tinytest.add('template-extension - inheritsEventsFrom', function (test) {
  Template.testTemplate5.inheritsEventsFrom('testTemplate3');
  test.equal(Template.testTemplate3.__eventMaps, Template.testTemplate5.__eventMaps);
});

Tinytest.add('template-extension - inheritsEventsFrom array', function (test) {
  Template.testTemplate6.inheritsEventsFrom(['testTemplate3', 'testTemplate4']);
  test.equal(Template.testTemplate3.__eventMaps[0], Template.testTemplate6.__eventMaps[0]);
  test.equal(Template.testTemplate4.__eventMaps[0], Template.testTemplate6.__eventMaps[1]);
});

Tinytest.add('template-extension - inheritsHooksFrom', function (test) {
  Template.testTemplate7.inheritsHooksFrom('testTemplate');
  Template.testTemplate7.created();
  test.equal(Template.testTemplate7._testTemplateField, 42);
});

Tinytest.add('template-extension - inheritsHooksFrom array', function (test) {
  Template.testTemplate9.inheritsHooksFrom(['testTemplate', 'testTemplate8']);
  Template.testTemplate9.created();
  Template.testTemplate9.rendered();
  test.equal(Template.testTemplate9._testTemplateField, 42);
  test.equal(Template.testTemplate9._testTemplateField4, 14);
});

Tinytest.add('template-extension - copyAs', function (test) {
  Template.testTemplate9.copyAs('testTemplate10');
  Template.testTemplate10.created();
  test.equal(Blaze.toHTML(Template.testTemplate10),'
copyAs
');
  test.equal(Template.testTemplate10._testTemplateField5, 14);
});

Tinytest.add('template-extension - copyAs array', function (test) {
  Template.testTemplate9.copyAs(['testTemplate11', 'testTemplate12']);
  Template.testTemplate11.created();
  Template.testTemplate12.created();
  test.equal(Blaze.toHTML(Template.testTemplate11),'
copyAs
');
  test.equal(Blaze.toHTML(Template.testTemplate12),'
copyAs
');
  test.equal(Template.testTemplate11._testTemplateField5, 14);
  test.equal(Template.testTemplate12._testTemplateField5, 14);
});

Tinytest.add('template-extension - copyAs returns newly created template', function (test) {
  var result = Template.testTemplate.copyAs('testTemplate3');
  test.instanceOf(result, Blaze.Template);
});

Tinytest.add('template-extension - copyAs returns newly created template array', function (test) {
  var result = Template.testTemplate.copyAs(['testTemplate3', 'testTemplate4']);
  test.instanceOf(result, Array);
  test.instanceOf(result[0], Blaze.Template);
  test.instanceOf(result[1], Blaze.Template);
});

Tinytest.add('template-extension - replaces', function (test) {
  Template.testTemplate9.replaces('testTemplate14');
  Template.testTemplate14.inheritsHelpersFrom('testTemplate9');
  test.equal(Blaze.toHTML(Template.testTemplate14),'
copyAs
');
});

Tinytest.add('template-extension - replaces array', function (test) {
  Template.testTemplate9.replaces(['testTemplate15', 'testTemplate16']);
  Template.testTemplate15.inheritsHelpersFrom('testTemplate9');
  Template.testTemplate16.inheritsHelpersFrom('testTemplate9');
  test.equal(Blaze.toHTML(Template.testTemplate15),'
copyAs
');
  test.equal(Blaze.toHTML(Template.testTemplate16),'
copyAs
');
});

Tinytest.add('template-extension - hooks', function (test) {
  Template.testTemplate17.created();
  test.equal(Template.testTemplate17._testTemplateField7, 14);
  Template.testTemplate17.rendered();
  test.equal(Template.testTemplate17._testTemplateField7, 15);
  Template.testTemplate17.destroyed();
  test.equal(Template.testTemplate17._testTemplateField7, 16);
});

Tinytest.add('template-extension - onCreated', function (test) {
  Template.testTemplate20.created();
  test.equal(Template.testTemplate20.testTemplateField14, 14);
});

Tinytest.add('template-extension - onRendered', function (test) {
  Template.testTemplate20.rendered();
  test.equal(Template.testTemplate20.testTemplateField15, 15);
});

Tinytest.add('template-extension - onDestroyed', function (test) {
  Template.testTemplate20.destroyed();
  test.equal(Template.testTemplate20.testTemplateField16, 16);
});
<commit_msg>fix tests for Meteor 1.0.4<commit_after>var testingInstanceGet = false;
var testingInstanceParent = false;
var testingData = false;

Template.noop.created = function () {
  this._testTemplateFieldNoop = 50;
};

Template.testTemplate.created = function () {
  this._testTemplateField = 42;
};

Template.testTemplate.helpers({
  data: function () {
    return _.extend({}, this, {data1: 'foo'});
  }
});

Template.testTemplate1.created = function () {
  this._testTemplateField1 = 43;
};

Template.testTemplate1.helpers({
  data: function () {
    // We add data2, but remove data1.
    return _.omit(_.extend({}, this, {data2: 'bar'}), 'data1');
  }
});

Template.testTemplate2.created = function () {
  this._testTemplateField3 = 44;
};

Template.testTemplate2.helpers({
  testInstanceGet: function () {
    if (testingInstanceGet) return EJSON.stringify(Template.instance().get(this.fieldName));
  },

  testInstanceParent: function () {
    if (!testingInstanceParent) return;

    var ancestors = [];
    var template = Template.instance();
    while (template) {
      // Only fields which start with _.
      ancestors.push(_.pick(template, _.filter(_.keys(template), function (key) {
        return key.substr(0, 1) === '_' &&
          key !== '_allSubsReadyDep' &&
          key !== '_allSubsReady' &&
          key !== '_subscriptionHandles';
      })));
      template = template.parent(this.numLevels, this.includeBlockHelpers);
    }
    return EJSON.stringify(ancestors);
  },

  testData: function () {
    if (testingData) return EJSON.stringify(Template.parentData(this.numLevels));
  }
});

Template.testTemplate3.events({
  'click #button': function () {
    return true;
  }
});

Template.testTemplate4.events({
  'mousemove .current': function () {
    return true;
  }
});

Template.testTemplate8.hooks({
  rendered: function () {
    this._testTemplateField4 = 14;
  }
});

Template.testTemplate9.hooks({
  created: function () {
    this._testTemplateField5 = 14;
  }
});

Template.testTemplate9.helpers({
  copyAsHelper: function () {
    return 'copyAs';
  }
});

Template.testTemplate17.hooks({
  created: function () {
    this._testTemplateField7 = 14;
  },
  rendered: function () {
    this._testTemplateField7 = 15;
  },
  destroyed: function () {
    this._testTemplateField7 = 16;
  },
});

Template.onCreated(function () {
  this.testTemplateField14 = 14;
});

Template.onRendered(function () {
  this.testTemplateField15 = 15;
});

Template.onDestroyed(function () {
  this.testTemplateField16 = 16;
});

Tinytest.add('template-extension - get', function (test) {
  testingInstanceGet = true;
  try {
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateField'}), '42');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateField1'}), '43');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateField3'}), '44');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_testTemplateFieldNoop'}), '50');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {fieldName: '_nonexistent'}), '');
  }
  finally {
    testingInstanceGet = false;
  }
});

Tinytest.add('template-extension - parent', function (test) {
  testingInstanceParent = true;
  try {
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 1, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 1, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: undefined, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: undefined, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: null, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: null, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateFieldNoop":50},{"_testTemplateField1":43},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 2, includeBlockHelpers: false}), '[{"_testTemplateField3":44},{"_testTemplateField":42}]');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 2, includeBlockHelpers: true}), '[{"_testTemplateField3":44},{"_testTemplateField1":43}]');
  }
  finally {
    testingInstanceParent = false;
  }
});

Tinytest.add('template-extension - parentData', function (test) {
  testingData = true;
  try {
    // Testing default behavior.
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {}), '{"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: undefined}), '{"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: null}), '{"numLevels":null,"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 0}), '{"numLevels":0,"data2":"bar"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 1}), '{"numLevels":1,"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 2}), '{"numLevels":2}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: 3}), 'null');

    // Testing a function.
    var hasField = function (fieldName) {
      return function (data) {
        return fieldName in data;
      };
    };

    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: hasField('data1')}), '{"data1":"foo"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: hasField('data2')}), '{"data2":"bar"}');
    test.equal(Blaze.toHTMLWithData(Template.testTemplate, {numLevels: hasField('data3')}), 'null');
  }
  finally {
    testingData = false;
  }
});

Tinytest.add('template-extension - inheritsHelpersFrom', function (test) {
  Template.testTemplate3.inheritsHelpersFrom('testTemplate2');
  test.equal(Template.testTemplate2.__helpers[' testInstanceGet'], Template.testTemplate3.__helpers[' testInstanceGet']);
});

Tinytest.add('template-extension - inheritsHelpersFrom array', function (test) {
  Template.testTemplate4.inheritsHelpersFrom(['testTemplate1', 'testTemplate2']);
  test.equal(Template.testTemplate2.__helpers[' testInstanceGet'], Template.testTemplate4.__helpers[' testInstanceGet']);
  test.equal(Template.testTemplate1.__helpers[' data'], Template.testTemplate4.__helpers[' data']);
});

Tinytest.add('template-extension - inheritsEventsFrom', function (test) {
  Template.testTemplate5.inheritsEventsFrom('testTemplate3');
  test.equal(Template.testTemplate3.__eventMaps, Template.testTemplate5.__eventMaps);
});

Tinytest.add('template-extension - inheritsEventsFrom array', function (test) {
  Template.testTemplate6.inheritsEventsFrom(['testTemplate3', 'testTemplate4']);
  test.equal(Template.testTemplate3.__eventMaps[0], Template.testTemplate6.__eventMaps[0]);
  test.equal(Template.testTemplate4.__eventMaps[0], Template.testTemplate6.__eventMaps[1]);
});

Tinytest.add('template-extension - inheritsHooksFrom', function (test) {
  Template.testTemplate7.inheritsHooksFrom('testTemplate');
  Template.testTemplate7.created();
  test.equal(Template.testTemplate7._testTemplateField, 42);
});

Tinytest.add('template-extension - inheritsHooksFrom array', function (test) {
  Template.testTemplate9.inheritsHooksFrom(['testTemplate', 'testTemplate8']);
  Template.testTemplate9.created();
  Template.testTemplate9.rendered();
  test.equal(Template.testTemplate9._testTemplateField, 42);
  test.equal(Template.testTemplate9._testTemplateField4, 14);
});

Tinytest.add('template-extension - copyAs', function (test) {
  Template.testTemplate9.copyAs('testTemplate10');
  Template.testTemplate10.created();
  test.equal(Blaze.toHTML(Template.testTemplate10),'
copyAs
');
  test.equal(Template.testTemplate10._testTemplateField5, 14);
});

Tinytest.add('template-extension - copyAs array', function (test) {
  Template.testTemplate9.copyAs(['testTemplate11', 'testTemplate12']);
  Template.testTemplate11.created();
  Template.testTemplate12.created();
  test.equal(Blaze.toHTML(Template.testTemplate11),'
copyAs
');
  test.equal(Blaze.toHTML(Template.testTemplate12),'
copyAs
');
  test.equal(Template.testTemplate11._testTemplateField5, 14);
  test.equal(Template.testTemplate12._testTemplateField5, 14);
});

Tinytest.add('template-extension - copyAs returns newly created template', function (test) {
  var result = Template.testTemplate.copyAs('testTemplate3');
  test.instanceOf(result, Blaze.Template);
});

Tinytest.add('template-extension - copyAs returns newly created template array', function (test) {
  var result = Template.testTemplate.copyAs(['testTemplate3', 'testTemplate4']);
  test.instanceOf(result, Array);
  test.instanceOf(result[0], Blaze.Template);
  test.instanceOf(result[1], Blaze.Template);
});

Tinytest.add('template-extension - replaces', function (test) {
  Template.testTemplate9.replaces('testTemplate14');
  Template.testTemplate14.inheritsHelpersFrom('testTemplate9');
  test.equal(Blaze.toHTML(Template.testTemplate14),'
copyAs
');
});

Tinytest.add('template-extension - replaces array', function (test) {
  Template.testTemplate9.replaces(['testTemplate15', 'testTemplate16']);
  Template.testTemplate15.inheritsHelpersFrom('testTemplate9');
  Template.testTemplate16.inheritsHelpersFrom('testTemplate9');
  test.equal(Blaze.toHTML(Template.testTemplate15),'
copyAs
');
  test.equal(Blaze.toHTML(Template.testTemplate16),'
copyAs
');
});

Tinytest.add('template-extension - hooks', function (test) {
  Template.testTemplate17.created();
  test.equal(Template.testTemplate17._testTemplateField7, 14);
  Template.testTemplate17.rendered();
  test.equal(Template.testTemplate17._testTemplateField7, 15);
  Template.testTemplate17.destroyed();
  test.equal(Template.testTemplate17._testTemplateField7, 16);
});

Tinytest.add('template-extension - onCreated', function (test) {
  Template.testTemplate20.created();
  test.equal(Template.testTemplate20.testTemplateField14, 14);
});

Tinytest.add('template-extension - onRendered', function (test) {
  Template.testTemplate20.rendered();
  test.equal(Template.testTemplate20.testTemplateField15, 15);
});

Tinytest.add('template-extension - onDestroyed', function (test) {
  Template.testTemplate20.destroyed();
  test.equal(Template.testTemplate20.testTemplateField16, 16);
});
<|endoftext|>


<commit_before>import _ from 'lodash';
import sandbox from 'test/utils/Sandbox-util';

//
// jQuery Mock
//
const jQueryObject = {
  on: sandbox.stub().returnsThis(),
};

function jQuery() {
  return jQueryObject;
}

jQuery.ajax = sandbox.stub().returnsThis();

//
// jQuery Plugin Mocks
//
const jQueryPlugins = {
  checkbox: sandbox.stub().returnsThis(),
  dropdown: sandbox.stub().returnsThis(),
  modal: sandbox.stub().returnsThis(),
  popup: sandbox.stub().returnsThis(),
  progress: sandbox.stub().returnsThis(),
  transition: sandbox.stub().returnsThis(),
};

// Extend jQuery with plugins
_.assign(jQueryObject, jQueryPlugins);

export default jQuery;
<commit_msg>add Semantic UI form jquery mock<commit_after>import _ from 'lodash';
import sandbox from 'test/utils/Sandbox-util';

//
// jQuery Mock
//
const jQueryObject = {
  on: sandbox.stub().returnsThis(),
};

function jQuery() {
  return jQueryObject;
}

jQuery.ajax = sandbox.stub().returnsThis();

//
// jQuery Plugin Mocks
//
const jQueryPlugins = {
  checkbox: sandbox.stub().returnsThis(),
  dropdown: sandbox.stub().returnsThis(),
  form: sandbox.stub().returnsThis(),
  modal: sandbox.stub().returnsThis(),
  popup: sandbox.stub().returnsThis(),
  progress: sandbox.stub().returnsThis(),
  transition: sandbox.stub().returnsThis(),
};

// Extend jQuery with plugins
_.assign(jQueryObject, jQueryPlugins);

export default jQuery;
<|endoftext|>


<commit_before><commit_msg>Fixes auto-open timing issue<commit_after><|endoftext|>


<commit_before>// ==UserScript==
// @name         Auto mp3 downloader
// @description  Automatic download of multiple mp3s from http://www.emp3z.ws/. Just insert list of songs (each on new line) into textarea, push the button and wait till it finishes.
// @author       Bladito
// @version      0.2
// @match        http://www.emp3z.ws/*
// @namespace    Bladito/auto-mp3-downloader
// @grant        none
// ==/UserScript==

var storageName = 'Bladito_autoDownloader_toDL';
var storageStateName = 'Bladito_autoDownloader_state';
var storageNowFinding = 'Bladito_autoDownloader_nowFinding';
var storageDebugName = 'Bladito_autoDownloader_isDebug';

var STATE = {
    IDLE:'IDLE',
    FINDING_URLS:'FINDING_URLS',
    DOWNLOADING:'DOWNLOADING'
};

(function() {
    'use strict';

    init();

    log('state=',getState());

    if (getState() === STATE.IDLE || getState() === STATE.DOWNLOADING) {
        insertCustomHTMLElements();
    }

    if (getState() === STATE.FINDING_URLS) {
        if (isResultsPage()) {
            log('opening download page');
            window.location.href = $('a[href^="/download"')[0].href;
        } else if (isDetailPage()) {
            $('#dl1').click();
            rememberUrlForSong();
        }
    }

    if (getState() === STATE.DOWNLOADING) {
        downloadSongs();
    }

})();

function init() {
    if (!getState()) {
        setState(STATE.IDLE);
    }

    if (getState() === STATE.FINDING_URLS && !getNextSongToFind()) {
        setState(STATE.DOWNLOADING);
    }
}

function findDownloadUrls() {
    var mp3List, textAreaVal = $('#my-dl-list').val();

    if (textAreaVal && textAreaVal.length > 0) {
        mp3List = prepareMp3List(textAreaVal);

        setMp3List(mp3List);
        setState(STATE.FINDING_URLS);

        log('to dl ', JSON.stringify(mp3List));

        findUrlForNextSong();
    } else {
        alert('No songs inserted into textarea!');
    }
}

function findUrlForNextSong() {
    var nextSong = getNextSongToFind();

    if (nextSong) {
        log('gonna find ', nextSong.name);

        setNowFinding(nextSong.name);
        $('input[name="search"]').val(nextSong.name);
        $('input[name="search"]').parents('form').submit();
    } else {
        location.href = 'http://www.emp3z.ws/';
    }
}

function setFoundUrlForCurrentSong(songUrl) {
    var currentSong = getNowFinding(),
        list = getMp3List();

    for (var i=0; i<list.length; i+=1) {
        if (list[i].name === currentSong) {
            list[i].url = songUrl;
            log('SAVED URL ', songUrl, ' for song ', list[i].name);
            break;
        }
    }

    setMp3List(list);
}

function getNextSongToFind() {
    var list = getMp3List();

    for (var i=0; i<list.length; i+=1) {
        if (list[i].url === undefined) {
            return list[i];
        }
    }

    return undefined;
}

function prepareMp3List(mp3listString) {
    var result = [],
        list = mp3listString.split('\n');

    for (var i=0; i<list.length; i+=1) {
        result.push({
            name: list[i]
        });
    }

    return result;
}

function rememberUrlForSong() {
    var interval = setInterval(function() {
        log('finding link...');

        var link = $($('iframe')[0].contentWindow.document).find('#ytd')[0];
        if (link && link.href && link.href.length > 0) {
            window.clearInterval(interval);
            log('found link! ', link.href);
            setFoundUrlForCurrentSong(link.href);
            findUrlForNextSong();
        }
    }, 1000);
}

function downloadSongs() {
    var list = getMp3List(),
        output = '\n';

    for (var i=0; i<list.length; i+=1) {
        window.open(list[i].url, '_blank');
        list[i].downloaded = true;
        output += ' - ' + list[i].name + '\n';
    }

    setMp3List(list);
    setState(STATE.IDLE);
    setNowFinding(null);
    alert('Download initiated. ' + list.length + ' songs.' + output);
}

function insertCustomHTMLElements() {
    var customElements, searchForm = $('form[action^="/search"]');

    customElements =
        '
' +
          '<textarea id="my-dl-list" class="form-control" style="height: 120px;" placeholder="Insert one or multiple songs separated by enter."/>' +
          '' +
            '<button id="my-btn" class="btn btn-primary" style="height: 100%; min-height: 120px; border: none; padding: 0 14px;">Auto Download</button>' +
          '' +
        '
'
    ;

    searchForm.append(customElements);
    $('#my-btn').click(findDownloadUrls);
}

function isResultsPage() {
    return isSubstringInURL('/mp3/');
}

function isDetailPage() {
    return isSubstringInURL('/download/');
}

function isSubstringInURL(substring) {
    return window.location.pathname.substring(0, substring.length) === substring;
}

function log() {
    if (localStorage.getItem(storageDebugName) === 'true') {
        console.log.apply(console, arguments);
    }
}

function setState(state) {
    localStorage.setItem(storageStateName, state);
}

function getState() {
    return localStorage.getItem(storageStateName);
}

function setNowFinding(songName) {
    localStorage.setItem(storageNowFinding, songName);
}

function getNowFinding() {
    return localStorage.getItem(storageNowFinding);
}

function setMp3List(mp3list) {
    localStorage.setItem(storageName, JSON.stringify(mp3list));
}

function getMp3List() {
    return JSON.parse(localStorage.getItem(storageName));
}
<commit_msg>fixed script not working after page changed it's domain + minor refactor<commit_after>// ==UserScript==
// @name         Auto mp3 downloader
// @description  Automatic download of multiple mp3s from http://www.emp3z.com. Just insert list of songs (each on new line) into textarea, push the button and wait till it finishes.
// @author       Bladito
// @version      0.3
// @match        http://www.emp3z.com/*
// @namespace    Bladito/auto-mp3-downloader
// @grant        none
// ==/UserScript==

(function($) {
    'use strict';

    var storageName = 'Bladito_autoDownloader_toDL';
    var storageStateName = 'Bladito_autoDownloader_state';
    var storageNowFinding = 'Bladito_autoDownloader_nowFinding';
    var storageDebugName = 'Bladito_autoDownloader_isDebug';

    var STATE = {
        IDLE:'IDLE',
        FINDING_URLS:'FINDING_URLS',
        DOWNLOADING:'DOWNLOADING'
    };

    init();

    log('state=',getState());

    if (getState() === STATE.IDLE || getState() === STATE.DOWNLOADING) {
        insertCustomHTMLElements();
    }

    if (getState() === STATE.FINDING_URLS) {
        if (isResultsPage()) {
            log('opening download page');
            window.location.href = $('a[href^="/download"')[0].href;
        } else if (isDetailPage()) {
            $('#dl1').click();
            rememberUrlForSong();
        }
    }

    if (getState() === STATE.DOWNLOADING) {
        downloadSongs();
    }

    //------------------------------------------------------------------------------------------------------------------

    function init() {
        if (!getState()) {
            setState(STATE.IDLE);
        }

        if (getState() === STATE.FINDING_URLS && !getNextSongToFind()) {
            setState(STATE.DOWNLOADING);
        }
    }

    function findDownloadUrls() {
        var mp3List, textAreaVal = $('#my-dl-list').val();

        if (textAreaVal && textAreaVal.length > 0) {
            mp3List = prepareMp3List(textAreaVal);

            setMp3List(mp3List);
            setState(STATE.FINDING_URLS);

            log('to dl ', JSON.stringify(mp3List));

            findUrlForNextSong();
        } else {
            alert('No songs inserted into textarea!');
        }
    }

    function findUrlForNextSong() {
        var nextSong = getNextSongToFind();

        if (nextSong) {
            log('gonna find ', nextSong.name);

            setNowFinding(nextSong.name);
            $('input[name="search"]').val(nextSong.name);
            $('input[name="search"]').parents('form').submit();
        } else {
            location.href = 'http://www.emp3z.ws/';
        }
    }

    function setFoundUrlForCurrentSong(songUrl) {
        var currentSong = getNowFinding(),
            list = getMp3List();

        for (var i=0; i<list.length; i+=1) {
            if (list[i].name === currentSong) {
                list[i].url = songUrl;
                log('SAVED URL ', songUrl, ' for song ', list[i].name);
                break;
            }
        }

        setMp3List(list);
    }

    function getNextSongToFind() {
        var list = getMp3List();

        for (var i=0; i<list.length; i+=1) {
            if (list[i].url === undefined) {
                return list[i];
            }
        }

        return undefined;
    }

    function prepareMp3List(mp3listString) {
        var result = [],
            list = mp3listString.split('\n');

        for (var i=0; i<list.length; i+=1) {
            result.push({
                name: list[i]
            });
        }

        return result;
    }

    function rememberUrlForSong() {
        var interval = setInterval(function() {
            log('finding link...');

            var link = $($('iframe')[0].contentWindow.document).find('#ytd')[0];
            if (link && link.href && link.href.length > 0) {
                window.clearInterval(interval);
                log('found link! ', link.href);
                setFoundUrlForCurrentSong(link.href);
                findUrlForNextSong();
            }
        }, 1000);
    }

    function downloadSongs() {
        var list = getMp3List(),
            output = '\n';

        for (var i=0; i<list.length; i+=1) {
            window.open(list[i].url, '_blank');
            list[i].downloaded = true;
            output += ' - ' + list[i].name + '\n';
        }

        setMp3List(list);
        setState(STATE.IDLE);
        setNowFinding(null);
        alert('Download initiated. ' + list.length + ' songs.' + output);
    }

    function insertCustomHTMLElements() {
        var customElements, searchForm = $('form[action^="/search"]');

        customElements =
            '
' +
            '<textarea id="my-dl-list" class="form-control" style="height: 120px;" placeholder="Insert one or multiple songs separated by enter."/>' +
            '' +
            '<button id="my-btn" class="btn btn-primary" style="height: 100%; min-height: 120px; border: none; padding: 0 14px;">Auto Download</button>' +
            '' +
            '
'
        ;

        searchForm.append(customElements);
        $('#my-btn').click(findDownloadUrls);
    }

    function isResultsPage() {
        return isSubstringInURL('/mp3/');
    }

    function isDetailPage() {
        return isSubstringInURL('/download/');
    }

    function isSubstringInURL(substring) {
        return window.location.pathname.substring(0, substring.length) === substring;
    }

    function log() {
        if (localStorage.getItem(storageDebugName) === 'true') {
            console.log.apply(console, arguments);
        }
    }

    function setState(state) {
        localStorage.setItem(storageStateName, state);
    }

    function getState() {
        return localStorage.getItem(storageStateName);
    }

    function setNowFinding(songName) {
        localStorage.setItem(storageNowFinding, songName);
    }

    function getNowFinding() {
        return localStorage.getItem(storageNowFinding);
    }

    function setMp3List(mp3list) {
        localStorage.setItem(storageName, JSON.stringify(mp3list));
    }

    function getMp3List() {
        return JSON.parse(localStorage.getItem(storageName));
    }

})(jQuery);
<|endoftext|>


<commit_before>/*global QUnit*/
(function() {
'use strict';

/*global splitSelectors*/
QUnit.test('splitSelectors', function(assert) {
	assert.deepEqual(splitSelectors('foo'), ['foo'], 'Simple selector doesn’t get split');
	assert.deepEqual(splitSelectors('foo,foo\t\n ,\t\n foo'), ['foo', 'foo', 'foo'], 'Simple selectors do get split');
});

/*global getComputedLength*/
QUnit.test('getComputedLength', function(assert) {

	var data = [
		['1px', 1],
		['10px', 10],
		['-0.123px', -0.123],
		['12pt', 16],
		['1pc', 16],
		['1in', 96],
		['2.54cm', 96],
		['25.4mm', 96],
		['1rem', 16],
		['1em', 10],
		['1ex', 5],
		['100vw', window.innerWidth],
		['100vh', window.innerHeight],
		['100vmin', Math.min(window.innerWidth, window.innerHeight)],
		['100vmax', Math.max(window.innerWidth, window.innerHeight)],
	];

	var dummy = document.createElement('div');
	dummy.style.fontSize = '10px';

	data.forEach(function(item) {
		assert.equal(getComputedLength(item[0], dummy), item[1], item[0] + ' == ' + item[1] + 'px');
	});

});

/*global getComputedStyle*/
QUnit.test('getComputedStyle', function(assert) {
	var element = document.createElement('div');
	element.style.width = '100px';
	element.style.height = '1in';
	element.style.cssFloat = 'left';
	document.body.appendChild(element);
	assert.equal(getComputedStyle(element).width, '100px', 'Normal style');
	assert.equal(getComputedStyle(element).height, '96px', 'Converted to pixel');
	assert.equal(getComputedStyle(element).cssFloat, 'left', 'Float left');
	assert.equal(getComputedStyle(element).display, 'block', 'Default style');
	document.body.removeChild(element);
});

/*global getOriginalStyle*/
QUnit.test('getOriginalStyle', function(assert) {

	var element = document.createElement('div');
	element.className = 'myel';

	var style = document.createElement('style');
	style.type = 'text/css';
	style.innerHTML = '.myel { width: 100%; height: auto !important }';

	document.head.appendChild(style);
	document.body.appendChild(element);

	assert.equal(getOriginalStyle(element, ['width']).width, '100%', 'Get width from <style>');
	assert.equal(getOriginalStyle(element, ['height']).height, 'auto', 'Get height from <style>');
	assert.equal(getOriginalStyle(element, ['color']).color, undefined, 'Get undefined property');
	element.style.width = '100px';
	assert.equal(getOriginalStyle(element, ['width']).width, '100px', 'Get width from style attribute');
	element.style.width = '';
	assert.equal(getOriginalStyle(element, ['width']).width, '100%', 'Get width from <style>');
	element.style.height = '100px';
	assert.equal(getOriginalStyle(element, ['height']).height, 'auto', 'Get height from <style> !important');
	element.style.setProperty('height', '100px', 'important');
	assert.equal(getOriginalStyle(element, ['height']).height, '100px', 'Get height from style attribute !important');

	document.head.removeChild(style);
	document.body.removeChild(element);

});

/*global filterRulesByElementAndProps*/
QUnit.test('filterRulesByElementAndProps', function(assert) {

	var element = document.createElement('div');
	element.className = 'myel';

	var style = document.createElement('style');
	style.type = 'text/css';
	style.innerHTML = '.myel, .notmyel { width: 1px }'
		+ '.notmyel { width: 2px }'
		+ '.myel { height: 3px }'
		+ '@media screen { div.myel { width: 4px } }';

	document.head.appendChild(style);
	document.body.appendChild(element);

	var rules = filterRulesByElementAndProps(style.sheet.cssRules, element, ['width']);
	assert.equal(rules.length, 2, 'Two rules');
	assert.equal(rules[0].selector, '.myel', 'First selector');
	assert.equal(rules[0].rule.style.width, '1px', 'Property');
	assert.equal(rules[1].selector, 'div.myel', 'Second selector');
	assert.equal(rules[1].rule.style.width, '4px', 'Property');

	document.head.removeChild(style);
	document.body.removeChild(element);

});

/*global styleHasProperty*/
QUnit.test('styleHasProperty', function(assert) {
	var style = document.createElement('div').style;
	style.width = '10px';
	assert.ok(styleHasProperty(style, ['width']), 'Single property');
	assert.ok(styleHasProperty(style, ['height', 'width']), 'One of two');
	assert.ok(!styleHasProperty(style, ['height']), 'None');
});

/*global sortRulesBySpecificity*/
QUnit.test('sortRulesBySpecificity', function(assert) {
	var unsorted = [
		{selector: 'tag'},
		{selector: '.class'},
		{selector: '#id'},
		{selector: 'tag tag'},
		{selector: '.class.class'},
		{selector: '#id#id'},
	];
	var sorted = [
		{selector: '#id#id'},
		{selector: '#id'},
		{selector: '.class.class'},
		{selector: '.class'},
		{selector: 'tag tag'},
		{selector: 'tag'},
	];
	assert.deepEqual(sortRulesBySpecificity(unsorted), sorted, 'Correct sort order');
});

/*global getSpecificity*/
QUnit.test('getSpecificity', function(assert) {

	var data = [
		['', 0, 'empty'],
		['div', 1, 'tag'],
		['.class', 256, 'class'],
		['#id', 256 * 256, 'ID'],
		['div.class#id', 256 * 256 + 256 + 1, 'tag, class and ID'],
		['::after', 1, 'pseudo element'],
		[':hover', 256, 'pseudo class'],
		['[foo="bar"]', 256, 'attribute'],
		['.\\:container\\(max-width\\:1px\\)', 256, 'escaped container query'],
	];

	data.forEach(function(item) {
		assert.equal(getSpecificity(item[0]), item[1], item[2][0].toUpperCase() + item[2].substr(1) + ' ("' + item[0] + '")' + ': ' + item[1]);
	});

	var allSelectors = data.reduce(function(all, item) { return all + ' ' + item[0]; }, '').trim();
	var allSpecifity = data.reduce(function(all, item) { return all + item[1]; }, 0);
	assert.equal(getSpecificity(allSelectors), allSpecifity, 'All combined ("' + allSelectors.trim() + '")' + ': ' + allSpecifity);

	data.reverse();
	allSelectors = data.reduce(function(all, item) { return all + ' ' + item[0]; }, '').trim();
	assert.equal(getSpecificity(allSelectors), allSpecifity, 'All combined reverse ("' + allSelectors.trim() + '")' + ': ' + allSpecifity);

});

})();
<commit_msg>Test getSize<commit_after>/*global QUnit*/
(function() {
'use strict';

/*global splitSelectors*/
QUnit.test('splitSelectors', function(assert) {
	assert.deepEqual(splitSelectors('foo'), ['foo'], 'Simple selector doesn’t get split');
	assert.deepEqual(splitSelectors('foo,foo\t\n ,\t\n foo'), ['foo', 'foo', 'foo'], 'Simple selectors do get split');
});

/*global getSize*/
QUnit.test('getSize', function(assert) {
	var element = document.createElement('div');
	element.style.width = '100px';
	element.style.height = '100px';
	element.style.boxSizing = 'border-box';
	element.style.padding = '1pc';
	element.style.border = '10px solid black';
	document.body.appendChild(element);
	assert.equal(getSize(element, 'width'), 48, 'Width');
	assert.equal(getSize(element, 'height'), 48, 'Height');
	document.body.removeChild(element);
});

/*global getComputedLength*/
QUnit.test('getComputedLength', function(assert) {

	var data = [
		['1px', 1],
		['10px', 10],
		['-0.123px', -0.123],
		['12pt', 16],
		['1pc', 16],
		['1in', 96],
		['2.54cm', 96],
		['25.4mm', 96],
		['1rem', 16],
		['1em', 10],
		['1ex', 5],
		['100vw', window.innerWidth],
		['100vh', window.innerHeight],
		['100vmin', Math.min(window.innerWidth, window.innerHeight)],
		['100vmax', Math.max(window.innerWidth, window.innerHeight)],
	];

	var dummy = document.createElement('div');
	dummy.style.fontSize = '10px';

	data.forEach(function(item) {
		assert.equal(getComputedLength(item[0], dummy), item[1], item[0] + ' == ' + item[1] + 'px');
	});

});

/*global getComputedStyle*/
QUnit.test('getComputedStyle', function(assert) {
	var element = document.createElement('div');
	element.style.width = '100px';
	element.style.height = '1in';
	element.style.cssFloat = 'left';
	document.body.appendChild(element);
	assert.equal(getComputedStyle(element).width, '100px', 'Normal style');
	assert.equal(getComputedStyle(element).height, '96px', 'Converted to pixel');
	assert.equal(getComputedStyle(element).cssFloat, 'left', 'Float left');
	assert.equal(getComputedStyle(element).display, 'block', 'Default style');
	document.body.removeChild(element);
});

/*global getOriginalStyle*/
QUnit.test('getOriginalStyle', function(assert) {

	var element = document.createElement('div');
	element.className = 'myel';

	var style = document.createElement('style');
	style.type = 'text/css';
	style.innerHTML = '.myel { width: 100%; height: auto !important }';

	document.head.appendChild(style);
	document.body.appendChild(element);

	assert.equal(getOriginalStyle(element, ['width']).width, '100%', 'Get width from <style>');
	assert.equal(getOriginalStyle(element, ['height']).height, 'auto', 'Get height from <style>');
	assert.equal(getOriginalStyle(element, ['color']).color, undefined, 'Get undefined property');
	element.style.width = '100px';
	assert.equal(getOriginalStyle(element, ['width']).width, '100px', 'Get width from style attribute');
	element.style.width = '';
	assert.equal(getOriginalStyle(element, ['width']).width, '100%', 'Get width from <style>');
	element.style.height = '100px';
	assert.equal(getOriginalStyle(element, ['height']).height, 'auto', 'Get height from <style> !important');
	element.style.setProperty('height', '100px', 'important');
	assert.equal(getOriginalStyle(element, ['height']).height, '100px', 'Get height from style attribute !important');

	document.head.removeChild(style);
	document.body.removeChild(element);

});

/*global filterRulesByElementAndProps*/
QUnit.test('filterRulesByElementAndProps', function(assert) {

	var element = document.createElement('div');
	element.className = 'myel';

	var style = document.createElement('style');
	style.type = 'text/css';
	style.innerHTML = '.myel, .notmyel { width: 1px }'
		+ '.notmyel { width: 2px }'
		+ '.myel { height: 3px }'
		+ '@media screen { div.myel { width: 4px } }';

	document.head.appendChild(style);
	document.body.appendChild(element);

	var rules = filterRulesByElementAndProps(style.sheet.cssRules, element, ['width']);
	assert.equal(rules.length, 2, 'Two rules');
	assert.equal(rules[0].selector, '.myel', 'First selector');
	assert.equal(rules[0].rule.style.width, '1px', 'Property');
	assert.equal(rules[1].selector, 'div.myel', 'Second selector');
	assert.equal(rules[1].rule.style.width, '4px', 'Property');

	document.head.removeChild(style);
	document.body.removeChild(element);

});

/*global styleHasProperty*/
QUnit.test('styleHasProperty', function(assert) {
	var style = document.createElement('div').style;
	style.width = '10px';
	assert.ok(styleHasProperty(style, ['width']), 'Single property');
	assert.ok(styleHasProperty(style, ['height', 'width']), 'One of two');
	assert.ok(!styleHasProperty(style, ['height']), 'None');
});

/*global sortRulesBySpecificity*/
QUnit.test('sortRulesBySpecificity', function(assert) {
	var unsorted = [
		{selector: 'tag'},
		{selector: '.class'},
		{selector: '#id'},
		{selector: 'tag tag'},
		{selector: '.class.class'},
		{selector: '#id#id'},
	];
	var sorted = [
		{selector: '#id#id'},
		{selector: '#id'},
		{selector: '.class.class'},
		{selector: '.class'},
		{selector: 'tag tag'},
		{selector: 'tag'},
	];
	assert.deepEqual(sortRulesBySpecificity(unsorted), sorted, 'Correct sort order');
});

/*global getSpecificity*/
QUnit.test('getSpecificity', function(assert) {

	var data = [
		['', 0, 'empty'],
		['div', 1, 'tag'],
		['.class', 256, 'class'],
		['#id', 256 * 256, 'ID'],
		['div.class#id', 256 * 256 + 256 + 1, 'tag, class and ID'],
		['::after', 1, 'pseudo element'],
		[':hover', 256, 'pseudo class'],
		['[foo="bar"]', 256, 'attribute'],
		['.\\:container\\(max-width\\:1px\\)', 256, 'escaped container query'],
	];

	data.forEach(function(item) {
		assert.equal(getSpecificity(item[0]), item[1], item[2][0].toUpperCase() + item[2].substr(1) + ' ("' + item[0] + '")' + ': ' + item[1]);
	});

	var allSelectors = data.reduce(function(all, item) { return all + ' ' + item[0]; }, '').trim();
	var allSpecifity = data.reduce(function(all, item) { return all + item[1]; }, 0);
	assert.equal(getSpecificity(allSelectors), allSpecifity, 'All combined ("' + allSelectors.trim() + '")' + ': ' + allSpecifity);

	data.reverse();
	allSelectors = data.reduce(function(all, item) { return all + ' ' + item[0]; }, '').trim();
	assert.equal(getSpecificity(allSelectors), allSpecifity, 'All combined reverse ("' + allSelectors.trim() + '")' + ': ' + allSpecifity);

});

})();
<|endoftext|>


<commit_before>/* eslint-disable no-unused-vars */
import {h} from 'hyperapp'

export const HeaderView = ({state, actions}) =>

Konvertera från SWEREF99 o RT90 till lat, lng

Välj projektion på ursprungsdatan o ladda upp ett .csv, eller klistra in en tabell nedan, med två kolumner där den första är X eller N o den andra är Y eller E. Du kan ladda ned ett exempel på hur ursprungsdatan bör se ut här.

          <input type='radio' onclick={actions.setSwerefSelected} name='radios' value='sweref' checked={state.swerefSelected} />
          SWEREF 99 TM etc

          <input type='radio' onclick={actions.setSwerefSelected} name='radios' value='rt90' />
           RT 90

         Välj SWEREF 99 projektion:
          <select value={state.selectedParam} onChange={actions.setSelectedParam} disabled={!state.swerefSelected}>
            {
              state.swerefs.map(ref =>
                <option value={ref.value}>{ref.text}</option>)
            }
          </select>

         Välj RT 90 projektion:
          <select value={state.selectedParam} onchange={actions.setSelectedParam} disabled={state.swerefSelected}>
            {
              state.rt90s.map(ref =>
                <option value={ref.value}>{ref.text}</option>)
            }
          </select>

<commit_msg>some space<commit_after>/* eslint-disable no-unused-vars */
import {h} from 'hyperapp'

export const HeaderView = ({state, actions}) =>

Konvertera från SWEREF99 o RT90 till lat, lng

Välj projektion på ursprungsdatan o ladda upp ett .csv, eller klistra in en tabell nedan, med två kolumner där den första är X eller N o den andra är Y eller E. Du kan ladda ned ett exempel på hur ursprungsdatan bör se ut här.

          <input type='radio' onclick={actions.setSwerefSelected} name='radios' value='sweref' checked={state.swerefSelected} />
          {' '}SWEREF 99 TM etc

          <input type='radio' onclick={actions.setSwerefSelected} name='radios' value='rt90' />
          {' '}RT 90

         Välj SWEREF 99 projektion:
          <select value={state.selectedParam} onChange={actions.setSelectedParam} disabled={!state.swerefSelected}>
            {
              state.swerefs.map(ref =>
                <option value={ref.value}>{ref.text}</option>)
            }
          </select>

         Välj RT 90 projektion:
          <select value={state.selectedParam} onchange={actions.setSelectedParam} disabled={state.swerefSelected}>
            {
              state.rt90s.map(ref =>
                <option value={ref.value}>{ref.text}</option>)
            }
          </select>

<|endoftext|>


<commit_before>const NavigationChapter = require('./navigation_chapter.js');

const LOGGER_TITLE = 'NAVIGATION';
const EDIT_DELAY_TIME_IN_MS = 1500;

function sendReactions(msg, reactions, logger) {
  let promise = Promise.resolve();
  for (let reaction of reactions) {
    promise = promise.then(() => {
      return msg.channel.addMessageReaction(msg.id, reaction);
    }).catch(err => {
      logger.logFailure(LOGGER_TITLE, 'Failed to add reaction button to navigation', err);
    });
  }
}

/**
 * Represents a collection of messages that can be navigated in two dimensions
 * via reaction buttons below the message. A navigation is a collection of {@link NavigationChapter}s.
 * Each navigation chapter controls a one-dimensional slice of navigable content.
 * The user can move forward and backward within a chapter by using the arrow reaction
 * buttons, and can switch chapters by using the other reaction buttons.

 * Once a Navigation is constructed, it can be shown to the user by using {@link NavigationManager#show}.
 * For an example of creating a navigation, see the [demo navigation command]{@link https://github.com/mistval/monochrome-demo/blob/master/commands/navigation.js}.
 */
class Navigation {
  /**
  * Construct a Navigation. If this Navigation only has one {@link NavigationChapter}, use the
  * {@link Navigation.fromOneNavigationChapter} factory method instead. If you only
  * need left and right pagination, and you already have all the content you want to display
  * to the user, use the {@link Navigation.fromOneDimensionalContents} factory method instead.
  * @param {string} ownerId - The user ID of the user who is allowed to use the reaction buttons to navigate.
  * @param {boolean} showPageArrows - Whether to show the arrow reactions or not. If you know in advance
  *   that none of your {@link NavigationChapter}s have more than one page, you can set this to false.
  * @param {string} initialEmojiName - The name of the emoji for the chapter to show initially.
  * @param {Object.<string, NavigationChapter>} chapterForEmojiName - An object where the keys are emoji names and the values are {@link NavigationChapter}s.
  *   When the emoji is clicked, control will switch to the specified chapter. To find the name of an emoji, enter the emoji in
  *   Discord's chat box, put a \ in front of it, and hit enter.
  */
  constructor(ownerId, showPageArrows, initialEmojiName, chapterForEmojiName) {
    let keys = Object.keys(chapterForEmojiName);
    if (keys.indexOf(initialEmojiName) === -1) {
      throw new Error('Value of initialEmojiName not found in chapterForEmojiName');
    }
    this.showPageArrows_ = showPageArrows;
    this.chapterForEmojiName_ = chapterForEmojiName;
    this.currentEmojiName_ = initialEmojiName;
    this.ownerId_ = ownerId;
    this.actionAccumulator_ = new ActionAccumulator(EDIT_DELAY_TIME_IN_MS);
  }

  /**
   * Construct a Navigation with just one chapter. Only the arrow reactions will
   * be shown and navigation will only be possible in one dimension.
   * If you already have all the content you want to display to the user,
   * you can use the {@link Navigation.fromOneDimensionalContents} factory method instead
   * and avoid having to construct a {@link NavigationChapter} yourself.
   * @param {string} ownerId - The user ID of the user who is allowed to use the reaction buttons to navigate.
   * @param {NavigationChapter} navigationChapter - The {@link NavigationChapter} to show.
   * @param {boolean} showPageArrows - Whether to show the page arrows or not.
   */
  static fromOneNavigationChapter(ownerId, navigationChapter, showPageArrows=true) {
    const chapterForEmojiName = { a: navigationChapter };
    return new Navigation(ownerId, showPageArrows, 'a', chapterForEmojiName);
  }

  /**
   * Construct a Navigation from an array of [Discord embed structures]{@link https://discordapp.com/developers/docs/resources/channel#embed-object} (or strings).
   * Only the arrow reactions will be shown and navigation will only be possible in one dimension.
   * @param {string} ownerId - The user ID of the user who is allowed to use the reaction buttons to navigate.
   * @param {string[]|Object[]} contents - The strings or [Discord embed structures]{@link https://discordapp.com/developers/docs/resources/channel#embed-object}
   *   to show in the navigation.
   */
  static fromOneDimensionalContents(ownerId, contents) {
    const chapter = NavigationChapter.fromContent(contents);
    return Navigation.fromOneNavigationChapter(ownerId, chapter, contents.length > 1);
  }

  createMessage(channel, parentMsg, logger) {
    let chapter = this.getChapterForEmojiName_(this.currentEmojiName_);
    return chapter.getCurrentPage(logger).then(navigationPage => {
      if (!navigationPage) {
        throw new Error('Navigation failed to create initial page.');
      }
      if (navigationPage.showPageArrows !== undefined) {
        this.showPageArrows_ = navigationPage.showPageArrows;
      }
      return channel.createMessage(navigationPage, undefined, parentMsg);
    }).then(sentMessage => {
      let emojis = Object.keys(this.chapterForEmojiName_);
      let reactionsToSend = [];
      if (emojis.length > 1) {
        reactionsToSend = reactionsToSend.concat(emojis);
      }

      if (this.showPageArrows_) {
        reactionsToSend.push('⬅');
        reactionsToSend.push('➡');
      }

      sendReactions(sentMessage, reactionsToSend, logger);
      this.message_ = sentMessage;
      return sentMessage.id;
    }).catch(err => {
      logger.logFailure(LOGGER_TITLE, 'Failed to create navigation.', err);
      throw err;
    });
  }

  handleEmojiToggled(bot, emoji, userId, logger) {
    if (bot.user.id === userId) {
      return;
    } else if (emoji.name === this.currentEmojiName_) {
      return;
    } else if (userId !== this.ownerId_) {
      return;
    }

    this.actionAccumulator_.enqueue(() => {
      let pagePromise = undefined;
      let desiredEmojiName = emoji.name;

      if (this.showPageArrows_ && emoji.name === '⬅') {
        pagePromise = this.getChapterForEmojiName_(this.currentEmojiName_).flipToPreviousPage(logger);
        desiredEmojiName = this.currentEmojiName_;
      } else if (this.showPageArrows_ && emoji.name === '➡') {
        pagePromise = this.getChapterForEmojiName_(this.currentEmojiName_).flipToNextPage(logger);
        desiredEmojiName = this.currentEmojiName_;
      } else {
        let chapter = this.getChapterForEmojiName_(emoji.name);
        if (!chapter) {
          return;
        }
        this.currentEmojiName_ = emoji.name;
        pagePromise = chapter.getCurrentPage(logger);
      }

      pagePromise.then(navigationPage => {
        if (navigationPage && desiredEmojiName === this.currentEmojiName_) {
          return this.message_.edit(navigationPage);
        }
      }).catch(err => {
        logger.logFailure(LOGGER_TITLE, 'Error navigating.', err);
      });
    });
  }

  getChapterForEmojiName_(emojiName) {
    let keys = Object.keys(this.chapterForEmojiName_);
    for (let key of keys) {
      if (key === emojiName) {
        return this.chapterForEmojiName_[key];
      }
    }

    return;
  }
}

class ActionAccumulator {
  constructor(delayInMs) {
    this.delayInMs_ = delayInMs;
    this.timerInFlight_ = false;
    this.callback_ = undefined;
  }

  enqueue(callback) {
    if (this.timerInFlight_) {
      this.callback_ = callback;
    } else {
      this.timerInFlight_ = true;
      callback();
      setTimeout(() => {
        this.timerInFlight_ = false;
        if (this.callback) {
          this.callback_();
          this.callback_ = undefined;
        }
      },
      this.delayInMs);
    }
  }
}

module.exports = Navigation;
<commit_msg>abort sending reaction buttons if no permissions<commit_after>const NavigationChapter = require('./navigation_chapter.js');

const LOGGER_TITLE = 'NAVIGATION';
const EDIT_DELAY_TIME_IN_MS = 1500;

async function sendReactions(msg, reactions, logger) {
  for (let i = 0; i < reactions.length; i += 1) {
    const reaction = reactions[i];
    try {
      await msg.channel.addMessageReaction(msg.id, reaction);
    } catch (err) {
      logger.logFailure(LOGGER_TITLE, 'Failed to add reaction button to navigation', err);
      if (err.code === 50013) {
        return; // Missing permissions error. Don't bother trying to send the other reactions.
      }
    }
  }
}

/**
 * Represents a collection of messages that can be navigated in two dimensions
 * via reaction buttons below the message. A navigation is a collection of {@link NavigationChapter}s.
 * Each navigation chapter controls a one-dimensional slice of navigable content.
 * The user can move forward and backward within a chapter by using the arrow reaction
 * buttons, and can switch chapters by using the other reaction buttons.

 * Once a Navigation is constructed, it can be shown to the user by using {@link NavigationManager#show}.
 * For an example of creating a navigation, see the [demo navigation command]{@link https://github.com/mistval/monochrome-demo/blob/master/commands/navigation.js}.
 */
class Navigation {
  /**
  * Construct a Navigation. If this Navigation only has one {@link NavigationChapter}, use the
  * {@link Navigation.fromOneNavigationChapter} factory method instead. If you only
  * need left and right pagination, and you already have all the content you want to display
  * to the user, use the {@link Navigation.fromOneDimensionalContents} factory method instead.
  * @param {string} ownerId - The user ID of the user who is allowed to use the reaction buttons to navigate.
  * @param {boolean} showPageArrows - Whether to show the arrow reactions or not. If you know in advance
  *   that none of your {@link NavigationChapter}s have more than one page, you can set this to false.
  * @param {string} initialEmojiName - The name of the emoji for the chapter to show initially.
  * @param {Object.<string, NavigationChapter>} chapterForEmojiName - An object where the keys are emoji names and the values are {@link NavigationChapter}s.
  *   When the emoji is clicked, control will switch to the specified chapter. To find the name of an emoji, enter the emoji in
  *   Discord's chat box, put a \ in front of it, and hit enter.
  */
  constructor(ownerId, showPageArrows, initialEmojiName, chapterForEmojiName) {
    let keys = Object.keys(chapterForEmojiName);
    if (keys.indexOf(initialEmojiName) === -1) {
      throw new Error('Value of initialEmojiName not found in chapterForEmojiName');
    }
    this.showPageArrows_ = showPageArrows;
    this.chapterForEmojiName_ = chapterForEmojiName;
    this.currentEmojiName_ = initialEmojiName;
    this.ownerId_ = ownerId;
    this.actionAccumulator_ = new ActionAccumulator(EDIT_DELAY_TIME_IN_MS);
  }

  /**
   * Construct a Navigation with just one chapter. Only the arrow reactions will
   * be shown and navigation will only be possible in one dimension.
   * If you already have all the content you want to display to the user,
   * you can use the {@link Navigation.fromOneDimensionalContents} factory method instead
   * and avoid having to construct a {@link NavigationChapter} yourself.
   * @param {string} ownerId - The user ID of the user who is allowed to use the reaction buttons to navigate.
   * @param {NavigationChapter} navigationChapter - The {@link NavigationChapter} to show.
   * @param {boolean} showPageArrows - Whether to show the page arrows or not.
   */
  static fromOneNavigationChapter(ownerId, navigationChapter, showPageArrows=true) {
    const chapterForEmojiName = { a: navigationChapter };
    return new Navigation(ownerId, showPageArrows, 'a', chapterForEmojiName);
  }

  /**
   * Construct a Navigation from an array of [Discord embed structures]{@link https://discordapp.com/developers/docs/resources/channel#embed-object} (or strings).
   * Only the arrow reactions will be shown and navigation will only be possible in one dimension.
   * @param {string} ownerId - The user ID of the user who is allowed to use the reaction buttons to navigate.
   * @param {string[]|Object[]} contents - The strings or [Discord embed structures]{@link https://discordapp.com/developers/docs/resources/channel#embed-object}
   *   to show in the navigation.
   */
  static fromOneDimensionalContents(ownerId, contents) {
    const chapter = NavigationChapter.fromContent(contents);
    return Navigation.fromOneNavigationChapter(ownerId, chapter, contents.length > 1);
  }

  createMessage(channel, parentMsg, logger) {
    let chapter = this.getChapterForEmojiName_(this.currentEmojiName_);
    return chapter.getCurrentPage(logger).then(navigationPage => {
      if (!navigationPage) {
        throw new Error('Navigation failed to create initial page.');
      }
      if (navigationPage.showPageArrows !== undefined) {
        this.showPageArrows_ = navigationPage.showPageArrows;
      }
      return channel.createMessage(navigationPage, undefined, parentMsg);
    }).then(sentMessage => {
      let emojis = Object.keys(this.chapterForEmojiName_);
      let reactionsToSend = [];
      if (emojis.length > 1) {
        reactionsToSend = reactionsToSend.concat(emojis);
      }

      if (this.showPageArrows_) {
        reactionsToSend.push('⬅');
        reactionsToSend.push('➡');
      }

      sendReactions(sentMessage, reactionsToSend, logger);
      this.message_ = sentMessage;
      return sentMessage.id;
    }).catch(err => {
      logger.logFailure(LOGGER_TITLE, 'Failed to create navigation.', err);
      throw err;
    });
  }

  handleEmojiToggled(bot, emoji, userId, logger) {
    if (bot.user.id === userId) {
      return;
    } else if (emoji.name === this.currentEmojiName_) {
      return;
    } else if (userId !== this.ownerId_) {
      return;
    }

    this.actionAccumulator_.enqueue(() => {
      let pagePromise = undefined;
      let desiredEmojiName = emoji.name;

      if (this.showPageArrows_ && emoji.name === '⬅') {
        pagePromise = this.getChapterForEmojiName_(this.currentEmojiName_).flipToPreviousPage(logger);
        desiredEmojiName = this.currentEmojiName_;
      } else if (this.showPageArrows_ && emoji.name === '➡') {
        pagePromise = this.getChapterForEmojiName_(this.currentEmojiName_).flipToNextPage(logger);
        desiredEmojiName = this.currentEmojiName_;
      } else {
        let chapter = this.getChapterForEmojiName_(emoji.name);
        if (!chapter) {
          return;
        }
        this.currentEmojiName_ = emoji.name;
        pagePromise = chapter.getCurrentPage(logger);
      }

      pagePromise.then(navigationPage => {
        if (navigationPage && desiredEmojiName === this.currentEmojiName_) {
          return this.message_.edit(navigationPage);
        }
      }).catch(err => {
        logger.logFailure(LOGGER_TITLE, 'Error navigating.', err);
      });
    });
  }

  getChapterForEmojiName_(emojiName) {
    let keys = Object.keys(this.chapterForEmojiName_);
    for (let key of keys) {
      if (key === emojiName) {
        return this.chapterForEmojiName_[key];
      }
    }

    return;
  }
}

class ActionAccumulator {
  constructor(delayInMs) {
    this.delayInMs_ = delayInMs;
    this.timerInFlight_ = false;
    this.callback_ = undefined;
  }

  enqueue(callback) {
    if (this.timerInFlight_) {
      this.callback_ = callback;
    } else {
      this.timerInFlight_ = true;
      callback();
      setTimeout(() => {
        this.timerInFlight_ = false;
        if (this.callback) {
          this.callback_();
          this.callback_ = undefined;
        }
      },
      this.delayInMs);
    }
  }
}

module.exports = Navigation;
<|endoftext|>


<commit_before>/*
 * biojs-vis-pinpad
 * https://github.com/ljgarcia/biojs-vis-pinpad
 *
 * Copyright (c) 2014 ljgarcia
 * Licensed under the Apache 2 license.
 */

// chai is an assertion library
var chai = require('chai');

// @see http://chaijs.com/api/assert/
var assert = chai.assert;

// register alternative styles
// @see http://chaijs.com/api/bdd/
chai.expect();
chai.should();

// requires your main app (specified in index.js)
var AnnotationViewer = require('../..');

describe('biotea-vis-annotation module', function(){
    it('should return a hello', function(){
        var instance = new AnnotationViewer({
            db: 'pmc',
            path: '../../data/',
            id: 13914
        });
    });
});
<commit_msg>Update test_bioteaVisTopicDistribution.js<commit_after>/*
 * biojs-vis-pinpad
 * https://github.com/ljgarcia/biojs-vis-pinpad
 *
 * Copyright (c) 2014 ljgarcia
 * Licensed under the Apache 2 license.
 */

// chai is an assertion library
var chai = require('chai');

// @see http://chaijs.com/api/assert/
var assert = chai.assert;

// register alternative styles
// @see http://chaijs.com/api/bdd/
chai.expect();
chai.should();

// requires your main app (specified in index.js)
var AnnotationViewer = require('../..');

describe('biotea-vis-tooltip module', function(){
    it('should return a hello', function(){
        var instance = new AnnotationViewer({
            db: 'pmc',
            path: '../../data/',
            id: 13914
        });
    });
});
<|endoftext|>


<commit_before>var TESTRUNNER = {};

TESTRUNNER.data = {
   baseline: ["hello_world"],
   embedded_images: ["many_files", "single_base64"],
   resources: ["few", "many"],
   fonts: ["local-base64"],
  custom_font: ["full", "subset"],
  images: ["avatars", "1136-pixel-image","2880-pixel-image"],
  libraries: [, "jquery-1-UI", "jquery-1", "jquery-2"]
}
<commit_msg>Altered tests so that percieved-to-be best practice is presented first in table<commit_after>var TESTRUNNER = {};

TESTRUNNER.data = {
   baseline: ["hello_world"],
   embedded_images: ["single_base64", "many_files"],
   resources: ["few", "many"],
   fonts: ["local-base64"],
  custom_font: ["subset", "full"],
  images: ["avatars", "1136-pixel-image","2880-pixel-image"],
  libraries: ["jquery-2", "jquery-1", "jquery-1-UI"]
}
<|endoftext|>