<commit_before>/**
* Format TestCase and return the source.
*
* @param testCase TestCase to format
* @param name The name of the test case, if any. It may be used to embed title into the source.
*/
function format(testCase, name) {
     var baseUrl = testCase.getBaseURL();

     var commandsText = '| script' + ('true' == options.libraryMode ? '' : ' | selenium driver fixture') + ' |\n';
     if ('true' == options.noStartStop) {
         commandsText += formatCommands(testCase.commands);
     } else {
    	 if (testCase.startBrowserLine) {
    		 commandsText += testCase.startBrowserLine + '\n';
    	 } else {
    		 commandsText += '| start browser | ' + options.browser + ' | on url | ' + baseUrl + ' |\n';
    	 }
		 commandsText += formatCommands(testCase.commands);
	     commandsText += '| stop browser |\n';
     }

     return commandsText;
}

/**
 * Format an array of commands to the snippet of source.
 * Used to copy the source into the clipboard.
 *
 * @param The array of commands to sort.
 */
function formatCommands(commands) {
    var commandsText = '';

    for (i = 0; i < commands.length; i++) {
        commandsText += getSourceForCommand(commands[i]) + "\n";
    }
    return  commandsText;
}

function getSourceForCommand(commandObj) {
	function escape(s) {
		s = s.replace(/\$\{(\w+)\}/g, "$$$1");
     	if (/^https?:\/\//.test(s) || /^[A-Z][a-z0-9]+[A-Z]/.test(s) || /@/.test(s)) { return "!-" + s + "-!"; }
		return s;
	}

	var text = null;

    if (commandObj.type == 'comment') {
    	return "| note | " + commandObj.comment + " |";
    } else if (commandObj.type == 'command') {
        // Set up variables to use for substitution
        var command = commandObj.command;
        var target = commandObj.target;
        var value = commandObj.value;

        if (/^store/.test(command)) {
         	if (value === '') {
             	return "| $" + target + "= | is | " + command + " |";
         	} else {
             	return "| $" + value + "= | is | " + command + " | on | " + escape(target) + " |";
         	}
     	} else if (value === '') {
             return "| ensure | do | " + command + " | on | " + escape(target) + " |";
        } else {
            return "| ensure | do | " + command + " | on | " + escape(target) + " | with | " + escape(value) + " |";
        }
    }
    return "| note | !-Untranslatable: '" + commandObj.toString + "'-! |";
}

/**
 * Parse source and update TestCase. Throw an exception if any error occurs.
 *
 * @param testCase TestCase to update
 * @param source The source to parse
 */
function parse(testCase, source) {
     var commands = [];

    var lines = source.split(/\n/g);

    for (i = 0; i < lines.length; i++) {
        // Catch some special cases (?)
        // TODO: extract BaseUrl -> testCase.setBaseURL(url)
        log.debug('Parsing line ' + i + ': "' + lines[i] + '"');
        var command;

        try {
        	command = getCommandForSource(lines[i]);
        } catch (err) {
        	if (err == 'unparsable') {
	    		if (/\t/.test(lines[i])) {
	    			// Line may be pasted directly from FitNesse page output (<-, -> for variable substitutions)
	    			var line = '| ' + lines[i].replace(/<-\[.*\]\s*\t/, '=\t').replace(/->\[.*?\]/g, '').replace(/\t/g, ' | ') + ' |';
	    			try {
	    				command = getCommandForSource(line);
	    			} catch (err) {
	    	        	if (err == 'unparsable') {
	    	        		command = new Comment("Can't parse line: '" + line + "'");
	    	        	} else {
	    	        		log.error(err);
	    	        	}
	    			}
	    		} else {
	    			command = new Comment("Can't parse line: '" + lines[i] + "'");
	    		}
        	} else {
        		log.error(err);
        	}
        }

        if (command) {
            if (command.baseUrl) {
            	testCase.setBaseURL(command.baseUrl);
            	testCase.startBrowserLine = command.line;
            } else {
            	commands.push(command);
            }
        }
    }

     testCase.commands = commands;
}

function getCommandForSource(line) {
	function unescape(s) {
		var m;
		// Convert variable from $fit to ${selenese} style
		s = s.replace(/\$(\w+)/g, '${$1}');
		// Clear escape characters from text section
		if (m = /^!-(.+?)-!$/.exec(s)) { s = m[1]; }
		return s;
	}

	var match;

	// | ensure | do | ${command} | on | ${target} | with | ${value} |
	if (match = /^\|\s*ensure\s*\|\s*do\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|\s*([^\|]+?)\s*\|\s*with\s*\|\s*((!-.*-!)|[^\|]+?)\s*\|/.exec(line)) {
		return new Command(match[1], unescape(match[2]), unescape(match[3]));

	// | ensure | do | ${command} | on | ${target} |
	} else if (match = /^\|\s*ensure\s*\|\s*do\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|\s*((!-.*-!)|[^\|]+?)\s*\|/.exec(line)) {
		return new Command(match[1], unescape(match[2]));

	// | ensure | do | ${command} | on | (some copy-paste cases)
	} else if (match = /^\|\s*ensure\s*\|\s*do\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|/.exec(line)) {
		return new Command(match[1]);

	// format: | $value= | is | ${command} | on | ${target} |
	} else if (match = /^\|\s*\$([^\|\s]+)=\s*\|\s*is\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|\s*((!-.*-!)|[^\|]+?)\s*\|/.exec(line)) {
		// Keep get->store mapping here for legacy
		return new Command(match[2].replace(/^get/, 'store'), unescape(match[3]), unescape(match[1]));

	// format: | $value= | is | ${command} |
	} else if (match = /^\|\s*\$([^\|\s]+)=\s*\|\s*is\s*\|\s*([^\|\s]+)\s*\|/.exec(line)) {
		// Keep get->store mapping here for legacy
		return new Command(match[2].replace(/^get/, 'store'), unescape(match[1]));

	// format: | note | ${text} |
	} else if (match = /^\|\s*note\s*\|\s*(.+?)\s*\|\s*$/.exec(line)) {
		return new Comment(match[1]);

	// format: | start browser | someBrowser | on url | http://example.com |
	} else if (match = /^\|\s*start\s+browser\s*\|\s*([^\|\s]+)\s*\|\s*on\s+url\s*\|\s*([^\|]+?)\s*\|/.exec(line)) {
		return {
			'line': line,
			'browser': match[1],
			'baseUrl': match[2]
		};

	// Ignore | script/scenario/start browser/stop browser |, log the rest
	} else if (!/^\s*$/.test(line)
			&& !/^\|\s*script\s*\|.*/i.test(line)
			&& !/^\|\s*scenario\s*\|.*/i.test(line)
			&& !/^\|\s*stop\s+browser\s*\|.*/.test(line)) {
		throw "unparsable";
	}
}

this.options = {
    'browser': 'firefox',
    'libraryMode': false,
    'noStartStop': false
}

this.configForm =
        '<description>Default browser</description>' +
        '<textbox id="options_browser" />' +
        '<checkbox id="options_libraryMode" label="Format as if the selenium driver fixture is defined as library"/>' +
        '<checkbox id="options_noStartStop" label="Do not add start/stop browser lines"/>';

<commit_msg>Simplified ensure command.<commit_after>/**
* Format TestCase and return the source.
*
* @param testCase TestCase to format
* @param name The name of the test case, if any. It may be used to embed title into the source.
*/
function format(testCase, name) {
     var baseUrl = testCase.getBaseURL();

     var commandsText = '| script' + ('true' == options.libraryMode ? '' : ' | selenium driver fixture') + ' |\n';
     if ('true' == options.noStartStop) {
         commandsText += formatCommands(testCase.commands);
     } else {
    	 if (testCase.startBrowserLine) {
    		 commandsText += testCase.startBrowserLine + '\n';
    	 } else {
    		 commandsText += '| start browser | ' + options.browser + ' | on url | ' + baseUrl + ' |\n';
    	 }
		 commandsText += formatCommands(testCase.commands);
	     commandsText += '| stop browser |\n';
     }

     return commandsText;
}

/**
 * Format an array of commands to the snippet of source.
 * Used to copy the source into the clipboard.
 *
 * @param The array of commands to sort.
 */
function formatCommands(commands) {
    var commandsText = '';

    for (i = 0; i < commands.length; i++) {
        commandsText += getSourceForCommand(commands[i]) + "\n";
    }
    return  commandsText;
}

function getSourceForCommand(commandObj) {
	function escape(s) {
		s = s.replace(/\$\{(\w+)\}/g, "$$$1");
     	if (/^https?:\/\//.test(s) || /^[A-Z][a-z0-9]+[A-Z]/.test(s) || /@/.test(s)) { return "!-" + s + "-!"; }
		return s;
	}

	var text = null;

    if (commandObj.type == 'comment') {
    	return "| note | " + commandObj.comment + " |";
    } else if (commandObj.type == 'command') {
        // Set up variables to use for substitution
        var command = commandObj.command;
        var target = commandObj.target;
        var value = commandObj.value;

        if (/^store/.test(command)) {
         	if (value === '') {
             	return "| $" + target + "= | is | " + command + " |";
         	} else {
             	return "| $" + value + "= | is | " + command + " | on | " + escape(target) + " |";
         	}
     	}

 		return "| ensure | do | " + command + " | on | " + escape(target) + (value === '' ? "" : " | with | " + escape(value)) + " |";
    }
    return "| note | !-Untranslatable: '" + commandObj.toString + "'-! |";
}

/**
 * Parse source and update TestCase. Throw an exception if any error occurs.
 *
 * @param testCase TestCase to update
 * @param source The source to parse
 */
function parse(testCase, source) {
     var commands = [];

    var lines = source.split(/\n/g);

    for (i = 0; i < lines.length; i++) {
        // Catch some special cases (?)
        // TODO: extract BaseUrl -> testCase.setBaseURL(url)
        log.debug('Parsing line ' + i + ': "' + lines[i] + '"');
        var command;

        try {
        	command = getCommandForSource(lines[i]);
        } catch (err) {
        	if (err == 'unparsable') {
	    		if (/\t/.test(lines[i])) {
	    			// Line may be pasted directly from FitNesse page output (<-, -> for variable substitutions)
	    			var line = '| ' + lines[i].replace(/<-\[.*\]\s*\t/, '=\t').replace(/->\[.*?\]/g, '').replace(/\t/g, ' | ') + ' |';
	    			try {
	    				command = getCommandForSource(line);
	    			} catch (err) {
	    	        	if (err == 'unparsable') {
	    	        		command = new Comment("Can't parse line: '" + line + "'");
	    	        	} else {
	    	        		log.error(err);
	    	        	}
	    			}
	    		} else {
	    			command = new Comment("Can't parse line: '" + lines[i] + "'");
	    		}
        	} else {
        		log.error(err);
        	}
        }

        if (command) {
            if (command.baseUrl) {
            	testCase.setBaseURL(command.baseUrl);
            	testCase.startBrowserLine = command.line;
            } else {
            	commands.push(command);
            }
        }
    }

     testCase.commands = commands;
}

function getCommandForSource(line) {
	function unescape(s) {
		var m;
		// Convert variable from $fit to ${selenese} style
		s = s.replace(/\$(\w+)/g, '${$1}');
		// Clear escape characters from text section
		if (m = /^!-(.+?)-!$/.exec(s)) { s = m[1]; }
		return s;
	}

	var match;

	// | ensure | do | ${command} | on | ${target} | with | ${value} |
	if (match = /^\|\s*ensure\s*\|\s*do\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|\s*([^\|]+?)\s*\|\s*with\s*\|\s*((!-.*-!)|[^\|]+?)\s*\|/.exec(line)) {
		return new Command(match[1], unescape(match[2]), unescape(match[3]));

	// | ensure | do | ${command} | on | ${target} |
	} else if (match = /^\|\s*ensure\s*\|\s*do\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|\s*((!-.*-!)|[^\|]+?)\s*\|/.exec(line)) {
		return new Command(match[1], unescape(match[2]));

	// | ensure | do | ${command} | on | (some copy-paste cases)
	} else if (match = /^\|\s*ensure\s*\|\s*do\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|/.exec(line)) {
		return new Command(match[1]);

	// format: | $value= | is | ${command} | on | ${target} |
	} else if (match = /^\|\s*\$([^\|\s]+)=\s*\|\s*is\s*\|\s*([^\|\s]+)\s*\|\s*on\s*\|\s*((!-.*-!)|[^\|]+?)\s*\|/.exec(line)) {
		// Keep get->store mapping here for legacy
		return new Command(match[2].replace(/^get/, 'store'), unescape(match[3]), unescape(match[1]));

	// format: | $value= | is | ${command} |
	} else if (match = /^\|\s*\$([^\|\s]+)=\s*\|\s*is\s*\|\s*([^\|\s]+)\s*\|/.exec(line)) {
		// Keep get->store mapping here for legacy
		return new Command(match[2].replace(/^get/, 'store'), unescape(match[1]));

	// format: | note | ${text} |
	} else if (match = /^\|\s*note\s*\|\s*(.+?)\s*\|\s*$/.exec(line)) {
		return new Comment(match[1]);

	// format: | start browser | someBrowser | on url | http://example.com |
	} else if (match = /^\|\s*start\s+browser\s*\|\s*([^\|\s]+)\s*\|\s*on\s+url\s*\|\s*([^\|]+?)\s*\|/.exec(line)) {
		return {
			'line': line,
			'browser': match[1],
			'baseUrl': match[2]
		};

	// Ignore | script/scenario/start browser/stop browser |, log the rest
	} else if (!/^\s*$/.test(line)
			&& !/^\|\s*script\s*\|.*/i.test(line)
			&& !/^\|\s*scenario\s*\|.*/i.test(line)
			&& !/^\|\s*stop\s+browser\s*\|.*/.test(line)) {
		throw "unparsable";
	}
}

this.options = {
    'browser': 'firefox',
    'libraryMode': false,
    'noStartStop': false
}

this.configForm =
        '<description>Default browser</description>' +
        '<textbox id="options_browser" />' +
        '<checkbox id="options_libraryMode" label="Format as if the selenium driver fixture is defined as library"/>' +
        '<checkbox id="options_noStartStop" label="Do not add start/stop browser lines"/>';

<|endoftext|>


<commit_before>const print = require('../utils/print')

/**
 * Extensions to print to the console.
 *
 * @param  {Plugin}     plugin  The plugin that triggered.
 * @param  {Command}    command The current command that is running.
 * @param  {RunContext} context The running context.
 * @return {Function}           A function to attach to the context.
 */
function attach (plugin, command, context) {
  const { colors, debug } = print
  const checkmark = colors.success('✔')
  const xmark = colors.error('ⅹ')

  /**
   * Prints an informational message.  Use this as your goto.
   */
  function info (message) {
    print.info(message)
  }

  /**
   * Prints a warning message.  Use this when you feel a disturbance in the force.
   */
  function warning (message) {
    print.warning(message)
  }

  /**
   * Prints an error message.  Use this when something goes Pants-On-Head wrong.
   * What does that mean?  Well, if your next line of code isn't process.exit(0), then
   * it was probably a warning.
   */
  function error (message) {
    print.error(message)
  }

  /**
   * Prints a success message.  Use this when something awesome just happened.
   */
  function success (message) {
    print.success(message)
  }

  // return back the feature set
  return {
    info,
    warning,
    error,
    success,
    debug,
    colors,
    checkmark,
    xmark,
    color: colors
  }
}

module.exports = attach
<commit_msg>Adds more printing options.<commit_after>const print = require('../utils/print')

/**
 * Extensions to print to the console.
 *
 * @param  {Plugin}     plugin  The plugin that triggered.
 * @param  {Command}    command The current command that is running.
 * @param  {RunContext} context The running context.
 * @return {Function}           A function to attach to the context.
 */
function attach (plugin, command, context) {
  const { colors, debug } = print
  const checkmark = colors.success('✔')
  const xmark = colors.error('ⅹ')

  /**
   * Prints an informational message.  Use this as your goto.
   */
  function info (message) {
    print.info(message)
  }

  /**
   * Prints a warning message.  Use this when you feel a disturbance in the force.
   */
  function warning (message) {
    print.warning(message)
  }

  /**
   * Prints an error message.  Use this when something goes Pants-On-Head wrong.
   * What does that mean?  Well, if your next line of code isn't process.exit(0), then
   * it was probably a warning.
   */
  function error (message) {
    print.error(message)
  }

  /**
   * Prints a success message.  Use this when something awesome just happened.
   */
  function success (message) {
    print.success(message)
  }

  // return back the feature set
  return {
    info,
    warning,
    error,
    success,
    debug,
    colors,
    checkmark,
    xmark,
    table: print.table,
    newline: print.newline,
    color: colors
  }
}

module.exports = attach
<|endoftext|>


<commit_before>require([
    'jquery',
    'underscore',
    'chai',
    'vellum/xml'
], function (
    $,
    _,
    chai,
    xml
) {
    var assert = chai.assert;

    describe("The XML normalizer", function () {
        function eq(value, escaped, humanize) {
            var normal = xml.normalize(value);
            assert.strictEqual(normal, escaped);
            if (_.isUndefined(humanize) || humanize) {
                assert.strictEqual(xml.humanize(normal), value);
            }
        }

        it("should preserve empty string", function () {
            eq('', '');
        });

        it("should preserve space", function () {
            eq(' ', ' ');
        });

        it("should preserve newline", function () {
            eq(' ', ' ');
        });

        it("should preserve empty XML node", function () {
            eq('<output />', '<output />');
        });

        it("should escape & character", function () {
            eq('Mother & Child Reunion', 'Mother & Child Reunion');
        });

        it("should escape attribute value with < character", function () {
            eq('<output value="2 < 3" />', '<output value="2 < 3" />');
        });

        it("should escape attribute value with > character", function () {
            eq('<output value="2 > 3" />', '<output value="2 > 3" />');
        });

        it("should escape attribute value with <= character", function () {
            eq('<output value="2 <= 3" />', '<output value="2 <= 3" />');
        });

        it("should escape attribute value with >= character", function () {
            eq('<output value="2 >= 3" />', '<output value="2 >= 3" />');
        });

        it("should escape attribute value with < tight", function () {
            eq('<output value="2<3" />', '<output value="2<3" />', false);
        });

        it("should escape attribute value with > tight", function () {
            eq('<output value="2>3" />', '<output value="2>3" />', false);
        });

        it("should escape < character", function () {
            eq('your visit count must be < 5', 'your visit count must be < 5');
        });

        it("should escape > character", function () {
            eq('your visit count must be > 5', 'your visit count must be > 5');
        });

        it("should escape attribute value with > and trailing text (v1)", function () {
            eq('<output value="2 > 3" /> text',
               '<output value="2 > 3" /> text');
        });

        it("should escape attribute value with > and trailing text (v2)", function () {
            eq("<output value='2 > 3' /> text",
               '<output value="2 > 3" /> text', false);
        });

        it("should escape attribute value with > and trailing text (v3)", function () {
            eq('<output value="2 > 3"/>text',
               '<output value="2 > 3" />text', false);
        });

        it("should escape attribute value with > and trailing text (v4)", function () {
            eq('<output value="2 > 3"></output> text',
               '<output value="2 > 3" /> text', false);
        });

        it("should escape attribute value with > and trailing text (v5)", function () {
            eq('<out:p_3-6. h:v-1._="2 > 3" />text',
               '<out:p_3-6. h:v-1._="2 > 3" />text');
        });

        it("should escape attribute value with < and trailing text", function () {
            eq('<output value="2 < 3" /> text',
               '<output value="2 < 3" /> text');
        });

        it("should escape < character before tag", function () {
            // immortalizing https://github.com/dimagi/Vellum/pull/212
            eq('your visit count must be < <output value="/path" />',
               'your visit count must be < <output value="/path" />');
        });

        it("should escape > character before tag", function () {
            eq('your visit count must be > <output value="/path" />',
               'your visit count must be > <output value="/path" />');
        });

        it("should preserve attribute value with path", function () {
            eq('<output value="/path" />', '<output value="/path" />');
        });

        it("should preserve output node with text before", function () {
            eq('text <output value="/path" />', 'text <output value="/path" />');
        });

        it("should preserve output node with text after", function () {
            eq('<output value="/path" /> text', '<output value="/path" /> text');
        });

        it("should preserve attribute without value", function () {
            eq('<output attr />', '<output attr="" />', false);
        });

        it("should preserve attribute value with path, unterminated tag", function () {
            eq('<output value="/path">', '<output value="/path" />', false);
        });

        it("should preserve newline", function () {
            eq('line 1\nline2', 'line 1\nline2');
        });

        it("should preserve newline in attribute value", function () {
            eq('<output value="selected(/path,\n2)" />',
               '<output value="selected(/path,\n2)" />');
        });

        it("should preserve attribute with unquoted value", function () {
            eq('<output attr=value />', '<output attr="value" />', false);
        });

        it("should accept jquery node", function () {
            eq($('<value><output /></value>'), '<output />', false);
        });

        /* -- The following are lossy -- */

        it("should handle malformed tag", function () {
            // big loss here
            eq('<output value="/path"', '', false);
        });

        it("should replace   with space", function () {
            eq('a   b', 'a   b', false);
        });
    });

    describe("The XML humanizer", function () {
        function eq(value, humanized) {
            assert.strictEqual(xml.humanize(value), humanized);
        }

        it("should convert free < character", function () {
            eq('2 < 3', '2 < 3');
        });

        it("should convert free > character", function () {
            eq('2 > 3', '2 > 3');
        });

        it("should convert free & character", function () {
            eq('2 & 3', '2 & 3');
        });

        it("should not convert escaped tag", function () {
            eq(' <div> ', ' <div> ');
        });

        it("should convert output tag", function () {
            eq('<output value="1 & 2 < 3" />',
               '<output value="1 & 2 < 3" />');
        });

        it("should convert child nodes", function () {
            var value = "<value>1 & 2 < 3 <output value='/path' /></value>";
            eq($(value), '1 & 2 < 3 <output value="/path" />');
        });

        it("should convert child output node", function () {
            var value = "<value><output value='1 & 2 < 3' /></value>";
            eq($(value), '<output value="1 & 2 < 3" />');
        });

        it("should convert empty child nodes", function () {
            eq($("<value />"), "");
        });

        it("should space child node", function () {
            eq($("<value> </value>"), " ");
        });

        it("should newline child node", function () {
            eq($("<value>\n</value>"), "\n");
        });
    });
});
<commit_msg>Add round-trip to XML humanizer tests<commit_after>require([
    'jquery',
    'underscore',
    'chai',
    'vellum/xml'
], function (
    $,
    _,
    chai,
    xml
) {
    var assert = chai.assert;

    describe("The XML normalizer", function () {
        function eq(value, escaped, humanize) {
            var normal = xml.normalize(value);
            assert.strictEqual(normal, escaped);
            if (_.isUndefined(humanize) || humanize) {
                assert.strictEqual(xml.humanize(normal), value);
            }
        }

        it("should preserve empty string", function () {
            eq('', '');
        });

        it("should preserve space", function () {
            eq(' ', ' ');
        });

        it("should preserve newline", function () {
            eq(' ', ' ');
        });

        it("should preserve empty XML node", function () {
            eq('<output />', '<output />');
        });

        it("should escape & character", function () {
            eq('Mother & Child Reunion', 'Mother & Child Reunion');
        });

        it("should escape attribute value with < character", function () {
            eq('<output value="2 < 3" />', '<output value="2 < 3" />');
        });

        it("should escape attribute value with > character", function () {
            eq('<output value="2 > 3" />', '<output value="2 > 3" />');
        });

        it("should escape attribute value with <= character", function () {
            eq('<output value="2 <= 3" />', '<output value="2 <= 3" />');
        });

        it("should escape attribute value with >= character", function () {
            eq('<output value="2 >= 3" />', '<output value="2 >= 3" />');
        });

        it("should escape attribute value with < tight", function () {
            eq('<output value="2<3" />', '<output value="2<3" />', false);
        });

        it("should escape attribute value with > tight", function () {
            eq('<output value="2>3" />', '<output value="2>3" />', false);
        });

        it("should escape < character", function () {
            eq('your visit count must be < 5', 'your visit count must be < 5');
        });

        it("should escape > character", function () {
            eq('your visit count must be > 5', 'your visit count must be > 5');
        });

        it("should escape attribute value with > and trailing text (v1)", function () {
            eq('<output value="2 > 3" /> text',
               '<output value="2 > 3" /> text');
        });

        it("should escape attribute value with > and trailing text (v2)", function () {
            eq("<output value='2 > 3' /> text",
               '<output value="2 > 3" /> text', false);
        });

        it("should escape attribute value with > and trailing text (v3)", function () {
            eq('<output value="2 > 3"/>text',
               '<output value="2 > 3" />text', false);
        });

        it("should escape attribute value with > and trailing text (v4)", function () {
            eq('<output value="2 > 3"></output> text',
               '<output value="2 > 3" /> text', false);
        });

        it("should escape attribute value with > and trailing text (v5)", function () {
            eq('<out:p_3-6. h:v-1._="2 > 3" />text',
               '<out:p_3-6. h:v-1._="2 > 3" />text');
        });

        it("should escape attribute value with < and trailing text", function () {
            eq('<output value="2 < 3" /> text',
               '<output value="2 < 3" /> text');
        });

        it("should escape < character before tag", function () {
            // immortalizing https://github.com/dimagi/Vellum/pull/212
            eq('your visit count must be < <output value="/path" />',
               'your visit count must be < <output value="/path" />');
        });

        it("should escape > character before tag", function () {
            eq('your visit count must be > <output value="/path" />',
               'your visit count must be > <output value="/path" />');
        });

        it("should preserve attribute value with path", function () {
            eq('<output value="/path" />', '<output value="/path" />');
        });

        it("should preserve output node with text before", function () {
            eq('text <output value="/path" />', 'text <output value="/path" />');
        });

        it("should preserve output node with text after", function () {
            eq('<output value="/path" /> text', '<output value="/path" /> text');
        });

        it("should preserve attribute without value", function () {
            eq('<output attr />', '<output attr="" />', false);
        });

        it("should preserve attribute value with path, unterminated tag", function () {
            eq('<output value="/path">', '<output value="/path" />', false);
        });

        it("should preserve newline", function () {
            eq('line 1\nline2', 'line 1\nline2');
        });

        it("should preserve newline in attribute value", function () {
            eq('<output value="selected(/path,\n2)" />',
               '<output value="selected(/path,\n2)" />');
        });

        it("should preserve attribute with unquoted value", function () {
            eq('<output attr=value />', '<output attr="value" />', false);
        });

        it("should accept jquery node", function () {
            eq($('<value><output /></value>'), '<output />', false);
        });

        /* -- The following are lossy -- */

        it("should handle malformed tag", function () {
            // big loss here
            eq('<output value="/path"', '', false);
        });

        it("should replace   with space", function () {
            eq('a   b', 'a   b', false);
        });
    });

    describe("The XML humanizer", function () {
        function eq(value, humanized, normalize) {
            var human = xml.humanize(value);
            assert.strictEqual(human, humanized);
            if (normalize) {
                assert.strictEqual(xml.normalize(human), value);
            }
        }

        it("should convert free < character", function () {
            eq('2 < 3', '2 < 3', true);
        });

        it("should convert free > character", function () {
            eq('2 > 3', '2 > 3', true);
        });

        it("should convert free & character", function () {
            eq('2 & 3', '2 & 3', true);
        });

        it("should not convert escaped tag", function () {
            eq(' <div> ', ' <div> ', true);
        });

        it("should convert output tag", function () {
            eq('<output value="1 & 2 < 3" />',
               '<output value="1 & 2 < 3" />', true);
        });

        it("should convert child nodes", function () {
            var value = "<value>1 & 2 < 3 <output value='/path' /></value>";
            eq($(value), '1 & 2 < 3 <output value="/path" />');
        });

        it("should convert child output node", function () {
            var value = "<value><output value='1 & 2 < 3' /></value>";
            eq($(value), '<output value="1 & 2 < 3" />');
        });

        it("should convert empty child nodes", function () {
            eq($("<value />"), "");
        });

        it("should space child node", function () {
            eq($("<value> </value>"), " ");
        });

        it("should newline child node", function () {
            eq($("<value>\n</value>"), "\n");
        });
    });
});
<|endoftext|>


<commit_before>/**
 * module.exports ... is required for things to work
 * @param {Object} grunt instance of Grunt.
 */
module.exports = function (grunt) {
  'use strict';

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-requirejs');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-jslint');
  grunt.loadNpmTasks('grunt-mocha');

  grunt.initConfig({

    clean: {
      build: {
        src: ['BlinkForms*.js*', 'js/*.min.js']
      }
    },

    jslint: {
      files: [
        '**/*.js'
      ],
      exclude: [
        'node_modules/**',
        'js/lib/**',
        'js/build/**',
        'BlinkForms*.js'
      ],
      directives: {
        browser: true,
        es5: true,
        indent: 2,
        nomen: true,
        todo: true, // TODO: eventually drop this
        sloppy: true, // we force strict-mode separately
        predef: [
          // pre-defined globals
          'module',
          'define',
          'require',
          // globals we assume have been loaded
          '$',
          '_',
          'Backbone',
          'rivets',
          'BMP',
          'Q'
        ]
      },
      options: {
        errorsOnly: true,
        failOnError: true
      }
    },

    requirejs: {
      compile: {
        options: {
          baseUrl: 'js',
          dir: 'js/build',
//          optimize: 'uglify2',
          optimize: 'none',
          uglify: {
            max_line_length: 80
          },
          uglify2: {
            output: {
              max_line_len: 80
            },
            warnings: false
          },
          paths: {
            backbone: 'empty:',
            jquery: 'empty:',
            jquerymobile: 'empty:',
            q: 'empty:',
            underscore: 'empty:',
            rivets: 'empty:',
            // testing libraries
            chai: 'empty:',
            mocha: 'empty:'
          },
          modules: [
            {
              name: 'main'
            },
            {
              name: 'views/jqm',
              exclude: ['main']
            }
          ]
        }
      }
    },

    concat: {
      core: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.4.js',
          'js/build/main.js',
          'parts/99-end-core.frag'
        ],
        dest: 'BlinkForms.js'
      },
      jqm: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.5.js',
          'js/build/main.js',
          'js/build/views/jqm.js',
          'parts/99-end-jqm.frag'
        ],
        dest: 'BlinkForms-jQueryMobile.js'
      }
    },

    uglify: {
      default: {
        options: {
          sourceMap: 'BlinkForms-jQueryMobile.js.map',
          beautify: {
            width: 80,
            max_line_len: 80
          }
        },
        files: {
          'BlinkForms-jQueryMobile.min.js': ['BlinkForms-jQueryMobile.js']
        }
      }

    },

    mocha: {
      all: {
        src: ['test/*/index.html'],
        options: {
          run: false
        }
      }
    },

    watch: {
      src: {
        files: [
          'Gruntfile.js',
          'js/**/*.js',
          'parts/*',
          'test/**/*'
        ],
        tasks: 'default'
      },
      options: {
        interrupt: true
      }
    }

  });

  grunt.registerTask('default', ['test']);

  grunt.registerTask('build', [
    'clean',
    'requirejs',
    'concat',
    'uglify'
  ]);

  grunt.registerTask('test', [
    'jslint',
    'build',
    'mocha'
  ]);

};

