<commit_before>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Generate a rasterized screenshot for a simulation using scenery's built-in machinery.
 * Used in phet-io as well as PhetMenu (optionally)
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import CanvasContextWrapper from '../../scenery/js/util/CanvasContextWrapper.js';
import joist from './joist.js';

class ScreenshotGenerator {

  /**
   * Given a sim, generate a screenshot as a data url
   * @param {Sim} sim
   * @param {string} [mimeType] - String for the image mimeType, defaults to 'image/png'
   * @returns {string} dataURL
   * @public
   */
  static generateScreenshot( sim, mimeType ) {

    // Default to PNG
    mimeType = mimeType || 'image/png';

    // set up our Canvas with the correct background color
    const canvas = document.createElement( 'canvas' );
    canvas.width = sim.display.width;
    canvas.height = sim.display.height;
    const context = canvas.getContext( '2d' );
    context.fillStyle = sim.display.domElement.style.backgroundColor;
    context.fillRect( 0, 0, canvas.width, canvas.height );
    const wrapper = new CanvasContextWrapper( canvas, context );

    sim.rootNode.renderToCanvasSubtree( wrapper );

    // get the data URL in PNG format
    return canvas.toDataURL( mimeType );
  }
}

joist.register( 'ScreenshotGenerator', ScreenshotGenerator );
export default ScreenshotGenerator;<commit_msg>Apply backing scales to screenshots, so we get the same resolution as viewed on-screen, see https://github.com/phetsims/joist/issues/664<commit_after>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Generate a rasterized screenshot for a simulation using scenery's built-in machinery.
 * Used in phet-io as well as PhetMenu (optionally)
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import Matrix3 from '../../dot/js/Matrix3.js';
import CanvasContextWrapper from '../../scenery/js/util/CanvasContextWrapper.js';
import Utils from '../../scenery/js/util/Utils.js';
import joist from './joist.js';

class ScreenshotGenerator {

  /**
   * Given a sim, generate a screenshot as a data url
   * @param {Sim} sim
   * @param {string} [mimeType] - String for the image mimeType, defaults to 'image/png'
   * @returns {string} dataURL
   * @public
   */
  static generateScreenshot( sim, mimeType ) {

    // Default to PNG
    mimeType = mimeType || 'image/png';

    // set up our Canvas with the correct background color
    const canvas = document.createElement( 'canvas' );
    const context = canvas.getContext( '2d' );

    const backingScale = Utils.backingScale( context );
    canvas.width = sim.display.width * backingScale;
    canvas.height = sim.display.height * backingScale;

    context.scale( backingScale, backingScale );
    context.fillStyle = sim.display.domElement.style.backgroundColor;
    context.fillRect( 0, 0, canvas.width, canvas.height );
    const wrapper = new CanvasContextWrapper( canvas, context );

    sim.rootNode.renderToCanvasSubtree( wrapper, Matrix3.scaling( backingScale ) );

    // get the data URL in PNG format
    return canvas.toDataURL( mimeType );
  }
}

joist.register( 'ScreenshotGenerator', ScreenshotGenerator );
export default ScreenshotGenerator;<|endoftext|>