<commit_msg>split `grunt watch` into separate jobs for efficiency<commit_after>/**
 * module.exports ... is required for things to work
 * @param {Object} grunt instance of Grunt.
 */
module.exports = function (grunt) {
  'use strict';

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-requirejs');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-jslint');
  grunt.loadNpmTasks('grunt-mocha');

  grunt.initConfig({

    clean: {
      build: {
        src: ['BlinkForms*.js*', 'js/*.min.js']
      }
    },

    jslint: {
      files: [
        '**/*.js'
      ],
      exclude: [
        'node_modules/**',
        'js/lib/**',
        'js/build/**',
        'BlinkForms*.js'
      ],
      directives: {
        browser: true,
        es5: true,
        indent: 2,
        nomen: true,
        todo: true, // TODO: eventually drop this
        sloppy: true, // we force strict-mode separately
        predef: [
          // pre-defined globals
          'module',
          'define',
          'require',
          // globals we assume have been loaded
          '$',
          '_',
          'Backbone',
          'rivets',
          'BMP',
          'Q'
        ]
      },
      options: {
        errorsOnly: true,
        failOnError: true
      }
    },

    requirejs: {
      compile: {
        options: {
          baseUrl: 'js',
          dir: 'js/build',
//          optimize: 'uglify2',
          optimize: 'none',
          uglify: {
            max_line_length: 80
          },
          uglify2: {
            output: {
              max_line_len: 80
            },
            warnings: false
          },
          paths: {
            backbone: 'empty:',
            jquery: 'empty:',
            jquerymobile: 'empty:',
            q: 'empty:',
            underscore: 'empty:',
            rivets: 'empty:',
            // testing libraries
            chai: 'empty:',
            mocha: 'empty:'
          },
          modules: [
            {
              name: 'main'
            },
            {
              name: 'views/jqm',
              exclude: ['main']
            }
          ]
        }
      }
    },

    concat: {
      core: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.4.js',
          'js/build/main.js',
          'parts/99-end-core.frag'
        ],
        dest: 'BlinkForms.js'
      },
      jqm: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.5.js',
          'js/build/main.js',
          'js/build/views/jqm.js',
          'parts/99-end-jqm.frag'
        ],
        dest: 'BlinkForms-jQueryMobile.js'
      }
    },

    uglify: {
      default: {
        options: {
          sourceMap: 'BlinkForms-jQueryMobile.js.map',
          beautify: {
            width: 80,
            max_line_len: 80
          }
        },
        files: {
          'BlinkForms-jQueryMobile.min.js': ['BlinkForms-jQueryMobile.js']
        }
      }

    },

    mocha: {
      all: {
        src: ['test/*/index.html'],
        options: {
          run: false
        }
      }
    },

    watch: {
      src: {
        files: [
          'Gruntfile.js',
          'js/**/*.js',
          '!js/build/**/*.js',
          'parts/*'
        ],
        tasks: 'default'
      },
      tests: {
        files: [
          'test/**/*'
        ],
        tasks: ['jslint', 'mocha']
      },
      options: {
        interrupt: true
      }
    }

  });

  grunt.registerTask('default', ['test']);

  grunt.registerTask('build', [
    'clean',
    'requirejs',
    'concat',
    'uglify'
  ]);

  grunt.registerTask('test', [
    'jslint',
    'build',
    'mocha'
  ]);

};

<|endoftext|>


<commit_before>/**
 * Created by prabhaths on 3/11/16.
 */

/**
 * Data related communication handler
 */
var DataManager = {

  getDataSet : function (opt, cb) {
    ajaxPostModel( "/data/getDataSet", opt, cb);
  },

  createConnection : function (opt, cb) {
    ajaxPostModel( "/data/createConnection", opt, cb);
  },

  getConnections : function (opt, cb) {
    ajaxPostModel( "/data/getConnections", opt, cb);
  },

  removeConnection : function (opt, cb) {
    ajaxPostModel( "/data/removeConnection", opt, cb);
  },

  getCollections : function (opt, cb) {
    ajaxPostModel( "/data/getCollections", opt, cb);
  }

};

var ajaxReq = null;
var prevReq = null;
var ajaxCall = function(ajaxParam, successFunction, errorFunction, completeFunction) {

  var ajaxBody = {};

  _.each(ajaxParam, function(value, key){
    ajaxBody[key] = value;
  });

  ajaxBody['beforeSend'] = function(jqXHR, jqReq){
    //abort previous request if its for the same target
    if(prevReq && prevReq.url === jqReq.url && prevReq.type === jqReq.type){
      if(ajaxReq != null){
        ajaxReq.abort();
      }
    }

    prevReq = jqReq
  };

  ajaxBody['success'] = successFunction;

  ajaxBody['error'] = function(jqXHR, status, errorThrown) {

    if(jqXHR.status == "401") {
      window.location.href = "/";
    }

    if(typeof errorFunction == "function") {
      errorFunction(jqXHR, status, errorThrown);
    }
  };

  ajaxBody['complete'] = function(jqXHR, status) {
    if(typeof completeFunction == "function") {
      completeFunction(jqXHR, status);
    }
  };

  ajaxReq = jQuery.ajax(ajaxBody);
};