<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "format", "settings", "preferences", "util", "save"
    ];
    main.provides = ["format.jsbeautify"];
    return main;

    function main(options, imports, register) {
        var util = imports.util;
        var prefs = imports.preferences;
        var Plugin = imports.Plugin;
        var format = imports.format;
        var settings = imports.settings;
        var save = imports.save;
        var Range = require("ace/range").Range;
        var jsbeautify = require("./lib_jsbeautify");
        var plugin = new Plugin("Ajax.org", main.consumes);

        var MODES = {
            "javascript": true,
            "html": true,
            "xhtml": true,
            "plugins/salesforce.language/modes/visualforce": true,
            "plugins/salesforce.language/modes/lightning": true,
            "css": true,
            "less": true,
            "scss": true,
            "xml": true,
            "json": true,
            "handlebars": true,
        };

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            settings.on("read", function() {
                settings.setDefaults("project/javascript", [
                    ["use_jsbeautify", "true"],
                ]);
                settings.setDefaults("project/format/jsbeautify", [
                    ["preserveempty", "true"],
                    ["keeparrayindentation", "false"],
                    ["jslinthappy", "false"],
                    ["braces", "end-expand"],
                    ["space_before_conditional", "true"],
                    ["unescape_strings", "true"],
                    ["indent_inner_html", false],
                    ["advanced", {}]
                ]);
            });

            format.on("format", function(e) {
                if (MODES[e.mode])
                    return formatEditor(e.editor, e.mode, e.all);
            });

            prefs.add({
                "Project": {
                    "Code Formatters": {
                        position: 900,
                        "JSBeautify": {
                            type: "label",
                            caption: "JSBeautify settings:",
                        },
                        "Format Code on Save": {
                            position: 320,
                            type: "checkbox",
                            path: "project/format/jsbeautify/@formatOnSave",
                            onchange: function(e) {
                                if (e.value && settings.get("project/javascript/@formatter", ""))
                                    settings.set("project/javascript/@use_jsbeautify", true);
                            }
                        },
                        "Use JSBeautify for JavaScript": {
                            position: 340,
                            type: "checkbox",
                            path: "project/javascript/@use_jsbeautify",
                            onchange: function(e) {
                                if (e.value)
                                    settings.set("project/javascript/@formatter", "");
                            }
                        },
                        "Preserve Empty Lines": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@preserveempty",
                            position: 350,
                        },
                        "Keep Array Indentation": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@keeparrayindentation",
                            position: 351,
                        },
                        "JSLint Strict Whitespace": {
                            type: "checkbox",

                            path: "project/format/jsbeautify/@jslinthappy",
                            position: 352,
                        },
                        "Braces": {
                            type: "dropdown",
                            path: "project/format/jsbeautify/@braces",
                            width: "185",
                            position: 353,
                            items: [
                                { value: "collapse", caption: "Braces with control statement" },
                                { value: "expand", caption: "Braces on own line" },
                                { value: "end-expand", caption: "End braces on own line" }
                            ]
                        },
                        "Space Before Conditionals": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@space_before_conditional",
                            position: 354,
                        },
                        "Unescape Strings": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@unescape_strings",
                            position: 355,
                        },
                        "Indent Inner Html": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@indent_inner_html",
                            position: 356,
                        }
                    },
                    "JavaScript Support": {
                        position: 1100,
                        "Use Built-in JSBeautify as Code Formatter": {
                            position: 320,
                            type: "checkbox",
                            path: "project/javascript/@use_jsbeautify",
                            onchange: function(e) {
                                if (e.value)
                                    settings.set("project/javascript/@formatter", "");
                            }
                        },
                    }
                }
            }, plugin);

            save.on("beforeSave", function(e) {
                if (!e.tab.editor || !e.tab.editor.ace || !e.tab.path)
                    return;
                var mode = e.tab.editor.ace.session.syntax;
                if (!e.tab.editor && !e.tab.editor.ace || !MODES[mode])
                    return;
                if (mode === "javascript" && !settings.getBool("project/javascript/@formatOnSave") && !settings.getBool("project/format/jsbeautify/@formatOnSave"))
                    return;
                if (mode === "javascript" && !settings.getBool("project/javascript/@use_jsbeautify"))
                    return;
                if (mode !== "javascript" && !settings.getBool("project/format/jsbeautify/@formatOnSave"))
                    return;
                format.formatCode(null, e.tab.editor);
            }, plugin);
        }

        /***** Methods *****/

        function formatEditor(editor, mode, all) {
            if (this.disabled === true)
                return;
            if (mode === "javascript" && !settings.getBool("project/javascript/@use_jsbeautify"))
                return;

            var ace = editor.ace;
            var sel = ace.selection;
            var session = ace.session;
            var range = sel.getRange();
            var keepSelection = false;

            if (range.isEmpty() || all) {
                range = new Range(0, 0, session.getLength(), 0);
                keepSelection = true;
            }

            // Load up current settings data
            var options = getOptions(null, true);

            if (session.getUseSoftTabs()) {
                options.indent_char = " ";
                options.indent_size = session.getTabSize();
            } else {
                options.indent_char = "\t";
                options.indent_size = 1;
            }

            var line = session.getLine(range.start.row);
            var indent = line.match(/^\s*/)[0];
            var trim = false;

            if (range.start.column < indent.length)
                range.start.column = 0;
            else
                trim = true;

            var value = session.getTextRange(range);
            var type = getType(mode, value, options);

            try {
                value = jsbeautify[type + "_beautify"](value, options);
                if (trim)
                    value = value.replace(/^/gm, indent).trim();
                if (range.end.column === 0)
                    value += "\n" + indent;
            }
            catch (e) {
                return false;
            }

            var end = session.diffAndReplace(range, value);
            if (!keepSelection)
                sel.setSelectionRange(Range.fromPoints(range.start, end));

            return true;
        }

        function formatString(mode, value, options) {
            options = getOptions(options);
            var type = getType(mode, value, options);
            return jsbeautify[type + "_beautify"](value, options);
        }

        function getOptions(options, allowAdvanced) {
            if (!options) options = {};

            if (!options.hasOwnProperty("space_before_conditional"))
                options.space_before_conditional =
                    settings.getBool("project/format/jsbeautify/@space_before_conditional");
            if (!options.hasOwnProperty("keep_array_indentation"))
                options.keep_array_indentation =
                    settings.getBool("project/format/jsbeautify/@keeparrayindentation");
            if (!options.hasOwnProperty("preserve_newlines"))
                options.preserve_newlines =
                    settings.getBool("project/format/jsbeautify/@preserveempty");
            if (!options.hasOwnProperty("unescape_strings"))
                options.unescape_strings =
                    settings.getBool("project/format/jsbeautify/@unescape_strings");
            if (!options.hasOwnProperty("jslint_happy"))
                options.jslint_happy =
                    settings.getBool("project/format/jsbeautify/@jslinthappy");
            if (!options.hasOwnProperty("brace_style"))
                options.brace_style =
                    settings.get("project/format/jsbeautify/@braces");
            if (!options.hasOwnProperty("indent_inner_html"))
                options.indent_inner_html =
                    settings.get("project/format/jsbeautify/@indent_inner_html");
            if (!options.hasOwnProperty("e4x"))
                options.e4x = true;

            if (allowAdvanced) {
                var json = settings.get("project/format/jsbeautify/@advanced");
                if (json && typeof json == "object")
                    util.extend(options, json);
            }

            if (!options.indent_char)
                options.indent_char = " ";
            if (!options.indent_size)
                options.indent_size = 4;

            return options;
        }

        function getType(mode, value, options) {
            var type = null;

            if (mode == "javascript" || mode == "json" || mode == "jsx") {
                type = "js";
            } else if (mode == "css" || mode == "less" || mode == "scss"){
                type = "css";
            } else if (/^\s*<!?\w/.test(value)) {
                type = "html";
            } else if (mode == "xml") {
                type = "html";
            } else if (mode == "html" || mode == "xhtml"
                       || mode === "plugins/salesforce.language/modes/visualforce"
                       || mode === "plugins/salesforce.language/modes/lightning") {
                if (/[^<]+?{[\s\-\w]+:[^}]+;/.test(value))
                    type = "css";
                else if (/<\w+[ \/>]/.test(value))
                    type = "html";
                else
                    type = "js";
            } else if (mode == "handlebars") {
                if (options) options.indent_handlebars = true;
                type = "html";
            }

            return type;
        }

        /***** Lifecycle *****/

        plugin.on("load", function() {
            load();
        });
        plugin.on("enable", function() {

        });
        plugin.on("disable", function() {

        });
        plugin.on("unload", function() {
            loaded = false;
        });

        /***** Register and define API *****/

        /**
         * Beautify extension for the Cloud9 client
         *
         * Reformats the selected code in the current document
         * Processing/formatting code from https://github.com/einars/js-beautify
         */
        plugin.freezePublicAPI({
            /**
             *
             */
            formatEditor: formatEditor,

            /**
             *
             */
            formatString: formatString,
        });

        register(null, {
            "format.jsbeautify": plugin
        });
    }
});
<commit_msg>Apply .eslintrc rules<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "format", "settings", "preferences", "util", "save"
    ];
    main.provides = ["format.jsbeautify"];
    return main;

    function main(options, imports, register) {
        var util = imports.util;
        var prefs = imports.preferences;
        var Plugin = imports.Plugin;
        var format = imports.format;
        var settings = imports.settings;
        var save = imports.save;
        var Range = require("ace/range").Range;
        var jsbeautify = require("./lib_jsbeautify");
        var plugin = new Plugin("Ajax.org", main.consumes);

        var MODES = {
            "javascript": true,
            "html": true,
            "xhtml": true,
            "plugins/salesforce.language/modes/visualforce": true,
            "plugins/salesforce.language/modes/lightning": true,
            "css": true,
            "less": true,
            "scss": true,
            "xml": true,
            "json": true,
            "handlebars": true,
        };

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            settings.on("read", function() {
                settings.setDefaults("project/javascript", [
                    ["use_jsbeautify", "true"],
                ]);
                settings.setDefaults("project/format/jsbeautify", [
                    ["preserveempty", "true"],
                    ["keeparrayindentation", "false"],
                    ["jslinthappy", "false"],
                    ["braces", "end-expand"],
                    ["space_before_conditional", "true"],
                    ["unescape_strings", "true"],
                    ["indent_inner_html", false],
                    ["advanced", {}]
                ]);
            });

            format.on("format", function(e) {
                if (MODES[e.mode])
                    return formatEditor(e.editor, e.mode, e.all);
            });

            prefs.add({
                "Project": {
                    "Code Formatters": {
                        position: 900,
                        "JSBeautify": {
                            type: "label",
                            caption: "JSBeautify settings:",
                        },
                        "Format Code on Save": {
                            position: 320,
                            type: "checkbox",
                            path: "project/format/jsbeautify/@formatOnSave",
                            onchange: function(e) {
                                if (e.value && settings.get("project/javascript/@formatter", ""))
                                    settings.set("project/javascript/@use_jsbeautify", true);
                            }
                        },
                        "Use JSBeautify for JavaScript": {
                            position: 340,
                            type: "checkbox",
                            path: "project/javascript/@use_jsbeautify",
                            onchange: function(e) {
                                if (e.value)
                                    settings.set("project/javascript/@formatter", "");
                            }
                        },
                        "Preserve Empty Lines": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@preserveempty",
                            position: 350,
                        },
                        "Keep Array Indentation": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@keeparrayindentation",
                            position: 351,
                        },
                        "JSLint Strict Whitespace": {
                            type: "checkbox",

                            path: "project/format/jsbeautify/@jslinthappy",
                            position: 352,
                        },
                        "Braces": {
                            type: "dropdown",
                            path: "project/format/jsbeautify/@braces",
                            width: "185",
                            position: 353,
                            items: [
                                { value: "collapse", caption: "Braces with control statement" },
                                { value: "expand", caption: "Braces on own line" },
                                { value: "end-expand", caption: "End braces on own line" }
                            ]
                        },
                        "Space Before Conditionals": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@space_before_conditional",
                            position: 354,
                        },
                        "Unescape Strings": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@unescape_strings",
                            position: 355,
                        },
                        "Indent Inner Html": {
                            type: "checkbox",
                            path: "project/format/jsbeautify/@indent_inner_html",
                            position: 356,
                        }
                    },
                    "JavaScript Support": {
                        position: 1100,
                        "Use Built-in JSBeautify as Code Formatter": {
                            position: 320,
                            type: "checkbox",
                            path: "project/javascript/@use_jsbeautify",
                            onchange: function(e) {
                                if (e.value)
                                    settings.set("project/javascript/@formatter", "");
                            }
                        },
                    }
                }
            }, plugin);

            save.on("beforeSave", function(e) {
                if (!e.tab.editor || !e.tab.editor.ace || !e.tab.path)
                    return;
                var mode = e.tab.editor.ace.session.syntax;
                if (!e.tab.editor && !e.tab.editor.ace || !MODES[mode])
                    return;
                if (mode === "javascript" && !settings.getBool("project/javascript/@formatOnSave") && !settings.getBool("project/format/jsbeautify/@formatOnSave"))
                    return;
                if (mode === "javascript" && !settings.getBool("project/javascript/@use_jsbeautify"))
                    return;
                if (mode !== "javascript" && !settings.getBool("project/format/jsbeautify/@formatOnSave"))
                    return;
                format.formatCode(null, e.tab.editor);
            }, plugin);
        }

        /***** Methods *****/

        function formatEditor(editor, mode, all) {
            if (this.disabled === true)
                return;
            if (mode === "javascript" && !settings.getBool("project/javascript/@use_jsbeautify"))
                return;

            var ace = editor.ace;
            var sel = ace.selection;
            var session = ace.session;
            var range = sel.getRange();
            var keepSelection = false;

            if (range.isEmpty() || all) {
                range = new Range(0, 0, session.getLength(), 0);
                keepSelection = true;
            }

            // Load up current settings data
            var options = getOptions(null, true);

            if (session.getUseSoftTabs()) {
                options.indent_char = " ";
                options.indent_size = session.getTabSize();
            } else {
                options.indent_char = "\t";
                options.indent_size = 1;
            }

            var line = session.getLine(range.start.row);
            var indent = line.match(/^\s*/)[0];
            var trim = false;

            if (range.start.column < indent.length)
                range.start.column = 0;
            else
                trim = true;

            var value = session.getTextRange(range);
            var type = getType(mode, value, options);

            try {
                value = jsbeautify[type + "_beautify"](value, options);
                if (trim)
                    value = value.replace(/^/gm, indent).trim();
                if (range.end.column === 0)
                    value += "\n" + indent;
            }
            catch (e) {
                return false;
            }

            var end = session.diffAndReplace(range, value);
            if (!keepSelection)
                sel.setSelectionRange(Range.fromPoints(range.start, end));

            return true;
        }

        function formatString(mode, value, options) {
            options = getOptions(options);
            var type = getType(mode, value, options);
            return jsbeautify[type + "_beautify"](value, options);
        }

        function getOptions(options, allowAdvanced) {
            if (!options) options = {};

            if (!options.hasOwnProperty("space_before_conditional"))
                options.space_before_conditional =
                    settings.getBool("project/format/jsbeautify/@space_before_conditional");
            if (!options.hasOwnProperty("keep_array_indentation"))
                options.keep_array_indentation =
                    settings.getBool("project/format/jsbeautify/@keeparrayindentation");
            if (!options.hasOwnProperty("preserve_newlines"))
                options.preserve_newlines =
                    settings.getBool("project/format/jsbeautify/@preserveempty");
            if (!options.hasOwnProperty("unescape_strings"))
                options.unescape_strings =
                    settings.getBool("project/format/jsbeautify/@unescape_strings");
            if (!options.hasOwnProperty("jslint_happy"))
                options.jslint_happy =
                    settings.getBool("project/format/jsbeautify/@jslinthappy");
            if (!options.hasOwnProperty("brace_style"))
                options.brace_style =
                    settings.get("project/format/jsbeautify/@braces");
            if (!options.hasOwnProperty("indent_inner_html"))
                options.indent_inner_html =
                    settings.get("project/format/jsbeautify/@indent_inner_html");
            if (!options.hasOwnProperty("e4x"))
                options.e4x = true;

            if (allowAdvanced) {
                var json = settings.get("project/format/jsbeautify/@advanced");
                if (json && typeof json == "object")
                    util.extend(options, json);
            }

            if (!options.indent_char)
                options.indent_char = " ";
            if (!options.indent_size)
                options.indent_size = 4;

            return options;
        }

        function getType(mode, value, options) {
            var type = null;

            if (mode == "javascript" || mode == "json" || mode == "jsx") {
                type = "js";
            } else if (mode == "css" || mode == "less" || mode == "scss") {
                type = "css";
            } else if (/^\s*<!?\w/.test(value)) {
                type = "html";
            } else if (mode == "xml") {
                type = "html";
            } else if (mode == "html" || mode == "xhtml"
                       || mode === "plugins/salesforce.language/modes/visualforce"
                       || mode === "plugins/salesforce.language/modes/lightning") {
                if (/[^<]+?{[\s\-\w]+:[^}]+;/.test(value))
                    type = "css";
                else if (/<\w+[ \/>]/.test(value))
                    type = "html";
                else
                    type = "js";
            } else if (mode == "handlebars") {
                if (options) options.indent_handlebars = true;
                type = "html";
            }

            return type;
        }

        /***** Lifecycle *****/

        plugin.on("load", function() {
            load();
        });
        plugin.on("enable", function() {

        });
        plugin.on("disable", function() {

        });
        plugin.on("unload", function() {
            loaded = false;
        });

        /***** Register and define API *****/

        /**
         * Beautify extension for the Cloud9 client
         *
         * Reformats the selected code in the current document
         * Processing/formatting code from https://github.com/einars/js-beautify
         */
        plugin.freezePublicAPI({
            /**
             *
             */
            formatEditor: formatEditor,

            /**
             *
             */
            formatString: formatString,
        });

        register(null, {
            "format.jsbeautify": plugin
        });
    }
});
<|endoftext|>