var ajaxPostModel = function (url,data,cb) {
  ajaxCall({
      url: url,
      type: "POST",
      dataType: 'json',
      data: data,
      timeout: 60000
    },
    function(resp) {
      cb(resp);
    });
};

<commit_msg>changed ajax call handling..<commit_after>/**
 * Created by prabhaths on 3/11/16.
 */

/**
 * Data related communication handler
 */
var DataManager = {

  getDataSet : function (opt, cb) {
    ajaxPostModel([
        1, opt.collection
    ],"/data/getDataSet", opt, cb);
  },

  createConnection : function (opt, cb) {
    ajaxPostModel([
      2, opt.dbDetails.serverAddress
    ],"/data/createConnection", opt, cb);
  },

  getConnections : function (opt, cb) {
    ajaxPostModel([
      3
    ],"/data/getConnections", opt, cb);
  },

  removeConnection : function (opt, cb) {
    ajaxPostModel([
      4, opt.dbTag
    ],"/data/removeConnection", opt, cb);
  },

  getCollections : function (opt, cb) {
    ajaxPostModel([
      5, opt.connection
    ],"/data/getCollections", opt, cb);
  }

};

//
//var ajaxReq = null;
//var prevReq = null;
//var ajaxCall = function(ajaxParam, successFunction, errorFunction, completeFunction) {
//
//  var ajaxBody = {};
//
//  _.each(ajaxParam, function(value, key){
//    ajaxBody[key] = value;
//  });
//
//  ajaxBody['beforeSend'] = function(jqXHR, jqReq){
//    //abort previous request if its for the same target
//    if(prevReq && prevReq.url === jqReq.url && prevReq.type === jqReq.type){
//      if(ajaxReq != null){
//        ajaxReq.abort();
//      }
//    }
//
//    prevReq = jqReq
//  };
//
//  ajaxBody['success'] = successFunction;
//
//  ajaxBody['error'] = function(jqXHR, status, errorThrown) {
//
//    if(jqXHR.status == "401") {
//      window.location.href = "/";
//    }
//
//    if(typeof errorFunction == "function") {
//      errorFunction(jqXHR, status, errorThrown);
//    }
//  };
//
//  ajaxBody['complete'] = function(jqXHR, status) {
//    if(typeof completeFunction == "function") {
//      completeFunction(jqXHR, status);
//    }
//  };
//
//  ajaxReq = jQuery.ajax(ajaxBody);
//};
//
//
//var ajaxPostModel = function (url,data,cb) {
//  ajaxCall({
//      url: url,
//      type: "POST",
//      dataType: 'json',
//      data: data,
//      timeout: 60000
//    },
//    function(resp) {
//      cb(resp);
//    });
//};

var ajaxCallStack = {};
var ajaxPostModel = function (idFields, url,data,cb) {
  //make ajax call type id
  var callId = idFields.join('_x_');
  if(!ajaxCallStack[callId]){
    ajaxCallStack[callId]=1;
    $.ajax({
      url: url,
      type: "POST",
      dataType: 'json',
      data: data,
      success: function (resp) {
        cb(resp);
        delete ajaxCallStack[callId];
      },
      error: function (status, err) {
        cb({
          error: status,
          errorContent: err
        });
        delete ajaxCallStack[callId];
      }
    }).fail(function (jqXHR, textStatus, errorThrown) {
      MESSAGE_BOT.addMessage('Communication Error', MESSAGE_BOT.MESSAGE_TYPES.ERROR);
    });
  } else {
    cb({
      error: 'repeatCalls to'+url,
      errorContent: 'One request already pending..',
      ajaxBlocked: true
    });
  }

};

<|endoftext|>


<commit_before>define(['configs', 'lodash', 'router', 'array-utilities', 'string-utilities'],
    function(configs, _, router, arrayUtilities, stringUtitilies) {
        'use strict';

        var UrlUtilities = function UrlUtilities() {};

        UrlUtilities.prototype.url = function(url, baseUrl) {
            var baseUrl = (baseUrl || configs.baseUrl).replace(/^\/|\/$/g, '');
            url = url.replace(/^\//g, '');

            return '/' + baseUrl + '/' + url;
        };

        UrlUtilities.prototype.patternWithQueryString = function(url) {
            return this.url(url) + ':?queryString:';
        };

        UrlUtilities.prototype.getReturnUrl = function(_default) {
            var self = this;

            var context = router.context();
            var result =  self.url(_default);

            if (context && context.route.query.returnTo) {
                result = context.route.query.returnTo;
            }

            return result || '';
        };

        UrlUtilities.prototype.joinUrlParts = function () {
            var result = '';
            var urlParts = Array.prototype.slice.call(arguments, 0);

            if (arrayUtilities.isNotEmptyArray(urlParts)) {
                for (var i = 0; i < urlParts.length; i++) {
                    var urlPart = urlParts[i];
                    var isLast = i == (urlParts.length - 1);

                    if (urlPart && !isLast) {
                        urlPart = stringUtitilies.trimRight(urlPart, '/');
                        urlPart += '/';
                    }

                    result += urlPart;
                }
            }

            return result;
        };

        return new UrlUtilities();
    });
<commit_msg>licensing<commit_after>// Copyright (c) CBC/Radio-Canada. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

define(['configs', 'lodash', 'router', 'array-utilities', 'string-utilities'],
    function(configs, _, router, arrayUtilities, stringUtitilies) {
        'use strict';

        var UrlUtilities = function UrlUtilities() {};

        UrlUtilities.prototype.url = function(url, baseUrl) {
            var baseUrl = (baseUrl || configs.baseUrl).replace(/^\/|\/$/g, '');
            url = url.replace(/^\//g, '');

            return '/' + baseUrl + '/' + url;
        };

        UrlUtilities.prototype.patternWithQueryString = function(url) {
            return this.url(url) + ':?queryString:';
        };

        UrlUtilities.prototype.getReturnUrl = function(_default) {
            var self = this;

            var context = router.context();
            var result =  self.url(_default);

            if (context && context.route.query.returnTo) {
                result = context.route.query.returnTo;
            }

            return result || '';
        };

        UrlUtilities.prototype.joinUrlParts = function () {
            var result = '';
            var urlParts = Array.prototype.slice.call(arguments, 0);

            if (arrayUtilities.isNotEmptyArray(urlParts)) {
                for (var i = 0; i < urlParts.length; i++) {
                    var urlPart = urlParts[i];
                    var isLast = i == (urlParts.length - 1);

                    if (urlPart && !isLast) {
                        urlPart = stringUtitilies.trimRight(urlPart, '/');
                        urlPart += '/';
                    }

                    result += urlPart;
                }
            }

            return result;
        };

        return new UrlUtilities();
    });
<|endoftext|>


<commit_before>$(document).ready(function(){
  init_madness();
});

function init_madness(){
  var canvas = document.getElementById("mad"),
  ctx = canvas.getContext("2d"),
  letters = [],
  phrases = ["Login", "March Madness", "Rock", "Paper", "Scissor"];

  for(var i = 97; i < 122; i++){
    letters.push(String.fromCharCode(i));
  }

  if(Math.floor(Math.random(0, 2)) == 1){
    use_phrases();
  }else{
    use_letters();
  }
  function use_phrases(){

  }

  function use_letters(){

  }
}
<commit_msg>Update thisismadness.js<commit_after>$(document).ready(function(){
  init_madness();
});

function init_madness(){
  var canvas = document.getElementById("mad"),
  ctx = canvas.getContext("2d"),
  letters = [],
  phrases = ["Login", "March Madness", "Rock", "Paper", "Scissor"];

  for(var i = 97; i < 122; i++){
    letters.push(String.fromCharCode(i));
  }

  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  if(Math.floor(Math.random(0, 2)) == 1){
    use_phrases();
  }else{
    use_letters();
  }

  function use_phrases(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }

  function use_letters(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }
}
<|endoftext|>


<commit_before>/*jshint node:true */
module.exports = function( grunt ) {

require( "load-grunt-tasks" )( grunt );

function process( code, filepath ) {

	// Make coverage ignore external files
	if ( filepath.match( /^external\// ) ) {
		code = "/*istanbul ignore next */\n" + code;
	}

	return code

		// Embed version
		.replace( /@VERSION/g, grunt.config( "pkg" ).version )

		// Embed date (yyyy-mm-ddThh:mmZ)
		.replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
}

grunt.initConfig({
	pkg: grunt.file.readJSON( "package.json" ),
	concat: {
		"src-js": {
			options: { process: process },
			src: [
				"src/intro.js",
				"src/core/initialize.js",
				"src/core/utilities.js",
				"src/core/stacktrace.js",
				"src/core/config.js",
				"src/core/logging.js",
				"src/core/onerror.js",
				"src/core.js",
				"src/test.js",
				"src/assert.js",
				"src/equiv.js",
				"src/dump.js",
				"src/export.js",
				"src/outro.js",
				"src/diff.js",
				"reporter/html.js"
			],
			dest: "dist/qunit.js"
		},
		"src-css": {
			options: { process: process },
			src: "src/qunit.css",
			dest: "dist/qunit.css"
		}
	},
	jshint: {
		options: {
			jshintrc: true
		},
		all: [
			"*.js",
			"{test,dist}/**/*.js",
			"build/*.js"
		]
	},
	jscs: {
		options: {
			config: ".jscsrc"
		},
		all: [
			"<%= jshint.all %>",
			"!test/main/deepEqual.js"
		]
	},
	search: {
		options: {

			// Ensure that the only HTML entities used are those with a special status in XHTML
			// and that any common singleton/empty HTML elements end with the XHTML-compliant
			// "/>"rather than ">"
			searchString: /(&(?!gt|lt|amp|quot)[A-Za-z0-9]+;|<(?:hr|HR|br|BR|input|INPUT)(?![^>]*\/>)(?:\s+[^>]*)?>)/g,
			logFormat: "console",
			failOnMatch: true
		},
		xhtml: [
			"src/**/*.js",
			"reporter/**/*.js"
		]
	},
	qunit: {
		options: {
			timeout: 30000,
			"--web-security": "no",
			coverage: {
				src: "dist/qunit.js",
				instrumentedFiles: "temp/",
				htmlReport: "build/report/coverage",
				lcovReport: "build/report/lcov",
				linesThresholdPct: 70
			}
		},
		qunit: [
			"test/index.html",
			"test/autostart.html",
			"test/startError.html",
			"test/logs.html",
			"test/setTimeout.html",
			"test/amd.html",
			"test/reporter-html/index.html",
			"test/reporter-html/legacy-markup.html",
			"test/reporter-html/no-qunit-element.html"
		]
	},
	coveralls: {
		options: {
			force: true
		},
		all: {

			// LCOV coverage file relevant to every target
			src: "build/report/lcov/lcov.info"
		}
	},
	watch: {
		options: {
			atBegin: true
		},
		files: [
			".jshintrc",
			"*.js",
			"build/*.js",
			"{src,test,reporter}/**/*.js",
			"src/qunit.css",
			"test/**/*.html"
		],
		tasks: "default"
	}
});

// TODO: Extract this task later, if feasible
// Also spawn a separate process to keep tests atomic
grunt.registerTask( "test-on-node", function() {
	var testActive = false,
		runDone = false,
		done = this.async(),
		QUnit = require( "./dist/qunit" );

	global.QUnit = QUnit;

	QUnit.testStart(function() {
		testActive = true;
	});
	QUnit.log(function( details ) {
		if ( !testActive || details.result ) {
			return;
		}
		var message = "name: " + details.name + " module: " + details.module +
			" message: " + details.message;
		grunt.log.error( message );
	});
	QUnit.testDone(function() {
		testActive = false;
	});
	QUnit.done(function( details ) {
		if ( runDone ) {
			return;
		}
		var succeeded = ( details.failed === 0 ),
			message = details.total + " assertions in (" + details.runtime + "ms), passed: " +
				details.passed + ", failed: " + details.failed;
		if ( succeeded ) {
			grunt.log.ok( message );
		} else {
			grunt.log.error( message );
		}
		done( succeeded );
		runDone = true;
	});
	QUnit.config.autorun = false;

	require( "./test/logs" );
	require( "./test/main/test" );
	require( "./test/main/assert" );
	require( "./test/main/async" );
	require( "./test/main/promise" );
	require( "./test/main/modules" );
	require( "./test/main/deepEqual" );
	require( "./test/main/stack" );
	require( "./test/globals-node" );

	QUnit.load();
});

grunt.registerTask( "build", [ "concat" ] );
grunt.registerTask( "default", [ "build", "jshint", "jscs", "search", "qunit", "test-on-node" ] );

};
<commit_msg>Build: Move QUnit.diff inside intro/outro<commit_after>/*jshint node:true */
module.exports = function( grunt ) {

require( "load-grunt-tasks" )( grunt );

function process( code, filepath ) {

	// Make coverage ignore external files
	if ( filepath.match( /^external\// ) ) {
		code = "/*istanbul ignore next */\n" + code;
	}

	return code

		// Embed version
		.replace( /@VERSION/g, grunt.config( "pkg" ).version )

		// Embed date (yyyy-mm-ddThh:mmZ)
		.replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
}

grunt.initConfig({
	pkg: grunt.file.readJSON( "package.json" ),
	concat: {
		"src-js": {
			options: { process: process },
			src: [
				"src/intro.js",
				"src/core/initialize.js",
				"src/core/utilities.js",
				"src/core/stacktrace.js",
				"src/core/config.js",
				"src/core/logging.js",
				"src/core/onerror.js",
				"src/core.js",
				"src/test.js",
				"src/assert.js",
				"src/equiv.js",
				"src/dump.js",
				"src/export.js",
				"src/diff.js",
				"src/outro.js",
				"reporter/html.js"
			],
			dest: "dist/qunit.js"
		},
		"src-css": {
			options: { process: process },
			src: "src/qunit.css",
			dest: "dist/qunit.css"
		}
	},
	jshint: {
		options: {
			jshintrc: true
		},
		all: [
			"*.js",
			"{test,dist}/**/*.js",
			"build/*.js"
		]
	},
	jscs: {
		options: {
			config: ".jscsrc"
		},
		all: [
			"<%= jshint.all %>",
			"!test/main/deepEqual.js"
		]
	},
	search: {
		options: {

			// Ensure that the only HTML entities used are those with a special status in XHTML
			// and that any common singleton/empty HTML elements end with the XHTML-compliant
			// "/>"rather than ">"
			searchString: /(&(?!gt|lt|amp|quot)[A-Za-z0-9]+;|<(?:hr|HR|br|BR|input|INPUT)(?![^>]*\/>)(?:\s+[^>]*)?>)/g,
			logFormat: "console",
			failOnMatch: true
		},
		xhtml: [
			"src/**/*.js",
			"reporter/**/*.js"
		]
	},
	qunit: {
		options: {
			timeout: 30000,
			"--web-security": "no",
			coverage: {
				src: "dist/qunit.js",
				instrumentedFiles: "temp/",
				htmlReport: "build/report/coverage",
				lcovReport: "build/report/lcov",
				linesThresholdPct: 70
			}
		},
		qunit: [
			"test/index.html",
			"test/autostart.html",
			"test/startError.html",
			"test/logs.html",
			"test/setTimeout.html",
			"test/amd.html",
			"test/reporter-html/index.html",
			"test/reporter-html/legacy-markup.html",
			"test/reporter-html/no-qunit-element.html"
		]
	},
	coveralls: {
		options: {
			force: true
		},
		all: {

			// LCOV coverage file relevant to every target
			src: "build/report/lcov/lcov.info"
		}
	},
	watch: {
		options: {
			atBegin: true
		},
		files: [
			".jshintrc",
			"*.js",
			"build/*.js",
			"{src,test,reporter}/**/*.js",
			"src/qunit.css",
			"test/**/*.html"
		],
		tasks: "default"
	}
});

// TODO: Extract this task later, if feasible
// Also spawn a separate process to keep tests atomic
grunt.registerTask( "test-on-node", function() {
	var testActive = false,
		runDone = false,
		done = this.async(),
		QUnit = require( "./dist/qunit" );

	global.QUnit = QUnit;

	QUnit.testStart(function() {
		testActive = true;
	});
	QUnit.log(function( details ) {
		if ( !testActive || details.result ) {
			return;
		}
		var message = "name: " + details.name + " module: " + details.module +
			" message: " + details.message;
		grunt.log.error( message );
	});
	QUnit.testDone(function() {
		testActive = false;
	});
	QUnit.done(function( details ) {
		if ( runDone ) {
			return;
		}
		var succeeded = ( details.failed === 0 ),
			message = details.total + " assertions in (" + details.runtime + "ms), passed: " +
				details.passed + ", failed: " + details.failed;
		if ( succeeded ) {
			grunt.log.ok( message );
		} else {
			grunt.log.error( message );
		}
		done( succeeded );
		runDone = true;
	});
	QUnit.config.autorun = false;

	require( "./test/logs" );
	require( "./test/main/test" );
	require( "./test/main/assert" );
	require( "./test/main/async" );
	require( "./test/main/promise" );
	require( "./test/main/modules" );
	require( "./test/main/deepEqual" );
	require( "./test/main/stack" );
	require( "./test/globals-node" );

	QUnit.load();
});

grunt.registerTask( "build", [ "concat" ] );
grunt.registerTask( "default", [ "build", "jshint", "jscs", "search", "qunit", "test-on-node" ] );

};
<|endoftext|>


<commit_before>// this is the router that instructs the server 'what to do' depending on which endpoint
const express = require('express');
const placesController = require('../controller/placesController');
const placesRouter = express.Router();

placesRouter.get('/bank', (req, res) => {
  lat = req.query.lat || 37.7825177;
  long = req.query.long || -122.4106772;
  //take results of nearby search and get their place ides
  let idList = [];
  //in case we end up with another hundred
  let idList2 = [];
  let coordinates = [];
  //holder for response object
  let result = [];
  //to iterate over results
  let counter = 0;
  placesController.getRadarData('bank', lat, long)
  .then(data => {
    // console.log(data);
    data.results.forEach((place) => {
      idList.push(place.place_id);
      coordinates.push(place.geometry.location);
    });
    //can only use 25 destinations at a time for Google distance matrix
    let shortList = idList.splice(0, 99);
    console.log(idList.length);
    placesController.getDistanceData(shortList, lat, long)
    .then(data => {
      console.log(data.rows[0].distance);
      data.destination_addresses.forEach(place => {
        result.push({
          'name': place,
          'time': data.rows[0].elements[counter].duration.text,
          'location': coordinates[counter],
          'distance': data.rows[0].elements[counter].distance.text,
          'metric distance': data.rows[0].elements[counter].distance.value
        });
        counter++;
      });
      res.status(200).json(result);
    });
  })
  .catch(err => res.sendStatus(500));
});

//DEV Testing
// const testArray = ["ChIJCdSUgO-AhYARuk0zTH3lyvU","ChIJ82ZgT_GAhYAR4rJh5-mnw9I","ChIJnbTAq--AhYAReI41AUmRd1w","ChIJtfiHddh_j4ARAjjq3GVQZdI"];
//this places nearby search
placesRouter.get('/devTest1', (req, res) =>{
  // console.log('look for coordinates in here', req);
  placesController.getData('museum', 37.7825177, -122.4106772)
  .then(data => {
    console.log(data);
    res.status(200).json(data);
  })
  .catch(err => res.sendStatus(500));
});

//places in a radar radius
placesRouter.get('/devTest2', (req, res) =>{
  // console.log('look for coordinates in here', req);
  placesController.getRadarData('museum', 37.7825177, -122.4106772)
  .then(data => {
    console.log(data);
    res.status(200).json(data);
  })
  .catch(err => res.sendStatus(500));
});

//how to get to an array of places, maximum of 25 in array
placesRouter.get('/devTest3', (req, res) =>{
  // console.log('look for coordinates in here', req);
  placesController.getDistanceData(testArray, 37.7825177, -122.4106772)
  .then(data => {
    console.log(data);
    res.status(200).json(data);
  })
  .catch(err => res.sendStatus(500));
});

//404 all other routes
placesRouter.use('*', (req, res) => res.status(404).send());

module.exports = placesRouter;<commit_msg>places/health and places/transit<commit_after>// this is the router that instructs the server 'what to do' depending on which endpoint
const express = require('express');
const placesController = require('../controller/placesController');
const placesRouter = express.Router();
//transit
//health

placesRouter.get('/bank', (req, res) => {
  lat = req.query.lat || 37.7825177;
  long = req.query.long || -122.4106772;
  //take results of nearby search and get their place ides
  let idList = [];
  //in case we end up with another hundred
  let idList2 = [];
  let coordinates = [];
  //holder for response object
  let result = [];
  //to iterate over results
  let counter = 0;
  placesController.getRadarData('bank', lat, long)
  .then(data => {
    // console.log(data);
    data.results.forEach((place) => {
      idList.push(place.place_id);
      coordinates.push(place.geometry.location);
    });
    //can only use 25 destinations at a time for Google distance matrix
    let shortList = idList.splice(0, 99);
    console.log(idList.length);
    placesController.getDistanceData(shortList, lat, long)
    .then(data => {
      console.log(data.rows[0].distance);
      data.destination_addresses.forEach(place => {
        result.push({
          'name': place,
          'time': data.rows[0].elements[counter].duration.text,
          'location': coordinates[counter],
          'distance': data.rows[0].elements[counter].distance.text,
          'metric distance': data.rows[0].elements[counter].distance.value
        });
        counter++;
      });
      res.status(200).json(result);
    });
  })
  .catch(err => res.sendStatus(500));
});

placesRouter.get('/health', (req, res) => {
  lat = req.query.lat || 37.7825177;
  long = req.query.long || -122.4106772;
  //take results of nearby search and get their place ides
  let idList = [];
  //in case we end up with another hundred
  let idList2 = [];
  let coordinates = [];
  //holder for response object
  let result = [];
  //to iterate over results
  let counter = 0;
  placesController.getRadarData('health', lat, long)
  .then(data => {
    // console.log(data);
    data.results.forEach((place) => {
      idList.push(place.place_id);
      coordinates.push(place.geometry.location);
    });
    //can only use 25 destinations at a time for Google distance matrix
    let shortList = idList.splice(0, 99);
    console.log(idList.length);
    placesController.getDistanceData(shortList, lat, long)
    .then(data => {
      console.log(data.rows[0].distance);
      data.destination_addresses.forEach(place => {
        result.push({
          'name': place,
          'time': data.rows[0].elements[counter].duration.text,
          'location': coordinates[counter],
          'distance': data.rows[0].elements[counter].distance.text,
          'metric distance': data.rows[0].elements[counter].distance.value
        });
        counter++;
      });
      res.status(200).json(result);
    });
  })
  .catch(err => res.sendStatus(500));
});

placesRouter.get('/transit', (req, res) => {
  lat = req.query.lat || 37.7825177;
  long = req.query.long || -122.4106772;
  //take results of nearby search and get their place ides
  let idList = [];
  //in case we end up with another hundred
  let idList2 = [];
  let coordinates = [];
  //holder for response object
  let result = [];
  //to iterate over results
  let counter = 0;
  placesController.getRadarData('transit_station', lat, long)
  .then(data => {
    // console.log(data);
    data.results.forEach((place) => {
      idList.push(place.place_id);
      coordinates.push(place.geometry.location);
    });
    //can only use 25 destinations at a time for Google distance matrix
    let shortList = idList.splice(0, 99);
    console.log(idList.length);
    placesController.getDistanceData(shortList, lat, long)
    .then(data => {
      console.log(data.rows[0].distance);
      data.destination_addresses.forEach(place => {
        result.push({
          'name': place,
          'time': data.rows[0].elements[counter].duration.text,
          'location': coordinates[counter],
          'distance': data.rows[0].elements[counter].distance.text,
          'metric distance': data.rows[0].elements[counter].distance.value
        });
        counter++;
      });
      res.status(200).json(result);
    });
  })
  .catch(err => res.sendStatus(500));
});
//DEV Testing
// const testArray = ["ChIJCdSUgO-AhYARuk0zTH3lyvU","ChIJ82ZgT_GAhYAR4rJh5-mnw9I","ChIJnbTAq--AhYAReI41AUmRd1w","ChIJtfiHddh_j4ARAjjq3GVQZdI"];
//this places nearby search
placesRouter.get('/devTest1', (req, res) =>{
  // console.log('look for coordinates in here', req);
  placesController.getData('museum', 37.7825177, -122.4106772)
  .then(data => {
    console.log(data);
    res.status(200).json(data);
  })
  .catch(err => res.sendStatus(500));
});

//places in a radar radius
placesRouter.get('/devTest2', (req, res) =>{
  // console.log('look for coordinates in here', req);
  placesController.getRadarData('museum', 37.7825177, -122.4106772)
  .then(data => {
    console.log(data);
    res.status(200).json(data);
  })
  .catch(err => res.sendStatus(500));
});

//how to get to an array of places, maximum of 25 in array
placesRouter.get('/devTest3', (req, res) =>{
  // console.log('look for coordinates in here', req);
  placesController.getDistanceData(testArray, 37.7825177, -122.4106772)
  .then(data => {
    console.log(data);
    res.status(200).json(data);
  })
  .catch(err => res.sendStatus(500));
});

//404 all other routes
placesRouter.use('*', (req, res) => res.status(404).send());

module.exports = placesRouter;<|endoftext|>


<commit_before>import React, {Component} from 'react'
import PropTypes from 'prop-types'

import server from 'views/helpers/network.js'
import ExpenseCard from 'views/components/ExpenseCard'
import { colorMoney, hashHSL } from 'views/helpers/colors.js'

import {
  Bar as BarChart,
  Doughnut as DoughnutChart
} from 'react-chartjs-2'
import moment from 'moment'
import { map } from 'lodash'

import Card from 'material-ui/Card'
import Chip from 'material-ui/Chip'
import IconButton from 'material-ui/IconButton'
import FontIcon from 'material-ui/FontIcon'

/**
 * Helper function used like `reduce(toCategory, {})` on `expenses`
 * @param {Object} categories
 * @param {Object} x
 */
function toCategory (categories, x) {
  const key = x.category || 'Other'
  if (!categories[key]) {
    categories[key] = 0
  }
  categories[key] += x.amount
  return categories
}

/**
 * Helper function used like `reduce(toAccount, {})` on `expenses`
 * @param {Object} accounts
 * @param {Object} x
 */
function toAccount (accounts, x) {
  const key = x.account
  if (!accounts[key]) {
    accounts[key] = 0
  }
  accounts[key] += x.amount
  return accounts
}

class ExpensesPage extends Component {
  state = {
    // overall
    expenses: [],
    accountBalances: {},
    // weekly data
    weekly: {
      start: moment().startOf('week'),
      labels: [0, 0, 0, 0, 0, 0, 0],
      totalIncome: 0,
      totalExpenses: 0,
      // bar chart
      income: [0, 0, 0, 0, 0, 0, 0],
      expenses: [0, 0, 0, 0, 0, 0, 0]
    },
    // monthly data
    monthly: {
      start: moment().startOf('month'),
      totalIncome: 0,
      totalExpenses: 0,
      // pie chart
      income: {},
      expenses: {}
    }
  }

  componentDidMount () {
    const byTimestampDesc = (x1, x2) => {
      return x2.timestamp - x1.timestamp
    }
    server.getExpenses(0, Date.now())
      .then(expenses => {
        const newExpenses = this.state.expenses
          .concat(expenses.map(expense => ({
            type: 'expense',
            ...expense
          })))
          .sort(byTimestampDesc)
        this.setState({
          expenses: newExpenses
        }, this.analyzeExpenses)
      })
    server.getIncome(0, Date.now())
      .then(income => {
        const newExpenses = this.state.expenses
          .concat(income.map(income => ({
            type: 'income',
            ...income
          })))
          .sort(byTimestampDesc)
        this.setState({
          expenses: newExpenses
        }, this.analyzeExpenses)
      })
  }

  analyzeExpenses () {
    const accountBalances = this.state.expenses
      .map(x => ({ ...x, amount: Math.round(x.amount) }))
      .reduce(toAccount, {})
    const weeklyChart = this.Weekly.computeForChart(this.state.weekly.start)
    const monthlyChart = this.Monthly.computeForChart(this.state.monthly.start)

    this.setState(prev => ({
      accountBalances,
      weekly: {
        ...prev.weekly,
        ...weeklyChart
      },
      monthly: {
        ...prev.monthly,
        ...monthlyChart
      }
    }))
  }

  handleEditExpense = (id, changes) => {
    const byTimestampDesc = (x1, x2) => {
      return x2.timestamp - x1.timestamp
    }
    this.setState(prev => ({
      expenses: prev.expenses
        .map(x => {
          if (x.id === id) {
            x = {
              ...x,
              description: changes.description,
              timestamp: changes.timestamp,
              category: changes.category
            }
          }
          return x
        })
        .sort(byTimestampDesc)
    }), this.analyzeExpenses)
  }

  handleDeleteExpense = id => {
    this.setState(prev => ({
      expenses: prev.expenses
        .filter(x => x.id !== id)
    }), this.analyzeExpenses)
  }

  Weekly = {
    handlePrev: event => {
      const start = moment(this.state.weekly.start)
        .add(-1, 'd')
        .startOf('week')
      this.setState({
        weekly: {
          start,
          ...this.Weekly.computeForChart(start)
        }
      })
    },

    handleNext: event => {
      const start = moment(this.state.weekly.start)
        .endOf('week')
        .add(1, 'd')
        .startOf('week')
      this.setState({
        weekly: {
          start,
          ...this.Weekly.computeForChart(start)
        }
      })
    },

    computeForChart: start => {
      const weekStart = start.unix()
      const weekEnd = moment(weekStart * 1000).endOf('week').unix()

      const day = 24 * 60 * 60
      const toWeekDays = (days, x) => {
        const i = Math.floor((x.timestamp - weekStart) / day)
        days[i] += x.amount
        return days
      }

      const weeklyIncome = this.state.expenses
        .filter(x => x.timestamp >= weekStart && x.timestamp <= weekEnd)
        .filter(x => x.amount > 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))
        .reduce(toWeekDays, [0, 0, 0, 0, 0, 0, 0])

      const totalWeeklyIncome = weeklyIncome
        .reduce((s, x) => s + x, 0)

      const weeklyExpenses = this.state.expenses
        .filter(x => x.timestamp >= weekStart && x.timestamp <= weekEnd)
        .filter(x => x.amount < 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))
        .reduce(toWeekDays, [0, 0, 0, 0, 0, 0, 0])

      const totalWeeklyExpenses = weeklyExpenses
        .reduce((s, x) => s + x, 0)

      const weeklyLabels = [0, 0, 0, 0, 0, 0, 0]
        .map((x, i) => {
          const t = moment(weekStart * 1000)
            .startOf('week')
            .add(i, 'd')
          return t
            .isSame(moment(), 'day')
            ? 'Today'
            : t.format('MMM D')
        })

      return {
        labels: weeklyLabels,
        income: weeklyIncome,
        expenses: weeklyExpenses,
        totalIncome: totalWeeklyIncome,
        totalExpenses: totalWeeklyExpenses
      }
    }
  }

  Monthly = {
    handlePrev: event => {
      const start = moment(this.state.monthly.start)
        .add(-1, 'd')
        .startOf('month')
      this.setState({
        monthly: {
          start,
          ...this.Monthly.computeForChart(start)
        }
      })
    },

    handleNext: event => {
      const start = moment(this.state.monthly.start)
        .endOf('month')
        .add(1, 'd')
      this.setState({
        monthly: {
          start,
          ...this.Monthly.computeForChart(start)
        }
      })
    },

    computeForChart: start => {
      const monthStart = start.unix()
      const monthEnd = moment(monthStart * 1000).endOf('month').unix()

      let monthlyIncome = this.state.expenses
        .filter(x => x.timestamp >= monthStart && x.timestamp <= monthEnd)
        .filter(x => x.amount > 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))

      const totalMonthlyIncome = monthlyIncome
        .reduce((s, x) => s + x.amount, 0)

      monthlyIncome = monthlyIncome
        .reduce(toCategory, {})

      let monthlyExpenses = this.state.expenses
        .filter(x => x.timestamp >= monthStart && x.timestamp <= monthEnd)
        .filter(x => x.amount < 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))

      const totalMonthlyExpenses = monthlyExpenses
        .reduce((s, x) => s + x.amount, 0)

      monthlyExpenses = monthlyExpenses
        .map(x => ({ ...x, amount: Math.abs(x.amount) }))
        .reduce(toCategory, {})

      return {
        income: monthlyIncome,
        expenses: monthlyExpenses,
        totalIncome: totalMonthlyIncome,
        totalExpenses: totalMonthlyExpenses
      }
    }
  }

  render () {
    const isEmpty = Object.keys(this.state.accountBalances).length
    let prevDay
    return (

        {!isEmpty && (
          <Card style={{ margin: '10px 0', padding: '16px' }}>

Hello Omar!

You do not have any expenses logged into the database yet.

          </Card>
        )}
        {isEmpty && (

            {/* Total */}
            <Card style={{ margin: '10px 0', padding: '16px' }}>

                {map(this.state.accountBalances, (balance, account) =>
                  <MoneySum
                    key={account}
                    title="Balance"
                    subtitle={'(' + account.substring(account.length - 4, account.length) + ')'}
                    value={balance}
                    currency="CAD"
                  />)}

            </Card>
            {/* Weekly */}
            <Card style={{ margin: '10px 0', padding: '16px' }}>

                {this.state.weekly.start
                  .format('MMM D') +
                  ' - ' +
                moment(this.state.weekly.start)
                  .endOf('week')
                  .format('MMM D')}

                {/* Previous Week */}

                  <IconButton
                    tooltip="Previous week"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Weekly.handlePrev}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_left
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.weekly.start)
                      .add(-7, 'd')
                      .format('MMM D')}

                <MoneySum
                  title="Income"
                  subtitle="(week)"
                  value={this.state.weekly.totalIncome}
                  currency="CAD"
                />
                <MoneySum
                  title="Expenses"
                  subtitle="(week)"
                  value={this.state.weekly.totalExpenses}
                  currency="CAD"
                />

                  {/* Next Week */}
                  <IconButton
                    tooltip="Next week"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Weekly.handleNext}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_right
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.weekly.start)
                      .add(7, 'd')
                      .format('MMM D')}

              {(this.state.weekly.totalIncome - this.state.weekly.totalExpenses > 0) &&
                <BarChart
                  data={{
                    labels: this.state.weekly.labels,
                    datasets: [
                      {
                        backgroundColor: colorMoney(1),
                        label: 'Income',
                        data: this.state.weekly.income
                      },
                      {
                        backgroundColor: colorMoney(-1),
                        label: 'Expense',
                        data: this.state.weekly.expenses.map(Math.abs)
                      }
                    ]
                  }}
                  options={{
                    scales: {
                      xAxes: [{
                        stacked: true
                      }],
                      yAxes: [{
                        stacked: true
                      }]
                    }
                  }}
                />}
            </Card>
            {/* Monthly */}
            <Card style={{ margin: '10px 0', padding: '16px' }}>

                {this.state.monthly.start
                  .format('MMM D') +
                  ' - ' +
                moment(this.state.monthly.start)
                  .endOf('month')
                  .format('MMM D')}

                  {/* Previous Month */}
                  <IconButton
                    tooltip="Previous month"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Monthly.handlePrev}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_left
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.monthly.start)
                      .add(-1, 'd')
                      .startOf('month')
                      .format('MMM D')}

                <MoneySum
                  title="Income"
                  subtitle="(month)"
                  value={this.state.monthly.totalIncome}
                  currency="CAD"
                />
                <MoneySum
                  title="Expenses"
                  subtitle="(month)"
                  value={this.state.monthly.totalExpenses}
                  currency="CAD"
                />

                  {/* Next Month */}
                  <IconButton
                    tooltip="Next month"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Monthly.handleNext}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_right
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.monthly.start)
                      .endOf('month')
                      .add(1, 'd')
                      .format('MMM D')}

              {(this.state.monthly.totalIncome - this.state.monthly.totalExpenses > 0) &&
                <DoughnutChart
                  data={{
                    labels: Object.keys(this.state.monthly.expenses),
                    datasets: [{
                      data: Object.values(this.state.monthly.expenses),
                      backgroundColor: Object.keys(this.state.monthly.expenses).map(s => hashHSL(s, '75%', '40%'))
                    }]
                  }}
                />}
            </Card>

              {this.state.expenses.map(expense => {
                const currDate = moment(expense.timestamp * 1000)
                const currDay = currDate.day()
                let divider
                if (!prevDay || prevDay !== currDay) {
                  divider = (
                    <Chip style={{ margin: '0 auto' }}>
                      {currDate.format('Y-M-D')}
                    </Chip>
                  )
                }
                prevDay = currDay
                return [
                  divider,
                  <ExpenseCard
                    key={expense.id}
                    {...expense}
                    categories={Object.keys(this.state.expenses.reduce(toCategory, {}))}
                    onEdit={this.handleEditExpense}
                    onDelete={this.handleDeleteExpense}
                  />
                ]
              })}

        )}

    )
  }
}