<commit_before>module.exports.init = function (sf, logger) {
    /**
     * Registering handler for failed asserts. The handler is doing logging and is throwing an error.
     * @param explanation {String} - failing reason message.
     */
    let __failWasAlreadyGenerated = false;
    let __assetsCounter = 0;
    let beginWasCalled = false;

    const timeoutUntilBegin = setTimeout(function () {
        if (!beginWasCalled) {
            sf.assert.begin("asset.begin was not called, the exit time for the test is automatically set to 2 seconds. asset.callback can increase this time");
        }
    }, 1000);

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('forceFailedTest', function (message, err) {
        console.error("Test should fail because:", message, err);
        __failWasAlreadyGenerated = true;
    });

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('begin', function (message, cleanFunctions, timeout) {
        //logger.recordAssert(message);
        beginWasCalled = true;
        console.log(message);
        sf.assert.end(cleanFunctions, timeout, true);
    });

    function recordFail(...args) { //record fail only once
        if (!__failWasAlreadyGenerated) {
            __failWasAlreadyGenerated = true;
            logger.recordAssert(...args);
        }
    }

    function delayEnd(message, milliseconds) {
        if (!beginWasCalled) {
            clearTimeout(timeoutUntilBegin);
            sf.assert.begin(message, null, milliseconds);
        } else {
            //console.log('Begin was called before the delay could be applied')
        }
    }

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.exceptions.register('assertFail', function (explanation) {
        const message = "Assert or invariant has failed " + (explanation ? explanation : "");
        const err = new Error(message);
        err.isFailedAssert = true;
        recordFail('[Fail] ' + message, err, true);
        throw err;
    });

    /**
     * Registering assert for equality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails.
     */
    sf.assert.addCheck('equal', function (v1, v2, explanation) {
        if (v1 !== v2) {
            if (!explanation) {
                explanation = "Assertion failed: [" + v1 + " !== " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for inequality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notEqual', function (v1, v2, explanation) {
        if (v1 === v2) {
            if (!explanation) {
                explanation = " [" + v1 + " == " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to true. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('true', function (b, explanation) {
        if (!b) {
            if (!explanation) {
                explanation = " expression is false but is expected to be true";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to false. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('false', function (b, explanation) {
        if (b) {
            if (!explanation) {
                explanation = " expression is true but is expected to be false";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('isNull', function (v1, explanation) {
        if (v1 !== null) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to be not null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notNull', function (v1, explanation) {
        if (v1 === null && typeof v1 === "object") {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Checks if all properties of the second object are own properties of the first object.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @returns {boolean} - returns true, if the check has passed or false otherwise.
     */
    function objectHasFields(firstObj, secondObj) {
        for (let field in secondObj) {
            if (firstObj.hasOwnProperty(field)) {
                if (firstObj[field] !== secondObj[field]) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return true;
    }

    function objectsAreEqual(firstObj, secondObj) {
        let areEqual = true;
        if (firstObj !== secondObj) {
            if (typeof firstObj !== typeof secondObj) {
                areEqual = false;
            } else if (Array.isArray(firstObj) && Array.isArray(secondObj)) {
                firstObj.sort();
                secondObj.sort();
                if (firstObj.length !== secondObj.length) {
                    areEqual = false;
                } else {
                    for (let i = 0; i < firstObj.length; ++i) {
                        if (!objectsAreEqual(firstObj[i], secondObj[i])) {
                            areEqual = false;
                            break;
                        }
                    }
                }
            } else if ((typeof firstObj === 'function' && typeof secondObj === 'function') ||
                (firstObj instanceof Date && secondObj instanceof Date) ||
                (firstObj instanceof RegExp && secondObj instanceof RegExp) ||
                (firstObj instanceof String && secondObj instanceof String) ||
                (firstObj instanceof Number && secondObj instanceof Number)) {
                areEqual = firstObj.toString() === secondObj.toString();
            } else if (typeof firstObj === 'object' && typeof secondObj === 'object') {
                areEqual = objectHasFields(firstObj, secondObj);
                // isNaN(undefined) returns true
            } else if (isNaN(firstObj) && isNaN(secondObj) && typeof firstObj === 'number' && typeof secondObj === 'number') {
                areEqual = true;
            } else {
                areEqual = false;
            }
        }

        return areEqual;
    }

    /**
     * Registering assert for evaluating if all properties of the second object are own properties of the first object.
     * If check fails, the assertFail is invoked.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("objectHasFields", function (firstObj, secondObj, explanation) {
        if (!objectHasFields(firstObj, secondObj)) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating if all element from the second array are present in the first array.
     * Deep comparison between the elements of the array is used.
     * If check fails, the assertFail is invoked.
     * @param firstArray {Array}- first array
     * @param secondArray {Array} - second array
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("arraysMatch", function (firstArray, secondArray, explanation) {
        if (firstArray.length !== secondArray.length) {
            sf.exceptions.assertFail(explanation);
        } else {
            const result = objectsAreEqual(firstArray, secondArray);
            // const arraysDontMatch = secondArray.every(element => firstArray.indexOf(element) !== -1);
            // let arraysDontMatch = secondArray.some(function (expectedElement) {
            //     let found = firstArray.some(function(resultElement){
            //         return objectHasFields(resultElement,expectedElement);
            //     });
            //     return found === false;
            // });

            if (!result) {
                sf.exceptions.assertFail(explanation);
            }
        }
    });

    // added mainly for test purposes, better test frameworks like mocha could be much better

    /**
     * Registering assert for checking if a function is failing.
     * If the function is throwing an exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('fail', function (testName, func) {
        try {
            func();
            recordFail("[Fail] " + testName);
        } catch (err) {
            logger.recordAssert("[Pass] " + testName);
        }
    });

    /**
     * Registering assert for checking if a function is executed with no exceptions.
     * If the function is not throwing any exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('pass', function (testName, func) {
        try {
            func();
            logger.recordAssert("[Pass] " + testName);
        } catch (err) {
            recordFail("[Fail] " + testName, err.stack);
        }
    });

    /**
     * Alias for the pass assert.
     */
    sf.assert.alias('test', 'pass');

    /**
     * Registering assert for checking if a callback function is executed before timeout is reached without any exceptions.
     * If the function is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('callback', function (testName, func, timeout) {

        if (!func || typeof func != "function") {
            throw new Error("Wrong usage of assert.callback!");
        }

        if (!timeout) {
            timeout = 500;
        }

        __assetsCounter++;

        let passed = false;

        function callback() {
            __assetsCounter--;
            if (!passed) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                successTest();
            } else {
                recordFail("[Fail (multiple calls)] " + testName);
            }
        }

        setTimeout(successTest, timeout);
        delayEnd(testName, timeout + 100);

        try {
            func(callback);
        } catch (err) {
            recordFail("[Fail] " + testName, err, true);
        }

        function successTest(force) {
            if (!passed) {
                logger.recordAssert("[Fail Timeout] " + testName);
            }
        }

    });

    /**
     * Registering assert for checking if an array of callback functions are executed in a waterfall manner,
     * before timeout is reached without any exceptions.
     * If any of the functions is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('steps', function (testName, arr, timeout) {
        if (!timeout) {
            timeout = 500;
        }

        let currentStep = 0;
        let passed = false;

        function next() {
            if (currentStep === arr.length) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                return;
            }

            const func = arr[currentStep];
            currentStep++;
            try {
                func(next);
            } catch (err) {
                recordFail("[Fail] " + testName + " [at step " + currentStep + "]", err);
            }
        }

        function successTest(force) {
            if (!passed) {
                recordFail("[Fail Timeout] " + testName + " [at step " + currentStep + "]");
            }
        }

        setTimeout(successTest, timeout);
        // delayEnd(testName, timeout + 100);
        next();
    });

    /**
     * Alias for the steps assert.
     */
    sf.assert.alias('waterfall', 'steps');

    let cleaningArray = [];
    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('addCleaningFunction', function (func) {
        cleaningArray.push(func);
    });

    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('disableCleanings', function (func) {
        cleaningArray = [];
    });

    sf.assert.addCheck('hashesAreEqual', function (hash1, hash2) {
        if (!Array.isArray(hash1) && !Array.isArray(hash2)) {
            return hash1 === hash2;
        }

        if (!Array.isArray(hash1) || !Array.isArray(hash2)) {
            return false;
        }

        hash1.sort();
        hash2.sort();
        for (let i in hash1) {
            if (hash1[i] !== hash2[i]) {
                return false;
            }
        }

        return true;
    });

    /**
     * Registering assert for asynchronously printing all execution summary from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('end', function (cleaningFunction, timeout, silence) {
        if (!timeout) {
            timeout = 1000;
        }

        function handler() {
            if (logger.dumpWhys) {
                logger.dumpWhys().forEach(function (c) {
                    const executionSummary = c.getExecutionSummary();
                    console.log(JSON.stringify(executionSummary, null, 4));
                });
            }

            if (!silence) {
                console.log("Forcing exit after", timeout, "ms");
            }

            setTimeout(function () {
                if (__failWasAlreadyGenerated || __assetsCounter != 0) {
                    process.exit(1);
                } else {
                    process.exit(0);
                }
            }, 1000);

            cleaningArray.map(function (func) {
                func();
            });
            if (cleaningFunction) {
                cleaningFunction();
            }
        }

        setTimeout(handler, timeout);
    });

};<commit_msg>bug fix: preventing double-check auto kill mechanism in case of misuse of the module<commit_after>module.exports.init = function (sf, logger) {
    /**
     * Registering handler for failed asserts. The handler is doing logging and is throwing an error.
     * @param explanation {String} - failing reason message.
     */
    let __failWasAlreadyGenerated = false;
    let __assetsCounter = 0;
    let beginWasCalled = false;

    if(typeof global.timeoutUntilBegin === "undefined"){
        global.timeoutUntilBegin = setTimeout(function () {
            if (!beginWasCalled) {
                sf.assert.begin("asset.begin was not called, the exit time for the test is automatically set to 2 seconds. asset.callback can increase this time");
            }
        }, 1000);
    }else{
        console.trace("DoubleCheck was evaluated atleast 2 times! Maybe it was required from disk instead of bundles.");
    }

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('forceFailedTest', function (message, err) {
        console.error("Test should fail because:", message, err);
        __failWasAlreadyGenerated = true;
    });

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('begin', function (message, cleanFunctions, timeout) {
        //logger.recordAssert(message);
        beginWasCalled = true;
        console.log(message);
        sf.assert.end(cleanFunctions, timeout, true);
    });

    function recordFail(...args) { //record fail only once
        if (!__failWasAlreadyGenerated) {
            __failWasAlreadyGenerated = true;
            logger.recordAssert(...args);
        }
    }

    function delayEnd(message, milliseconds) {
        if (!beginWasCalled) {
            clearTimeout(timeoutUntilBegin);
            sf.assert.begin(message, null, milliseconds);
        } else {
            //console.log('Begin was called before the delay could be applied')
        }
    }

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.exceptions.register('assertFail', function (explanation) {
        const message = "Assert or invariant has failed " + (explanation ? explanation : "");
        const err = new Error(message);
        err.isFailedAssert = true;
        recordFail('[Fail] ' + message, err, true);
        throw err;
    });

    /**
     * Registering assert for equality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails.
     */
    sf.assert.addCheck('equal', function (v1, v2, explanation) {
        if (v1 !== v2) {
            if (!explanation) {
                explanation = "Assertion failed: [" + v1 + " !== " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for inequality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notEqual', function (v1, v2, explanation) {
        if (v1 === v2) {
            if (!explanation) {
                explanation = " [" + v1 + " == " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to true. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('true', function (b, explanation) {
        if (!b) {
            if (!explanation) {
                explanation = " expression is false but is expected to be true";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to false. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('false', function (b, explanation) {
        if (b) {
            if (!explanation) {
                explanation = " expression is true but is expected to be false";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('isNull', function (v1, explanation) {
        if (v1 !== null) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to be not null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notNull', function (v1, explanation) {
        if (v1 === null && typeof v1 === "object") {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Checks if all properties of the second object are own properties of the first object.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @returns {boolean} - returns true, if the check has passed or false otherwise.
     */
    function objectHasFields(firstObj, secondObj) {
        for (let field in secondObj) {
            if (firstObj.hasOwnProperty(field)) {
                if (firstObj[field] !== secondObj[field]) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return true;
    }

    function objectsAreEqual(firstObj, secondObj) {
        let areEqual = true;
        if (firstObj !== secondObj) {
            if (typeof firstObj !== typeof secondObj) {
                areEqual = false;
            } else if (Array.isArray(firstObj) && Array.isArray(secondObj)) {
                firstObj.sort();
                secondObj.sort();
                if (firstObj.length !== secondObj.length) {
                    areEqual = false;
                } else {
                    for (let i = 0; i < firstObj.length; ++i) {
                        if (!objectsAreEqual(firstObj[i], secondObj[i])) {
                            areEqual = false;
                            break;
                        }
                    }
                }
            } else if ((typeof firstObj === 'function' && typeof secondObj === 'function') ||
                (firstObj instanceof Date && secondObj instanceof Date) ||
                (firstObj instanceof RegExp && secondObj instanceof RegExp) ||
                (firstObj instanceof String && secondObj instanceof String) ||
                (firstObj instanceof Number && secondObj instanceof Number)) {
                areEqual = firstObj.toString() === secondObj.toString();
            } else if (typeof firstObj === 'object' && typeof secondObj === 'object') {
                areEqual = objectHasFields(firstObj, secondObj);
                // isNaN(undefined) returns true
            } else if (isNaN(firstObj) && isNaN(secondObj) && typeof firstObj === 'number' && typeof secondObj === 'number') {
                areEqual = true;
            } else {
                areEqual = false;
            }
        }

        return areEqual;
    }

    /**
     * Registering assert for evaluating if all properties of the second object are own properties of the first object.
     * If check fails, the assertFail is invoked.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("objectHasFields", function (firstObj, secondObj, explanation) {
        if (!objectHasFields(firstObj, secondObj)) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating if all element from the second array are present in the first array.
     * Deep comparison between the elements of the array is used.
     * If check fails, the assertFail is invoked.
     * @param firstArray {Array}- first array
     * @param secondArray {Array} - second array
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("arraysMatch", function (firstArray, secondArray, explanation) {
        if (firstArray.length !== secondArray.length) {
            sf.exceptions.assertFail(explanation);
        } else {
            const result = objectsAreEqual(firstArray, secondArray);
            // const arraysDontMatch = secondArray.every(element => firstArray.indexOf(element) !== -1);
            // let arraysDontMatch = secondArray.some(function (expectedElement) {
            //     let found = firstArray.some(function(resultElement){
            //         return objectHasFields(resultElement,expectedElement);
            //     });
            //     return found === false;
            // });

            if (!result) {
                sf.exceptions.assertFail(explanation);
            }
        }
    });

    // added mainly for test purposes, better test frameworks like mocha could be much better

    /**
     * Registering assert for checking if a function is failing.
     * If the function is throwing an exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('fail', function (testName, func) {
        try {
            func();
            recordFail("[Fail] " + testName);
        } catch (err) {
            logger.recordAssert("[Pass] " + testName);
        }
    });

    /**
     * Registering assert for checking if a function is executed with no exceptions.
     * If the function is not throwing any exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('pass', function (testName, func) {
        try {
            func();
            logger.recordAssert("[Pass] " + testName);
        } catch (err) {
            recordFail("[Fail] " + testName, err.stack);
        }
    });

    /**
     * Alias for the pass assert.
     */
    sf.assert.alias('test', 'pass');

    /**
     * Registering assert for checking if a callback function is executed before timeout is reached without any exceptions.
     * If the function is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('callback', function (testName, func, timeout) {

        if (!func || typeof func != "function") {
            throw new Error("Wrong usage of assert.callback!");
        }

        if (!timeout) {
            timeout = 500;
        }

        __assetsCounter++;

        let passed = false;

        function callback() {
            __assetsCounter--;
            if (!passed) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                successTest();
            } else {
                recordFail("[Fail (multiple calls)] " + testName);
            }
        }

        setTimeout(successTest, timeout);
        delayEnd(testName, timeout + 100);

        try {
            func(callback);
        } catch (err) {
            recordFail("[Fail] " + testName, err, true);
        }

        function successTest(force) {
            if (!passed) {
                logger.recordAssert("[Fail Timeout] " + testName);
            }
        }

    });

    /**
     * Registering assert for checking if an array of callback functions are executed in a waterfall manner,
     * before timeout is reached without any exceptions.
     * If any of the functions is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('steps', function (testName, arr, timeout) {
        if (!timeout) {
            timeout = 500;
        }

        let currentStep = 0;
        let passed = false;

        function next() {
            if (currentStep === arr.length) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                return;
            }

            const func = arr[currentStep];
            currentStep++;
            try {
                func(next);
            } catch (err) {
                recordFail("[Fail] " + testName + " [at step " + currentStep + "]", err);
            }
        }

        function successTest(force) {
            if (!passed) {
                recordFail("[Fail Timeout] " + testName + " [at step " + currentStep + "]");
            }
        }

        setTimeout(successTest, timeout);
        // delayEnd(testName, timeout + 100);
        next();
    });

    /**
     * Alias for the steps assert.
     */
    sf.assert.alias('waterfall', 'steps');

    let cleaningArray = [];
    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('addCleaningFunction', function (func) {
        cleaningArray.push(func);
    });

    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('disableCleanings', function (func) {
        cleaningArray = [];
    });

    sf.assert.addCheck('hashesAreEqual', function (hash1, hash2) {
        if (!Array.isArray(hash1) && !Array.isArray(hash2)) {
            return hash1 === hash2;
        }

        if (!Array.isArray(hash1) || !Array.isArray(hash2)) {
            return false;
        }

        hash1.sort();
        hash2.sort();
        for (let i in hash1) {
            if (hash1[i] !== hash2[i]) {
                return false;
            }
        }

        return true;
    });

    /**
     * Registering assert for asynchronously printing all execution summary from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('end', function (cleaningFunction, timeout, silence) {
        if (!timeout) {
            timeout = 1000;
        }

        function handler() {
            if (logger.dumpWhys) {
                logger.dumpWhys().forEach(function (c) {
                    const executionSummary = c.getExecutionSummary();
                    console.log(JSON.stringify(executionSummary, null, 4));
                });
            }

            if (!silence) {
                console.log("Forcing exit after", timeout, "ms");
            }

            setTimeout(function () {
                if (__failWasAlreadyGenerated || __assetsCounter != 0) {
                    process.exit(1);
                } else {
                    process.exit(0);
                }
            }, 1000);

            cleaningArray.map(function (func) {
                func();
            });
            if (cleaningFunction) {
                cleaningFunction();
            }
        }

        setTimeout(handler, timeout);
    });

};<|endoftext|>


<commit_before>/* globals window */
var Backbone     = require('backbone'),
    $            = require('jquery'),
    Query        = require('lib/query'),
    _            = require('underscore'),
    facilities   = require('collections/facilities').instance,
    searchParams = ["fr"],
    parseParams  = require('lib/query_param_parser');

function generateQueryParams(queryString) {
  var params       = parseParams(queryString),
      categories   = _.compact((params.categories || '').split(',')),
      demographics = _.compact((params.demographics || '').split(',')),
      gender       = params.gender || null,
      search       = decodeURIComponent(params.search || ''),
      sort         = params.sort,
      queryParams  = { search: search },
      filterParams = {};

  if (categories.length > 0) {
    filterParams.categories = categories;
  }

  if (demographics.length > 0) {
    filterParams.age = demographics;
  }

  if (params.gender && params.gender !== 'A') {
    filterParams.gender = params.gender;
  }

  if (params.sort) {
    queryParams.sort = params.sort;
  }

  queryParams.filter = filterParams;

  return queryParams;
}

function validCategory(category) {
  return category && (/[a-z]+/).test(category.toString());
}

function getData($elements, dataAttrName) {
  var result = [];
  $elements.each(function(n, el) {
    result.push($(el).data(dataAttrName));
  });
  return result;
}

var CATEGORIES = [
  {
    key: "food",
    title: "Food",
    icon: "coffee"
  },
  {
    key: "medical",
    title: "Medical",
    icon: "medical"
  },
  {
    key: "housing",
    title: "Housing",
    icon: "house"
  },
  {
    key: "technology",
    title: "Technology",
    icon: "desktop"
  },
  {
    key: "hygiene",
    title: "Hygiene",
    icon: "droplet"
  }
];

var ListView = Backbone.View.extend({
  template: require('templates/list'),

  events: {
    "click #filter-button": 'goToFilter',
    "click #load-more":     'loadMore'
  },

  initialize: function() {
    this.listenTo(this.collection, 'reset', this.render);
  },

  reset: function() {
    this.offset = this.hideMore = null;
  },

  loadMore: function() {
    $('#loading-spinner').show();
    $('#load-more-container').hide();

    Query.submit(this.getFilterParams()).done(function(results) {
      $('#loading-spinner').hide();

      this.offset = results.offset;
      this.hideMore = (results.data.length < 10);

      this.collection.add(results.data);
      this.render();
    }.bind(this));
  },

  goToFilter: function() {
    var router = require('routers/router').instance;
    router.navigate("filter", {trigger: true});
  },

  generateQueryParams: generateQueryParams,

  getFilterParams: function () {
    var queryString  = window.location.hash.substring(window.location.hash.indexOf('?')+1),
        queryParams  = generateQueryParams(queryString);

    queryParams.offset = this.offset;

    this.options.categories = queryParams.filter.categories || [];

    return queryParams;
  },

  performQuery: function(params) {
    return Query.submit(params).done(function(results) {
      searchParams = params;
      // populate with results
      facilities.reset(results.data);
      this.offset = results.offset;
    }.bind(this));
  },

  resetFilters: function() {
    this.$(".query .selected").removeClass("selected");
    var self = this;
    _.each(this.options.categories, function(category) {
      self.$categoryOption(category).addClass("selected");
    });

    this.$(".query-option-gender [data-value='A']").addClass('selected');
  },

  $categoryOption: function(category) {
    if(validCategory(category)) {
      return this.$(".query-option-category [data-value=" + category + "]").addClass("selected");
    } else {
      return $();
    }

  },

  render: function() {
    var deepJson = this.deepToJson(this.collection);
    var templateJson = this.flattenServices(deepJson);

    // replace with template
    this.$el.html(this.template({
      facilities: templateJson,
      categories: CATEGORIES,
      searchParams: this.filterSelectCategories(this.options.categories),
      navButtons: [
        {class: 'left', id: 'backNav-button', text: 'Back'},
        {class: 'right', id: 'filter-button', text: 'Filter'}
      ]
    }));
    this.$('.query').hide();
    this.$('.option-group-exclusive .query-option').click(function() {
      $(this).closest(".option-group-exclusive").find(".query-option").removeClass("selected");
      $(this).toggleClass("selected");
    });

    this.$('.option-group .query-option').click(function() {
      $(this).toggleClass("selected");
    });
    this.$('#backNav-button').click(function(){
      require('routers/router').instance.back();
    });

    if ( !this.hideMore ) {
      this.$('#load-more-container').show();
    }

    this.resetFilters();
    return this;
  },

  deepToJson: function(collection) {
    var json = [],
        modelJson;

    json = _.map(collection.models, function(model) {
      modelJson = model.toJSON();
      modelJson.services = [];
      _.each(model.attributes.services, function(service) {
        modelJson.services.push(service.toJSON());
      });
      return modelJson;
    });

    return json;
  },

  filterSelectCategories: function(queryParams) {
    var selectedCategories = [];
    _.each(queryParams, function(queryName){
      var match = _.find(CATEGORIES, function(e){ return e.key == queryName; });
      selectedCategories.push(match);
    });
    return selectedCategories;
  },

  flattenServices: function(jsonArray) {
    var serviceCategories,
        allNotes,
        flattened = [],
        self = this;

    _.each(jsonArray, function(jsonModel) {
      serviceCategories = [];
      allNotes = [];
      _.each(jsonModel.services, function(jsonService) {
        serviceCategories.push(jsonService.category);
        allNotes.push(jsonService.notes);
      });
      jsonModel.serviceCategories = self.filterSelectCategories(serviceCategories);
      // jsonModel.serviceCategories = serviceCategories.join(', ');
      jsonModel.allNotes = allNotes.join(' ');
      flattened.push(jsonModel);
    });

    return flattened;
  }
});

module.exports = ListView;
<commit_msg>List View: avoid showing repeated categories<commit_after>/* globals window */
var Backbone     = require('backbone'),
    $            = require('jquery'),
    Query        = require('lib/query'),
    _            = require('underscore'),
    facilities   = require('collections/facilities').instance,
    searchParams = ["fr"],
    parseParams  = require('lib/query_param_parser');

function generateQueryParams(queryString) {
  var params       = parseParams(queryString),
      categories   = _.compact((params.categories || '').split(',')),
      demographics = _.compact((params.demographics || '').split(',')),
      gender       = params.gender || null,
      search       = decodeURIComponent(params.search || ''),
      sort         = params.sort,
      queryParams  = { search: search },
      filterParams = {};

  if (categories.length > 0) {
    filterParams.categories = categories;
  }

  if (demographics.length > 0) {
    filterParams.age = demographics;
  }

  if (params.gender && params.gender !== 'A') {
    filterParams.gender = params.gender;
  }

  if (params.sort) {
    queryParams.sort = params.sort;
  }

  queryParams.filter = filterParams;

  return queryParams;
}

function validCategory(category) {
  return category && (/[a-z]+/).test(category.toString());
}

function getData($elements, dataAttrName) {
  var result = [];
  $elements.each(function(n, el) {
    result.push($(el).data(dataAttrName));
  });
  return result;
}

var CATEGORIES = [
  {
    key: "food",
    title: "Food",
    icon: "coffee"
  },
  {
    key: "medical",
    title: "Medical",
    icon: "medical"
  },
  {
    key: "housing",
    title: "Housing",
    icon: "house"
  },
  {
    key: "technology",
    title: "Technology",
    icon: "desktop"
  },
  {
    key: "hygiene",
    title: "Hygiene",
    icon: "droplet"
  }
];

var ListView = Backbone.View.extend({
  template: require('templates/list'),

  events: {
    "click #filter-button": 'goToFilter',
    "click #load-more":     'loadMore'
  },

  initialize: function() {
    this.listenTo(this.collection, 'reset', this.render);
  },

  reset: function() {
    this.offset = this.hideMore = null;
  },

  loadMore: function() {
    $('#loading-spinner').show();
    $('#load-more-container').hide();

    Query.submit(this.getFilterParams()).done(function(results) {
      $('#loading-spinner').hide();

      this.offset = results.offset;
      this.hideMore = (results.data.length < 10);

      this.collection.add(results.data);
      this.render();
    }.bind(this));
  },

  goToFilter: function() {
    var router = require('routers/router').instance;
    router.navigate("filter", {trigger: true});
  },

  generateQueryParams: generateQueryParams,

  getFilterParams: function () {
    var queryString  = window.location.hash.substring(window.location.hash.indexOf('?')+1),
        queryParams  = generateQueryParams(queryString);

    queryParams.offset = this.offset;

    this.options.categories = queryParams.filter.categories || [];

    return queryParams;
  },

  performQuery: function(params) {
    return Query.submit(params).done(function(results) {
      searchParams = params;
      // populate with results
      facilities.reset(results.data);
      this.offset = results.offset;
    }.bind(this));
  },

  resetFilters: function() {
    this.$(".query .selected").removeClass("selected");
    var self = this;
    _.each(this.options.categories, function(category) {
      self.$categoryOption(category).addClass("selected");
    });

    this.$(".query-option-gender [data-value='A']").addClass('selected');
  },

  $categoryOption: function(category) {
    if(validCategory(category)) {
      return this.$(".query-option-category [data-value=" + category + "]").addClass("selected");
    } else {
      return $();
    }

  },

  render: function() {
    var deepJson = this.deepToJson(this.collection);
    var templateJson = this.flattenServices(deepJson);

    // replace with template
    this.$el.html(this.template({
      facilities: templateJson,
      categories: CATEGORIES,
      searchParams: this.filterSelectCategories(this.options.categories),
      navButtons: [
        {class: 'left', id: 'backNav-button', text: 'Back'},
        {class: 'right', id: 'filter-button', text: 'Filter'}
      ]
    }));
    this.$('.query').hide();
    this.$('.option-group-exclusive .query-option').click(function() {
      $(this).closest(".option-group-exclusive").find(".query-option").removeClass("selected");
      $(this).toggleClass("selected");
    });

    this.$('.option-group .query-option').click(function() {
      $(this).toggleClass("selected");
    });
    this.$('#backNav-button').click(function(){
      require('routers/router').instance.back();
    });

    if ( !this.hideMore ) {
      this.$('#load-more-container').show();
    }

    this.resetFilters();
    return this;
  },

  deepToJson: function(collection) {
    var json = [],
        modelJson;

    json = _.map(collection.models, function(model) {
      modelJson = model.toJSON();
      modelJson.services = [];
      _.each(model.attributes.services, function(service) {
        modelJson.services.push(service.toJSON());
      });
      return modelJson;
    });

    return json;
  },

  filterSelectCategories: function(queryParams) {
    var match, selectedCategories = [];
    _.each(queryParams, function(queryName){
      match = _.find(CATEGORIES, function(e){ return e.key == queryName; });
      if (!_.contains(selectedCategories, match)) {
        selectedCategories.push(match);
      }
    });
    return selectedCategories;
  },

  flattenServices: function(jsonArray) {
    var serviceCategories,
        allNotes,
        flattened = [],
        self = this;

    _.each(jsonArray, function(jsonModel) {
      serviceCategories = [];
      allNotes = [];
      _.each(jsonModel.services, function(jsonService) {
        serviceCategories.push(jsonService.category);
        allNotes.push(jsonService.notes);
      });
      jsonModel.serviceCategories = self.filterSelectCategories(serviceCategories);
      // jsonModel.serviceCategories = serviceCategories.join(', ');
      jsonModel.allNotes = allNotes.join(' ');
      flattened.push(jsonModel);
    });

    return flattened;
  }
});

module.exports = ListView;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

export default class ErrorBoundary extends React.Component {
  static propTypes = {
    children: PropTypes.any,
    fallback: PropTypes.any,
  };

  constructor(props) {
    super(props);
    this.state = {hasError: false, error: null, errorInfo: null};
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return {hasError: true};
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error,
      errorInfo,
    });
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return this.props.fallback ? this.props.fallback : null;
    }

    return this.props.children;
  }
}
<commit_msg>Update lib/error-boundary.js<commit_after>import React from 'react';
import PropTypes from 'prop-types';

export default class ErrorBoundary extends React.Component {
  static propTypes = {
    children: PropTypes.node.isRequired,
    fallback: PropTypes.any,
  };

  constructor(props) {
    super(props);
    this.state = {hasError: false, error: null, errorInfo: null};
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return {hasError: true};
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error,
      errorInfo,
    });
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return this.props.fallback ? this.props.fallback : null;
    }

    return this.props.children;
  }
}
<|endoftext|>