function MoneySum (props) {
  return (

        {props.title}

        {props.subtitle}

        {props.value} {props.currency}

  )
}

MoneySum.propTypes = {
  title: PropTypes.string,
  subtitle: PropTypes.string,
  value: PropTypes.number,
  currency: PropTypes.string
}

export default ExpensesPage
<commit_msg>Fix empty database message when loading<commit_after>import React, {Component} from 'react'
import PropTypes from 'prop-types'

import server from 'views/helpers/network.js'
import ExpenseCard from 'views/components/ExpenseCard'
import { colorMoney, hashHSL } from 'views/helpers/colors.js'

import {
  Bar as BarChart,
  Doughnut as DoughnutChart
} from 'react-chartjs-2'
import moment from 'moment'
import { map } from 'lodash'

import Card from 'material-ui/Card'
import Chip from 'material-ui/Chip'
import IconButton from 'material-ui/IconButton'
import FontIcon from 'material-ui/FontIcon'

/**
 * Helper function used like `reduce(toCategory, {})` on `expenses`
 * @param {Object} categories
 * @param {Object} x
 */
function toCategory (categories, x) {
  const key = x.category || 'Other'
  if (!categories[key]) {
    categories[key] = 0
  }
  categories[key] += x.amount
  return categories
}

/**
 * Helper function used like `reduce(toAccount, {})` on `expenses`
 * @param {Object} accounts
 * @param {Object} x
 */
function toAccount (accounts, x) {
  const key = x.account
  if (!accounts[key]) {
    accounts[key] = 0
  }
  accounts[key] += x.amount
  return accounts
}

class ExpensesPage extends Component {
  state = {
    // overall
    expenses: undefined,
    accountBalances: undefined,
    // weekly data
    weekly: {
      start: moment().startOf('week'),
      labels: [0, 0, 0, 0, 0, 0, 0],
      totalIncome: 0,
      totalExpenses: 0,
      // bar chart
      income: [0, 0, 0, 0, 0, 0, 0],
      expenses: [0, 0, 0, 0, 0, 0, 0]
    },
    // monthly data
    monthly: {
      start: moment().startOf('month'),
      totalIncome: 0,
      totalExpenses: 0,
      // pie chart
      income: {},
      expenses: {}
    }
  }

  componentDidMount () {
    const byTimestampDesc = (x1, x2) => {
      return x2.timestamp - x1.timestamp
    }
    server.getExpenses(0, Date.now())
      .then(expenses => {
        const newExpenses = this.state.expenses
          .concat(expenses.map(expense => ({
            type: 'expense',
            ...expense
          })))
          .sort(byTimestampDesc)
        this.setState({
          expenses: newExpenses
        }, this.analyzeExpenses)
      })
    server.getIncome(0, Date.now())
      .then(income => {
        const newExpenses = this.state.expenses
          .concat(income.map(income => ({
            type: 'income',
            ...income
          })))
          .sort(byTimestampDesc)
        this.setState({
          expenses: newExpenses
        }, this.analyzeExpenses)
      })
  }

  analyzeExpenses () {
    const accountBalances = this.state.expenses
      .map(x => ({ ...x, amount: Math.round(x.amount) }))
      .reduce(toAccount, {})
    const weeklyChart = this.Weekly.computeForChart(this.state.weekly.start)
    const monthlyChart = this.Monthly.computeForChart(this.state.monthly.start)

    this.setState(prev => ({
      accountBalances,
      weekly: {
        ...prev.weekly,
        ...weeklyChart
      },
      monthly: {
        ...prev.monthly,
        ...monthlyChart
      }
    }))
  }

  handleEditExpense = (id, changes) => {
    const byTimestampDesc = (x1, x2) => {
      return x2.timestamp - x1.timestamp
    }
    this.setState(prev => ({
      expenses: prev.expenses
        .map(x => {
          if (x.id === id) {
            x = {
              ...x,
              description: changes.description,
              timestamp: changes.timestamp,
              category: changes.category
            }
          }
          return x
        })
        .sort(byTimestampDesc)
    }), this.analyzeExpenses)
  }

  handleDeleteExpense = id => {
    this.setState(prev => ({
      expenses: prev.expenses
        .filter(x => x.id !== id)
    }), this.analyzeExpenses)
  }

  Weekly = {
    handlePrev: event => {
      const start = moment(this.state.weekly.start)
        .add(-1, 'd')
        .startOf('week')
      this.setState({
        weekly: {
          start,
          ...this.Weekly.computeForChart(start)
        }
      })
    },

    handleNext: event => {
      const start = moment(this.state.weekly.start)
        .endOf('week')
        .add(1, 'd')
        .startOf('week')
      this.setState({
        weekly: {
          start,
          ...this.Weekly.computeForChart(start)
        }
      })
    },

    computeForChart: start => {
      const weekStart = start.unix()
      const weekEnd = moment(weekStart * 1000).endOf('week').unix()

      const day = 24 * 60 * 60
      const toWeekDays = (days, x) => {
        const i = Math.floor((x.timestamp - weekStart) / day)
        days[i] += x.amount
        return days
      }

      const weeklyIncome = this.state.expenses
        .filter(x => x.timestamp >= weekStart && x.timestamp <= weekEnd)
        .filter(x => x.amount > 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))
        .reduce(toWeekDays, [0, 0, 0, 0, 0, 0, 0])

      const totalWeeklyIncome = weeklyIncome
        .reduce((s, x) => s + x, 0)

      const weeklyExpenses = this.state.expenses
        .filter(x => x.timestamp >= weekStart && x.timestamp <= weekEnd)
        .filter(x => x.amount < 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))
        .reduce(toWeekDays, [0, 0, 0, 0, 0, 0, 0])

      const totalWeeklyExpenses = weeklyExpenses
        .reduce((s, x) => s + x, 0)

      const weeklyLabels = [0, 0, 0, 0, 0, 0, 0]
        .map((x, i) => {
          const t = moment(weekStart * 1000)
            .startOf('week')
            .add(i, 'd')
          return t
            .isSame(moment(), 'day')
            ? 'Today'
            : t.format('MMM D')
        })

      return {
        labels: weeklyLabels,
        income: weeklyIncome,
        expenses: weeklyExpenses,
        totalIncome: totalWeeklyIncome,
        totalExpenses: totalWeeklyExpenses
      }
    }
  }

  Monthly = {
    handlePrev: event => {
      const start = moment(this.state.monthly.start)
        .add(-1, 'd')
        .startOf('month')
      this.setState({
        monthly: {
          start,
          ...this.Monthly.computeForChart(start)
        }
      })
    },

    handleNext: event => {
      const start = moment(this.state.monthly.start)
        .endOf('month')
        .add(1, 'd')
      this.setState({
        monthly: {
          start,
          ...this.Monthly.computeForChart(start)
        }
      })
    },

    computeForChart: start => {
      const monthStart = start.unix()
      const monthEnd = moment(monthStart * 1000).endOf('month').unix()

      let monthlyIncome = this.state.expenses
        .filter(x => x.timestamp >= monthStart && x.timestamp <= monthEnd)
        .filter(x => x.amount > 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))

      const totalMonthlyIncome = monthlyIncome
        .reduce((s, x) => s + x.amount, 0)

      monthlyIncome = monthlyIncome
        .reduce(toCategory, {})

      let monthlyExpenses = this.state.expenses
        .filter(x => x.timestamp >= monthStart && x.timestamp <= monthEnd)
        .filter(x => x.amount < 0)
        .map(x => ({ ...x, amount: Math.round(x.amount) }))

      const totalMonthlyExpenses = monthlyExpenses
        .reduce((s, x) => s + x.amount, 0)

      monthlyExpenses = monthlyExpenses
        .map(x => ({ ...x, amount: Math.abs(x.amount) }))
        .reduce(toCategory, {})

      return {
        income: monthlyIncome,
        expenses: monthlyExpenses,
        totalIncome: totalMonthlyIncome,
        totalExpenses: totalMonthlyExpenses
      }
    }
  }

  render () {
    if (this.state.accountBalances === undefined) {
      return null
    }
    const isEmpty = Object.keys(this.state.accountBalances).length
    let prevDay
    return (

        {!isEmpty && (
          <Card style={{ margin: '10px 0', padding: '16px' }}>

Hello Omar!

You do not have any expenses logged into the database yet.

          </Card>
        )}
        {isEmpty && (

            {/* Total */}
            <Card style={{ margin: '10px 0', padding: '16px' }}>

                {map(this.state.accountBalances, (balance, account) =>
                  <MoneySum
                    key={account}
                    title="Balance"
                    subtitle={'(' + account.substring(account.length - 4, account.length) + ')'}
                    value={balance}
                    currency="CAD"
                  />)}

            </Card>
            {/* Weekly */}
            <Card style={{ margin: '10px 0', padding: '16px' }}>

                {this.state.weekly.start
                  .format('MMM D') +
                  ' - ' +
                moment(this.state.weekly.start)
                  .endOf('week')
                  .format('MMM D')}

                {/* Previous Week */}

                  <IconButton
                    tooltip="Previous week"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Weekly.handlePrev}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_left
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.weekly.start)
                      .add(-7, 'd')
                      .format('MMM D')}

                <MoneySum
                  title="Income"
                  subtitle="(week)"
                  value={this.state.weekly.totalIncome}
                  currency="CAD"
                />
                <MoneySum
                  title="Expenses"
                  subtitle="(week)"
                  value={this.state.weekly.totalExpenses}
                  currency="CAD"
                />

                  {/* Next Week */}
                  <IconButton
                    tooltip="Next week"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Weekly.handleNext}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_right
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.weekly.start)
                      .add(7, 'd')
                      .format('MMM D')}

              {(this.state.weekly.totalIncome - this.state.weekly.totalExpenses > 0) &&
                <BarChart
                  data={{
                    labels: this.state.weekly.labels,
                    datasets: [
                      {
                        backgroundColor: colorMoney(1),
                        label: 'Income',
                        data: this.state.weekly.income
                      },
                      {
                        backgroundColor: colorMoney(-1),
                        label: 'Expense',
                        data: this.state.weekly.expenses.map(Math.abs)
                      }
                    ]
                  }}
                  options={{
                    scales: {
                      xAxes: [{
                        stacked: true
                      }],
                      yAxes: [{
                        stacked: true
                      }]
                    }
                  }}
                />}
            </Card>
            {/* Monthly */}
            <Card style={{ margin: '10px 0', padding: '16px' }}>

                {this.state.monthly.start
                  .format('MMM D') +
                  ' - ' +
                moment(this.state.monthly.start)
                  .endOf('month')
                  .format('MMM D')}

                  {/* Previous Month */}
                  <IconButton
                    tooltip="Previous month"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Monthly.handlePrev}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_left
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.monthly.start)
                      .add(-1, 'd')
                      .startOf('month')
                      .format('MMM D')}

                <MoneySum
                  title="Income"
                  subtitle="(month)"
                  value={this.state.monthly.totalIncome}
                  currency="CAD"
                />
                <MoneySum
                  title="Expenses"
                  subtitle="(month)"
                  value={this.state.monthly.totalExpenses}
                  currency="CAD"
                />

                  {/* Next Month */}
                  <IconButton
                    tooltip="Next month"
                    tooltipPosition="bottom-center"
                    onTouchTap={this.Monthly.handleNext}>
                    <FontIcon className="material-icons"
                      color="#444">
                      chevron_right
                    </FontIcon>
                  </IconButton>

                    {moment(this.state.monthly.start)
                      .endOf('month')
                      .add(1, 'd')
                      .format('MMM D')}

              {(this.state.monthly.totalIncome - this.state.monthly.totalExpenses > 0) &&
                <DoughnutChart
                  data={{
                    labels: Object.keys(this.state.monthly.expenses),
                    datasets: [{
                      data: Object.values(this.state.monthly.expenses),
                      backgroundColor: Object.keys(this.state.monthly.expenses).map(s => hashHSL(s, '75%', '40%'))
                    }]
                  }}
                />}
            </Card>

              {this.state.expenses.map(expense => {
                const currDate = moment(expense.timestamp * 1000)
                const currDay = currDate.day()
                let divider
                if (!prevDay || prevDay !== currDay) {
                  divider = (
                    <Chip style={{ margin: '0 auto' }}>
                      {currDate.format('Y-M-D')}
                    </Chip>
                  )
                }
                prevDay = currDay
                return [
                  divider,
                  <ExpenseCard
                    key={expense.id}
                    {...expense}
                    categories={Object.keys(this.state.expenses.reduce(toCategory, {}))}
                    onEdit={this.handleEditExpense}
                    onDelete={this.handleDeleteExpense}
                  />
                ]
              })}

        )}

    )
  }
}

function MoneySum (props) {
  return (

        {props.title}

        {props.subtitle}

        {props.value} {props.currency}

  )
}

MoneySum.propTypes = {
  title: PropTypes.string,
  subtitle: PropTypes.string,
  value: PropTypes.number,
  currency: PropTypes.string
}

export default ExpensesPage
<|endoftext|>


<commit_before>//CREATES NEW MAP//
        function initMap() { //Call this function whenever a new marker needs to be made
          var centerPoint = {lat: 45.060, lng: -91.407};

          var map = new google.maps.Map(document.getElementById('map'), {
            zoom: 10,
            center: centerPoint
          });
        }
        //CREATES ONCLICK EVENT//
        google.maps.event.addListener(map, 'click', function(event) { //OnClick Function that generates a new marker
          placeMarker(event.latLng); //Save this info to the database
        });

        //ONCLICK EVENT - ADDS MARKER
        function placeMarker(location) { //Shows the user where their marker is placed
          var title = prompt("Enter a name for your marker: "); //Ask user for input
          reader.close(); //close scanner
          var marker = new google.maps.Marker({
            position: markerPoint,
            map: map,
            title: title //call reader as title of new marker
          });
        }
<commit_msg>fixed map js by removed extra code<commit_after>function initMap() {
  var centerPoint = {lat: 45.060, lng: -91.407};

  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 10,
    center: centerPoint
  });
}<|endoftext|>


<commit_before>import '../style/PolyannoEd.css'
<commit_msg>set modules for webpack entry<commit_after>import '../style/dragondrop.css'
import '../style/alltheunicode.css'
import '../style/polyanno.css'
import '../style/PolyannoEd.css'

import './dragondrop.js'
import './alltheunicode.js'
import './polyanno.js'

imageSelected = 'http://images.is.ed.ac.uk/luna/servlet/iiif/UoEwmm~2~2~77099~164515/info.json'

var polyanno_setup_options = {
  'highlighting': true,
  'minimising': true,
  'voting': true
}

// this is assuming the defaults of storage URLs using the web page url and polyanno_storage for storage and no users
polyanno_setup(polyanno_setup_options)
<|endoftext|>