<commit_before>/* jshint boss: true */
/* global Text */
;(function(root, factory) {
    'use strict';
    if (typeof define === "function" && define.amd) {
        define(factory);
    } else {
        root.DOMPurify = factory();
    }
}(this, function() {
    'use strict';

    var DOMPurify = {};
    DOMPurify.sanitize = function(dirty, cfg) {

        /**
         * We consider the elements and attributes below to be safe. Ideally
         * don't add any new ones but feel free to remove unwanted ones.
         */

        /* allowed element names */
        var ALLOWED_TAGS = [

            // HTML
            'abbr','acronym','address','area','article','aside','audio','b',
            'bdi','bdo','big','blink','blockquote','body','br','button','canvas',
            'caption','center','cite','code','col','colgroup','content','data',
            'datalist','dd','decorator','del','details','dfn','dir','div','dl','dt',
            'element','em','fieldset','figcaption','figure','font','footer','form',
            'h1','h2','h3','h4','h5','h6','head','header','hgroup','hr','html','i',
            'img','input','ins','kbd','label','legend','li','main','map','mark',
            'marquee','menu','menuitem','meter','nav','nobr','ol','optgroup',
            'option','output','p','pre','progress','q','rp','rt','ruby','s','samp',
            'section','select','shadow','small','source','spacer','span','strike',
            'strong','style','sub','summary','sup','table','tbody','td','template',
            'textarea','tfoot','th','thead','time','tr','track','tt','u','ul','var',
            'video','wbr',

            // SVG
            'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
            'animatemotion','animatetransform','circle','clippath','defs','desc',
            'ellipse','font','g','glyph','glyphref','hkern','image','line',
            'lineargradient','marker','mask','metadata','mpath','path','pattern',
            'polygon','polyline','radialgradient','rect','stop','switch','symbol',
            'text','textpath','title','tref','tspan','view','vkern',

            //MathML
            'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr',
            'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow',
            'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd',
            'mtext','mtr','munder','munderover',

            //Text
            '#text'
        ];

        /* Decide if custom data attributes are okay */
        var ALLOW_DATA_ATTR = true;

        /* Allowed attribute names */
        var ALLOWED_ATTR = [

            // HTML
            'name', 'id','href','action','class','title','alt','src','type',
            'height','width', 'method','rev','rel','accept','align','autocomplete',
            'xmlns','bgcolor','border','checked','cite','color','cols','colspan',
            'coords','datetime','default','dir','disabled','download','enctype',
            'for','headers','hidden','high','hreflang','ismap','label','lang',
            'list','loop', 'low','max','maxlength','media','min','multiple',
            'novalidate','open','optimum','pattern','placeholder','poster',
            'preload','pubdate','radiogroup','readonly','required','reversed',
            'rows','rowspan','spellcheck','scope','selected','shape','size','span',
            'srclang','start','step','style','summary','tabindex','usemap','value',

            // SVG
            'accent-height','accumulate','additivive','alignment-baseline',
            'ascent','azimuth','baseline-shift','bias','clip','clip-path',
            'clip-rule','color','color-interpolation','color-interpolation-filters',
            'color-profile','color-rendering','cx','cy','d','dy','dy','direction',
            'display','divisor','dur','elevation','end','fill','fill-opacity',
            'fill-rule','filter','flood-color','flood-opacity','font-family',
            'font-size','font-size-adjust','font-stretch','font-style','font-variant',
            'front-weight','image-rendering','in','in2','k1','k2','k3','k4','kerning',
            'letter-spacing','lighting-color','local','marker-end','marker-mid',
            'marker-start','max','mask','mode','min','operator','opacity','order',
            'overflow','paint-order','path','points','r','rx','ry','radius','restart',
            'scale','seed','shape-rendering','stop-color','stop-opacity',
            'stroke-dasharray','stroke-dashoffset','stroke-linecap','stroke-linejoin',
            'stroke-miterlimit','stroke-opacity','stroke','stroke-width','transform',
            'text-anchor','text-decoration','text-rendering','u1','u2','viewbox',
            'visibility','word-spacing','wrap','writing-mode','x','x1','x2','y',
            'y1','y2','z',

            // MathML
            'accent','accentunder','bevelled','close','columnsalign','columnlines',
            'columnspan','denomalign','depth','display','displaystyle','fence',
            'frame','largeop','length','linethickness','lspace','lquote',
            'mathbackground','mathcolor','mathsize','mathvariant','maxsize',
            'minsize','movablelimits','notation','numalign','open','rowalign',
            'rowlines','rowspacing','rowspan','rspace','rquote','scriptlevel',
            'scriptminsize','scriptsizemultiplier','selection','separator',
            'separators','stretchy','subscriptshift','supscriptshift','symmetric',
            'voffset',

            // XML
            'xlink:href','xml:id','xlink:title','xml:space'
        ];

        /* Decide if document with <html>... should be returned */
        var WHOLE_DOCUMENT = false;

        /* Decide if a DOM node or a string should be returned */
        var RETURN_DOM = false;

        /* Output should be safe for jQuery's $() factory? */
        var SAFE_FOR_JQUERY = false;

        /* Output should be free from DOM clobbering attacks? */
        var SANITIZE_DOM = true;

        /* Keep element content when removing element? */
        var KEEP_CONTENT = true;

        /* Ideally, do not touch anything below this line */
        /* ______________________________________________ */

        /**
         * _parseConfig
         *
         * @param  optional config literal
         */
        var _parseConfig = function(cfg) {
            cfg.ALLOWED_ATTR    ? ALLOWED_ATTR    = cfg.ALLOWED_ATTR    : null;
            cfg.ALLOWED_TAGS    ? ALLOWED_TAGS    = cfg.ALLOWED_TAGS    : null;
            cfg.ALLOW_DATA_ATTR ? ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR : null;
            cfg.SAFE_FOR_JQUERY ? SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY : null;
            cfg.WHOLE_DOCUMENT  ? WHOLE_DOCUMENT  = cfg.WHOLE_DOCUMENT  : null;
            cfg.RETURN_DOM      ? RETURN_DOM      = cfg.RETURN_DOM      : null;
            cfg.SANITIZE_DOM    ? SANITIZE_DOM    = cfg.SANITIZE_DOM    : null;
            cfg.KEEP_CONTENT    ? KEEP_CONTENT    = cfg.KEEP_CONTENT    : null;
        };

        /**
         * _createIterator
         *
         * @param  document/fragment to create iterator for
         * @return iterator instance
         */
        var _createIterator = function(doc) {
            return document.createNodeIterator(
                doc,
                NodeFilter.SHOW_ELEMENT
                | NodeFilter.SHOW_COMMENT
                | NodeFilter.SHOW_TEXT,
                function() { return NodeFilter.FILTER_ACCEPT; },
                false
            );
        };

        /**
         * _isClobbered
         *
         * @param  element to check for clobbering attacks
         * @return true if clobbered, false if safe
         */
        var _isClobbered = function(elm) {
            if(elm instanceof Text) {
                return false;
            }
            if (
                (elm.children && !(elm.children instanceof HTMLCollection))
                || typeof elm.nodeName !== 'string'
                || typeof elm.textContent !== 'string'
                || typeof elm.nodeType !== 'number'
                || typeof elm.COMMENT_NODE !== 'number'
                || typeof elm.setAttribute !== 'function'
                || typeof elm.cloneNode !== 'function'
                || typeof elm.removeAttributeNode !== 'function'
                || typeof elm.insertAdjacentHTML !== 'function'
                || typeof elm.attributes.item !== 'function'
            ) {
                return true;
            }
            return false;
        };

        /**
         * _sanitizeElements
         *
         * @protect removeChild
         * @protect nodeType
         * @protect nodeName
         * @protect textContent
         * @protect currentNode
         * @protect insertAdjacentHTML
         *
         * @param   node to check for permission to exist
         * @return  true if node was killed, false if left alive
         */
        var _sanitizeElements = function(currentNode) {
            if (
                _isClobbered(currentNode)
                || currentNode.nodeType === currentNode.COMMENT_NODE
                || ALLOWED_TAGS.indexOf(currentNode.nodeName.toLowerCase()) === -1
            ) {
                if(KEEP_CONTENT && currentNode.insertAdjacentHTML){
                    currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
                }
                currentNode.parentNode.removeChild(currentNode);
                return true;
            }
            if (SAFE_FOR_JQUERY && !currentNode.firstElementChild) {
                currentNode.textContent = currentNode.textContent.replace(/</g, '<');
            }
            return false;
        };

        /**
         * _sanitizeAttributes
         *
         * @protect attributes
         * @protect removeAttribiuteNode
         * @protect setAttribute
         * @protect cloneNode
         *
         * @param   node to sanitize
         * @return  void
         */
        var _sanitizeAttributes = function(currentNode) {
            var regex = /^(\w+script|data):/gi,
                clonedNode = currentNode.cloneNode(true),
                tmp, clobbering;

            /* Check if we have attributes; if not we might have a text node */
            if(currentNode.attributes) {

                /* Go backwards over all attributes; safely remove bad ones */
                for (var attr = currentNode.attributes.length-1; attr >= 0; attr--) {
                    tmp = clonedNode.attributes[attr];
                    clobbering = false;
                    currentNode.removeAttribute(currentNode.attributes[attr].name);

                    if (tmp instanceof Attr) {
                        if(SANITIZE_DOM) {
                            if(tmp.name === 'id' && window[tmp.value]) {
                                clobbering = true;
                            }
                            if(tmp.name === 'name' && document[tmp.value]){
                                clobbering = true;
                            }
                        }

                        /* Safely handle custom data attributes */
                        if (
                            (ALLOWED_ATTR.indexOf(tmp.name.toLowerCase()) > -1 ||
                            (ALLOW_DATA_ATTR && tmp.name.match(/^data-[\w-]+/i)))
                            && !tmp.value.replace(/[\x00-\x20]/g,'').match(regex)
                            && !clobbering
                        ) {
                            currentNode.setAttribute(tmp.name, tmp.value);
                        }
                    }
            }
            }
        };

        /**
         * _sanitizeShadowDOM
         *
         * @param  fragment to iterate over recursively
         * @return void
         */
        var _sanitizeShadowDOM = function(fragment) {
            var shadowNode;
            var shadowIterator = _createIterator(fragment);

            while (shadowNode = shadowIterator.nextNode()) {

                /* Sanitize tags and elements */
                if (_sanitizeElements(shadowNode)) {
                    continue;
                }

                /* Deep shadow DOM detected */
                if (shadowNode.content instanceof DocumentFragment) {
                    _sanitizeShadowDOM(shadowNode.content);
                }

                /* Check attributes, sanitize if necessary */
                _sanitizeAttributes(shadowNode);
            }
        };

        /* Assign config vars */
        cfg ? _parseConfig(cfg) : null;

        /* Create documents to map markup to */
        var dom = document.implementation.createHTMLDocument('');
            dom.body.parentNode.removeChild(dom.body.parentNode.firstElementChild);
            dom.body.outerHTML = dirty;

        /* Work on whole document or just its body */
        var body = WHOLE_DOCUMENT ? dom.body.parentNode : dom.body;
        if (
            !(dom.body instanceof HTMLBodyElement) ||
            !(dom.body instanceof HTMLHtmlElement)
        ) {
            var freshdom = document.implementation.createHTMLDocument('');
            body = WHOLE_DOCUMENT
                ? freshdom.getElementsByTagName.call(dom,'html')[0]
                : freshdom.getElementsByTagName.call(dom,'body')[0];
        }

        /* Early exit in case document is empty */
        if(typeof body === 'undefined') {
            return '';
        }

        /* Get node iterator */
        var currentNode;
        var nodeIterator = _createIterator(body);

        /* Now start iterating over the created document */
        while (currentNode = nodeIterator.nextNode()) {
            /* Sanitize tags and elements */
            if (_sanitizeElements(currentNode)) {
                continue;
            }

            /* Shadow DOM detected, sanitize it */
            if (currentNode.content instanceof DocumentFragment) {
                _sanitizeShadowDOM(currentNode.content);
            }

            /* Check attributes, sanitize if necessary */
            _sanitizeAttributes(currentNode);
        }

        /* Return sanitized string or DOM */
        if (RETURN_DOM) {
            return body;
        }
        return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
    };
    return DOMPurify;
}));
<commit_msg>Fixed an exception on Blink<commit_after>/* jshint boss: true */
/* global Text */
;(function(root, factory) {
    'use strict';
    if (typeof define === "function" && define.amd) {
        define(factory);
    } else {
        root.DOMPurify = factory();
    }
}(this, function() {
    'use strict';

    var DOMPurify = {};
    DOMPurify.sanitize = function(dirty, cfg) {

        /**
         * We consider the elements and attributes below to be safe. Ideally
         * don't add any new ones but feel free to remove unwanted ones.
         */

        /* allowed element names */
        var ALLOWED_TAGS = [

            // HTML
            'abbr','acronym','address','area','article','aside','audio','b',
            'bdi','bdo','big','blink','blockquote','body','br','button','canvas',
            'caption','center','cite','code','col','colgroup','content','data',
            'datalist','dd','decorator','del','details','dfn','dir','div','dl','dt',
            'element','em','fieldset','figcaption','figure','font','footer','form',
            'h1','h2','h3','h4','h5','h6','head','header','hgroup','hr','html','i',
            'img','input','ins','kbd','label','legend','li','main','map','mark',
            'marquee','menu','menuitem','meter','nav','nobr','ol','optgroup',
            'option','output','p','pre','progress','q','rp','rt','ruby','s','samp',
            'section','select','shadow','small','source','spacer','span','strike',
            'strong','style','sub','summary','sup','table','tbody','td','template',
            'textarea','tfoot','th','thead','time','tr','track','tt','u','ul','var',
            'video','wbr',

            // SVG
            'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
            'animatemotion','animatetransform','circle','clippath','defs','desc',
            'ellipse','font','g','glyph','glyphref','hkern','image','line',
            'lineargradient','marker','mask','metadata','mpath','path','pattern',
            'polygon','polyline','radialgradient','rect','stop','switch','symbol',
            'text','textpath','title','tref','tspan','view','vkern',

            //MathML
            'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr',
            'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow',
            'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd',
            'mtext','mtr','munder','munderover',

            //Text
            '#text'
        ];

        /* Decide if custom data attributes are okay */
        var ALLOW_DATA_ATTR = true;

        /* Allowed attribute names */
        var ALLOWED_ATTR = [

            // HTML
            'name', 'id','href','action','class','title','alt','src','type',
            'height','width', 'method','rev','rel','accept','align','autocomplete',
            'xmlns','bgcolor','border','checked','cite','color','cols','colspan',
            'coords','datetime','default','dir','disabled','download','enctype',
            'for','headers','hidden','high','hreflang','ismap','label','lang',
            'list','loop', 'low','max','maxlength','media','min','multiple',
            'novalidate','open','optimum','pattern','placeholder','poster',
            'preload','pubdate','radiogroup','readonly','required','reversed',
            'rows','rowspan','spellcheck','scope','selected','shape','size','span',
            'srclang','start','step','style','summary','tabindex','usemap','value',

            // SVG
            'accent-height','accumulate','additivive','alignment-baseline',
            'ascent','azimuth','baseline-shift','bias','clip','clip-path',
            'clip-rule','color','color-interpolation','color-interpolation-filters',
            'color-profile','color-rendering','cx','cy','d','dy','dy','direction',
            'display','divisor','dur','elevation','end','fill','fill-opacity',
            'fill-rule','filter','flood-color','flood-opacity','font-family',
            'font-size','font-size-adjust','font-stretch','font-style','font-variant',
            'front-weight','image-rendering','in','in2','k1','k2','k3','k4','kerning',
            'letter-spacing','lighting-color','local','marker-end','marker-mid',
            'marker-start','max','mask','mode','min','operator','opacity','order',
            'overflow','paint-order','path','points','r','rx','ry','radius','restart',
            'scale','seed','shape-rendering','stop-color','stop-opacity',
            'stroke-dasharray','stroke-dashoffset','stroke-linecap','stroke-linejoin',
            'stroke-miterlimit','stroke-opacity','stroke','stroke-width','transform',
            'text-anchor','text-decoration','text-rendering','u1','u2','viewbox',
            'visibility','word-spacing','wrap','writing-mode','x','x1','x2','y',
            'y1','y2','z',

            // MathML
            'accent','accentunder','bevelled','close','columnsalign','columnlines',
            'columnspan','denomalign','depth','display','displaystyle','fence',
            'frame','largeop','length','linethickness','lspace','lquote',
            'mathbackground','mathcolor','mathsize','mathvariant','maxsize',
            'minsize','movablelimits','notation','numalign','open','rowalign',
            'rowlines','rowspacing','rowspan','rspace','rquote','scriptlevel',
            'scriptminsize','scriptsizemultiplier','selection','separator',
            'separators','stretchy','subscriptshift','supscriptshift','symmetric',
            'voffset',

            // XML
            'xlink:href','xml:id','xlink:title','xml:space'
        ];

        /* Decide if document with <html>... should be returned */
        var WHOLE_DOCUMENT = false;

        /* Decide if a DOM node or a string should be returned */
        var RETURN_DOM = false;

        /* Output should be safe for jQuery's $() factory? */
        var SAFE_FOR_JQUERY = false;

        /* Output should be free from DOM clobbering attacks? */
        var SANITIZE_DOM = true;

        /* Keep element content when removing element? */
        var KEEP_CONTENT = true;

        /* Ideally, do not touch anything below this line */
        /* ______________________________________________ */

        /**
         * _parseConfig
         *
         * @param  optional config literal
         */
        var _parseConfig = function(cfg) {
            cfg.ALLOWED_ATTR    ? ALLOWED_ATTR    = cfg.ALLOWED_ATTR    : null;
            cfg.ALLOWED_TAGS    ? ALLOWED_TAGS    = cfg.ALLOWED_TAGS    : null;
            cfg.ALLOW_DATA_ATTR ? ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR : null;
            cfg.SAFE_FOR_JQUERY ? SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY : null;
            cfg.WHOLE_DOCUMENT  ? WHOLE_DOCUMENT  = cfg.WHOLE_DOCUMENT  : null;
            cfg.RETURN_DOM      ? RETURN_DOM      = cfg.RETURN_DOM      : null;
            cfg.SANITIZE_DOM    ? SANITIZE_DOM    = cfg.SANITIZE_DOM    : null;
            cfg.KEEP_CONTENT    ? KEEP_CONTENT    = cfg.KEEP_CONTENT    : null;
        };

        /**
         * _createIterator
         *
         * @param  document/fragment to create iterator for
         * @return iterator instance
         */
        var _createIterator = function(doc) {
            return document.createNodeIterator(
                doc,
                NodeFilter.SHOW_ELEMENT
                | NodeFilter.SHOW_COMMENT
                | NodeFilter.SHOW_TEXT,
                function() { return NodeFilter.FILTER_ACCEPT; },
                false
            );
        };

        /**
         * _isClobbered
         *
         * @param  element to check for clobbering attacks
         * @return true if clobbered, false if safe
         */
        var _isClobbered = function(elm) {
            if(elm instanceof Text) {
                return false;
            }
            if (
                (elm.children && !(elm.children instanceof HTMLCollection))
                || typeof elm.nodeName !== 'string'
                || typeof elm.textContent !== 'string'
                || typeof elm.nodeType !== 'number'
                || typeof elm.COMMENT_NODE !== 'number'
                || typeof elm.setAttribute !== 'function'
                || typeof elm.cloneNode !== 'function'
                || typeof elm.removeAttributeNode !== 'function'
                || typeof elm.insertAdjacentHTML !== 'function'
                || typeof elm.attributes.item !== 'function'
            ) {
                return true;
            }
            return false;
        };

        /**
         * _sanitizeElements
         *
         * @protect removeChild
         * @protect nodeType
         * @protect nodeName
         * @protect textContent
         * @protect currentNode
         * @protect insertAdjacentHTML
         *
         * @param   node to check for permission to exist
         * @return  true if node was killed, false if left alive
         */
        var _sanitizeElements = function(currentNode) {
            if (
                _isClobbered(currentNode)
                || currentNode.nodeType === currentNode.COMMENT_NODE
                || ALLOWED_TAGS.indexOf(currentNode.nodeName.toLowerCase()) === -1
            ) {
                if(KEEP_CONTENT && currentNode.insertAdjacentHTML){
                    try {
                        currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
                    } catch(e) {}
                }
                currentNode.parentNode.removeChild(currentNode);
                return true;
            }
            if (SAFE_FOR_JQUERY && !currentNode.firstElementChild) {
                currentNode.textContent = currentNode.textContent.replace(/</g, '<');
            }
            return false;
        };

        /**
         * _sanitizeAttributes
         *
         * @protect attributes
         * @protect removeAttribiuteNode
         * @protect setAttribute
         * @protect cloneNode
         *
         * @param   node to sanitize
         * @return  void
         */
        var _sanitizeAttributes = function(currentNode) {
            var regex = /^(\w+script|data):/gi,
                clonedNode = currentNode.cloneNode(true),
                tmp, clobbering;

            /* Check if we have attributes; if not we might have a text node */
            if(currentNode.attributes) {

                /* Go backwards over all attributes; safely remove bad ones */
                for (var attr = currentNode.attributes.length-1; attr >= 0; attr--) {
                    tmp = clonedNode.attributes[attr];
                    clobbering = false;
                    currentNode.removeAttribute(currentNode.attributes[attr].name);

                    if (tmp instanceof Attr) {
                        if(SANITIZE_DOM) {
                            if(tmp.name === 'id' && window[tmp.value]) {
                                clobbering = true;
                            }
                            if(tmp.name === 'name' && document[tmp.value]){
                                clobbering = true;
                            }
                        }

                        /* Safely handle custom data attributes */
                        if (
                            (ALLOWED_ATTR.indexOf(tmp.name.toLowerCase()) > -1 ||
                            (ALLOW_DATA_ATTR && tmp.name.match(/^data-[\w-]+/i)))
                            && !tmp.value.replace(/[\x00-\x20]/g,'').match(regex)
                            && !clobbering
                        ) {
                            currentNode.setAttribute(tmp.name, tmp.value);
                        }
                    }
            }
            }
        };

        /**
         * _sanitizeShadowDOM
         *
         * @param  fragment to iterate over recursively
         * @return void
         */
        var _sanitizeShadowDOM = function(fragment) {
            var shadowNode;
            var shadowIterator = _createIterator(fragment);

            while (shadowNode = shadowIterator.nextNode()) {

                /* Sanitize tags and elements */
                if (_sanitizeElements(shadowNode)) {
                    continue;
                }

                /* Deep shadow DOM detected */
                if (shadowNode.content instanceof DocumentFragment) {
                    _sanitizeShadowDOM(shadowNode.content);
                }

                /* Check attributes, sanitize if necessary */
                _sanitizeAttributes(shadowNode);
            }
        };

        /* Assign config vars */
        cfg ? _parseConfig(cfg) : null;

        /* Create documents to map markup to */
        var dom = document.implementation.createHTMLDocument('');
            dom.body.parentNode.removeChild(dom.body.parentNode.firstElementChild);
            dom.body.outerHTML = dirty;

        /* Work on whole document or just its body */
        var body = WHOLE_DOCUMENT ? dom.body.parentNode : dom.body;
        if (
            !(dom.body instanceof HTMLBodyElement) ||
            !(dom.body instanceof HTMLHtmlElement)
        ) {
            var freshdom = document.implementation.createHTMLDocument('');
            body = WHOLE_DOCUMENT
                ? freshdom.getElementsByTagName.call(dom,'html')[0]
                : freshdom.getElementsByTagName.call(dom,'body')[0];
        }

        /* Early exit in case document is empty */
        if(typeof body === 'undefined') {
            return '';
        }

        /* Get node iterator */
        var currentNode;
        var nodeIterator = _createIterator(body);

        /* Now start iterating over the created document */
        while (currentNode = nodeIterator.nextNode()) {
            /* Sanitize tags and elements */
            if (_sanitizeElements(currentNode)) {
                continue;
            }

            /* Shadow DOM detected, sanitize it */
            if (currentNode.content instanceof DocumentFragment) {
                _sanitizeShadowDOM(currentNode.content);
            }

            /* Check attributes, sanitize if necessary */
            _sanitizeAttributes(currentNode);
        }

        /* Return sanitized string or DOM */
        if (RETURN_DOM) {
            return body;
        }
        return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
    };
    return DOMPurify;
}));
<|endoftext|>


<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Javascript for omnibox.html, served from chrome://omnibox/
 * This is used to debug omnibox ranking.  The user enters some text
 * into a box, submits it, and then sees lots of debug information
 * from the autocompleter that shows what omnibox would do with that
 * input.
 *
 * The simple object defined in this javascript file listens for
 * certain events on omnibox.html, sends (when appropriate) the
 * input text to C++ code to start the omnibox autcomplete controller
 * working, and listens from callbacks from the C++ code saying that
 * results are available.  When results (possibly intermediate ones)
 * are available, the Javascript formats them and displays them.
 */
cr.define('omniboxDebug', function() {
  'use strict';

  /**
   * Register our event handler.
   */
  function initialize() {
    document.getElementById('omnibox-input-form').addEventListener(
        'submit', startOmniboxQuery, false);
  }

  /**
   * Extracts the input text from the text field and sends it to the
   * C++ portion of chrome to handle.  The C++ code will iteratively
   * call handleNewAutocompleteResult as results come in.
   */
  function startOmniboxQuery(event) {
    // First, clear the results of past calls (if any).
    document.getElementById('omnibox-debug-text').innerHTML = '';
    // Then, call chrome with a one-element list: the value in the text box.
    chrome.send('startOmniboxQuery',
                [document.getElementById('input-text').value]);
    // Cancel the submit action.  i.e., don't submit the form.  (We handle
    // display the results solely with Javascript.)
    event.preventDefault();
  }

  /**
   * Returns a simple object with information about how to display an
   * autocomplete result data field.
   * @param {string} header the label for the top of the column/table.
   * @param {string} urlLabelForHeader the URL that the header should point
   *    to (if non-empty).
   * @param {string} propertyName the name of the property in the autocomplete
   *     result record that we lookup.
   * @param {boolean} displayAlways whether the property should be displayed
   *     regardless of whether we're in detailed more.
   * @constructor
   */
  function PresentationInfoRecord(header, url, propertyName, displayAlways) {
    this.header = header;
    this.urlLabelForHeader = url;
    this.propertyName = propertyName;
    this.displayAlways = displayAlways;
  }

  /**
   * A constant that's used to decide what autocomplete result
   * properties to output in what order.  This is an array of
   * PresentationInfoRecord() objects; for details see that
   * function.
   * @type {Array.<Object>}
   * @const
   */
  var PROPERTY_OUTPUT_ORDER = [
    new PresentationInfoRecord('URL', '', 'destination_url', true),
    new PresentationInfoRecord('Provider Name', '', 'provider_name', true),
    new PresentationInfoRecord('Type', '', 'type', true),
    new PresentationInfoRecord('Relevance', '', 'relevance', true),
    new PresentationInfoRecord('Starred', '', 'starred', false),
    new PresentationInfoRecord(
        '== default_match iterator', '', 'is_default_match', false),
    new PresentationInfoRecord(
        'Is History What You Typed Match', '',
        'is_history_what_you_typed_match', false),
    new PresentationInfoRecord('Description', '', 'description', false),
    new PresentationInfoRecord('Contents', '', 'contents', false),
    new PresentationInfoRecord('Fill Into Edit', '', 'fill_into_edit', false),
    new PresentationInfoRecord(
        'Inline Autocomplete Offset', '', 'inline_autocomplete_offset', false),
    new PresentationInfoRecord('Deletable', '', 'deletable', false),
    new PresentationInfoRecord('From Previous', '', 'from_previous', false),
    new PresentationInfoRecord(
        'Transition Type',
        'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
        'common/page_transition_types.h&exact_package=chromium&l=24',
        'transition', false),
    new PresentationInfoRecord(
        'Is This Provider Done', '', 'provider_done', false)
  ];

  /**
   * Returns an HTML Element of type table row that contains the
   * headers we'll use for labeling the columns.  If we're in
   * detailed_mode, we use all the headers.  If not, we only use ones
   * marked displayAlways.
   */
  function createAutocompleteResultTableHeader() {
    var row = document.createElement('tr');
    var inDetailedMode = document.getElementById('show-details').checked;
    for (var i = 0; i < PROPERTY_OUTPUT_ORDER.length; i++) {
      if (inDetailedMode || PROPERTY_OUTPUT_ORDER[i].displayAlways) {
        var headerCell = document.createElement('th');
        if (PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader != '') {
          // Wrap header text in URL.
          var linkNode = document.createElement('a');
          linkNode.href = PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader;
          linkNode.textContent = PROPERTY_OUTPUT_ORDER[i].header;
          headerCell.appendChild(linkNode);
        } else {
          // Output header text without a URL.
          headerCell.textContent = PROPERTY_OUTPUT_ORDER[i].header;
        }
        row.appendChild(headerCell);
      }
    }
    return row;
  }

  /**
   * @param {Object} autocompleteSuggestion the particular autocomplete
   *     suggestion we're in the process of displaying.
   * @param {string} propertyName the particular property of the autocomplete
   *     suggestion that should go in this cell.
   * @return {HTMLTableCellElement} that contains the value within this
   *     autocompleteSuggestion associated with propertyName.  In the process,
   *     remove propertyName from autocompleteSuggestion so we know we've
   *     outputted it.
   */
  function createCellForPropertyAndRemoveProperty(autocompleteSuggestion,
                                                  propertyName) {
    var cell = document.createElement('td');
    if (propertyName in autocompleteSuggestion) {
      if (typeof autocompleteSuggestion[propertyName] == 'boolean') {
        // If this is a boolean, display a checkmark or an X instead of
        // the strings true or false.
        if (autocompleteSuggestion[propertyName]) {
          cell.className = 'check-mark';
          cell.textContent = '✔';
        } else {
          cell.className = 'x-mark';
          cell.textContent = '✗';
        }
      } else {
        // All other data types (integer, strings, etc.) display their
        // normal toString() output.
        cell.textContent = autocompleteSuggestion[propertyName];
      }
      // Prune this propertyName so we know it's been displayed.
      delete autocompleteSuggestion[propertyName];
    }  // else: if propertyName is undefined, we leave the cell blank
    return cell;
  }

  /**
   * Called by C++ code when we get an update from the
   * AutocompleteController.  result is an complex Object with lots of
   * information about various autocomplete matches.  The function
   * takes result and puts it in human-readable form in the HTML node
   * omnibox-debug-text.  It may clobber what's already there
   * depending on parameters.  The current human-readable form a few
   * lines about general autocomplete result statistics followed by a
   * table with one line for each autocomplete result.  For an example
   * of the output, try it with chrome://omnibox/.  As for the
   * input parameter (result), here's an example of what it looks
   * like:
   *

   * {@code
   * { 'done': false,
   *   'time_since_omnibox_started_ms': 15,
   *   'num_items': 4,
   *   'item_0': {
   *     'destination_url': 'http://mail.google.com',
   *     'provider_name': 'HistoryURL',
   *     'relevance': 1410,
   *     'is_default_match': true,
   *     ...
   *   }
   *   'item_1: {
   *     ...
   *   }
   *   ...
   * }
   * }
   *

   * For information on how the result is packed, see the
   * corresponding code in chrome/browser/ui/webui/omnibox_ui.cc
   */
  function handleNewAutocompleteResult(result) {
    var showIncompleteResults =
        document.getElementById('show-incomplete-results').checked;
    // If we're asked not to show intermediate results, then clear
    // whatever results are already there.
    if (!showIncompleteResults)
      document.getElementById('omnibox-debug-text').innerHTML = '';

    var output = document.getElementById('omnibox-debug-text');
    var inDetailedMode = document.getElementById('show-details').checked;

    // Output the result-level features in detailed mode and in
    // show incomplete results mode.  We do the latter because without
    // these result-level features, one can't make sense of each
    // batch of results.
    if (inDetailedMode || showIncompleteResults) {
      var p1 = document.createElement('p');
      p1.textContent = 'elapsed time = ' +
          result.time_since_omnibox_started_ms + 'ms';
      output.appendChild(p1);
      var p2 = document.createElement('p');
      p2.textContent = 'all providers done = ' + result.done;
      output.appendChild(p2);
    }

    // Create a table to hold all the autocomplete items.
    var table = document.createElement('table');
    table.className = 'autocomplete-results-table';
    table.appendChild(createAutocompleteResultTableHeader());
    // Loop over every autocomplete item and add it as a row in the table.
    for (var i = 0; i < result.num_items; i++) {
      var autocompleteSuggestion = result['item_' + i];
      var row = document.createElement('tr');
      // Loop over all the columns/properties and output either them
      // all (if we're in detailed mode) or only the ones marked displayAlways.
      for (var j = 0; j < PROPERTY_OUTPUT_ORDER.length; j++) {
        if (inDetailedMode || PROPERTY_OUTPUT_ORDER[j].displayAlways) {
          row.appendChild(createCellForPropertyAndRemoveProperty(
              autocompleteSuggestion, PROPERTY_OUTPUT_ORDER[j].propertyName));
        }
      }

      // Now, if we're in detailed mode, add all the properties that
      // haven't already been output.  (We know which properties have
      // already been output because we delete the property when we output
      // it.  The only way we have properties left at this point if
      // we're in detailed mode and we're getting back properties
      // not listed in PROPERTY_OUTPUT_ORDER.  Perhaps someone added
      // something to the C++ code but didn't bother to update this
      // Javascript?  In any case, we want to display them.)
      if (inDetailedMode) {
        for (var key in autocompleteSuggestion) {
          var cell = document.createElement('td');
          cell.textContent = key + '=' + autocompleteSuggestion[key];
          row.appendChild(cell);
        }
      }

      table.appendChild(row);
    }
    output.appendChild(table);
  }

  return {
    initialize: initialize,
    startOmniboxQuery: startOmniboxQuery,
    handleNewAutocompleteResult: handleNewAutocompleteResult
  };
});

document.addEventListener('DOMContentLoaded', omniboxDebug.initialize);
<commit_msg>Improve chrome://omnibox/ page to be a proper UI controller.<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Javascript for omnibox.html, served from chrome://omnibox/
 * This is used to debug omnibox ranking.  The user enters some text
 * into a box, submits it, and then sees lots of debug information
 * from the autocompleter that shows what omnibox would do with that
 * input.
 *
 * The simple object defined in this javascript file listens for
 * certain events on omnibox.html, sends (when appropriate) the
 * input text to C++ code to start the omnibox autcomplete controller
 * working, and listens from callbacks from the C++ code saying that
 * results are available.  When results (possibly intermediate ones)
 * are available, the Javascript formats them and displays them.
 */
cr.define('omniboxDebug', function() {
  'use strict';

  /**
   * Register our event handlers.
   */
  function initialize() {
    document.getElementById('omnibox-input-form').addEventListener(
        'submit', startOmniboxQuery, false);
    document.getElementById('show-details').addEventListener(
        'change', refresh);
    document.getElementById('show-incomplete-results').addEventListener(
        'change', refresh);
  }

  /**
   * @type {Array.<Object>} an array of all autocomplete results we've seen
   *     for this query.  We append to this list once for every call to
   *     handleNewAutocompleteResult.  For details on the structure of
   *     the object inside, see the comments by addResultToOutput.
   */
  var progressiveAutocompleteResults = [];

  /**
   * Extracts the input text from the text field and sends it to the
   * C++ portion of chrome to handle.  The C++ code will iteratively
   * call handleNewAutocompleteResult as results come in.
   */
  function startOmniboxQuery(event) {
    // First, clear the results of past calls (if any).
    progressiveAutocompleteResults = [];
    // Then, call chrome with a one-element list: the value in the text box.
    chrome.send('startOmniboxQuery',
                [document.getElementById('input-text').value]);
    // Cancel the submit action.  i.e., don't submit the form.  (We handle
    // display the results solely with Javascript.)
    event.preventDefault();
  }

  /**
   * Returns a simple object with information about how to display an
   * autocomplete result data field.
   * @param {string} header the label for the top of the column/table.
   * @param {string} urlLabelForHeader the URL that the header should point
   *     to (if non-empty).
   * @param {string} propertyName the name of the property in the autocomplete
   *     result record that we lookup.
   * @param {boolean} displayAlways whether the property should be displayed
   *     regardless of whether we're in detailed more.
   * @constructor
   */
  function PresentationInfoRecord(header, url, propertyName, displayAlways) {
    this.header = header;
    this.urlLabelForHeader = url;
    this.propertyName = propertyName;
    this.displayAlways = displayAlways;
  }

  /**
   * A constant that's used to decide what autocomplete result
   * properties to output in what order.  This is an array of
   * PresentationInfoRecord() objects; for details see that
   * function.
   * @type {Array.<Object>}
   * @const
   */
  var PROPERTY_OUTPUT_ORDER = [
    new PresentationInfoRecord('URL', '', 'destination_url', true),
    new PresentationInfoRecord('Provider Name', '', 'provider_name', true),
    new PresentationInfoRecord('Type', '', 'type', true),
    new PresentationInfoRecord('Relevance', '', 'relevance', true),
    new PresentationInfoRecord('Starred', '', 'starred', false),
    new PresentationInfoRecord(
        '== default_match iterator', '', 'is_default_match', false),
    new PresentationInfoRecord(
        'Is History What You Typed Match', '',
        'is_history_what_you_typed_match', false),
    new PresentationInfoRecord('Description', '', 'description', false),
    new PresentationInfoRecord('Contents', '', 'contents', false),
    new PresentationInfoRecord('Fill Into Edit', '', 'fill_into_edit', false),
    new PresentationInfoRecord(
        'Inline Autocomplete Offset', '', 'inline_autocomplete_offset', false),
    new PresentationInfoRecord('Deletable', '', 'deletable', false),
    new PresentationInfoRecord('From Previous', '', 'from_previous', false),
    new PresentationInfoRecord(
        'Transition Type',
        'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
        'common/page_transition_types.h&exact_package=chromium&l=24',
        'transition', false),
    new PresentationInfoRecord(
        'Is This Provider Done', '', 'provider_done', false)
  ];

  /**
   * Returns an HTML Element of type table row that contains the
   * headers we'll use for labeling the columns.  If we're in
   * detailed_mode, we use all the headers.  If not, we only use ones
   * marked displayAlways.
   */
  function createAutocompleteResultTableHeader() {
    var row = document.createElement('tr');
    var inDetailedMode = document.getElementById('show-details').checked;
    for (var i = 0; i < PROPERTY_OUTPUT_ORDER.length; i++) {
      if (inDetailedMode || PROPERTY_OUTPUT_ORDER[i].displayAlways) {
        var headerCell = document.createElement('th');
        if (PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader != '') {
          // Wrap header text in URL.
          var linkNode = document.createElement('a');
          linkNode.href = PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader;
          linkNode.textContent = PROPERTY_OUTPUT_ORDER[i].header;
          headerCell.appendChild(linkNode);
        } else {
          // Output header text without a URL.
          headerCell.textContent = PROPERTY_OUTPUT_ORDER[i].header;
        }
        row.appendChild(headerCell);
      }
    }
    return row;
  }

  /**
   * @param {Object} autocompleteSuggestion the particular autocomplete
   *     suggestion we're in the process of displaying.
   * @param {string} propertyName the particular property of the autocomplete
   *     suggestion that should go in this cell.
   * @return {HTMLTableCellElement} that contains the value within this
   *     autocompleteSuggestion associated with propertyName.
   */
  function createCellForPropertyAndRemoveProperty(autocompleteSuggestion,
                                                  propertyName) {
    var cell = document.createElement('td');
    if (propertyName in autocompleteSuggestion) {
      if (typeof autocompleteSuggestion[propertyName] == 'boolean') {
        // If this is a boolean, display a checkmark or an X instead of
        // the strings true or false.
        if (autocompleteSuggestion[propertyName]) {
          cell.className = 'check-mark';
          cell.textContent = '✔';
        } else {
          cell.className = 'x-mark';
          cell.textContent = '✗';
        }
      } else {
        // All other data types (integer, strings, etc.) display their
        // normal toString() output.
        cell.textContent = autocompleteSuggestion[propertyName];
      }
    }  // else: if propertyName is undefined, we leave the cell blank
    return cell;
  }

  /**
   * Called by C++ code when we get an update from the
   * AutocompleteController.  We simply append the result to
   * progressiveAutocompleteResults and refresh the page.
   */
  function handleNewAutocompleteResult(result) {
    progressiveAutocompleteResults.push(result);
    refresh();
  }

  /**
   * Appends some human-readable information about the provided
   * autocomplete result to the HTML node with id omnibox-debug-text.
   * The current human-readable form is a few lines about general
   * autocomplete result statistics followed by a table with one line
   * for each autocomplete match.  The input parameter result is a
   * complex Object with lots of information about various
   * autocomplete matches.  Here's an example of what it looks like:
   *

   * {@code
   * { 'done': false,
   *   'time_since_omnibox_started_ms': 15,
   *   'num_items': 4,
   *   'item_0': {
   *     'destination_url': 'http://mail.google.com',
   *     'provider_name': 'HistoryURL',
   *     'relevance': 1410,
   *     'is_default_match': true,
   *     ...
   *   }
   *   'item_1: {
   *     ...
   *   }
   *   ...
   * }
   * }
   *

   * For information on how the result is packed, see the
   * corresponding code in chrome/browser/ui/webui/omnibox_ui.cc
   */
  function addResultToOutput(result) {
    var output = document.getElementById('omnibox-debug-text');
    var inDetailedMode = document.getElementById('show-details').checked;
    var showIncompleteResults =
        document.getElementById('show-incomplete-results').checked;

    // Output the result-level features in detailed mode and in
    // show incomplete results mode.  We do the latter because without
    // these result-level features, one can't make sense of each
    // batch of results.
    if (inDetailedMode || showIncompleteResults) {
      var p1 = document.createElement('p');
      p1.textContent = 'elapsed time = ' +
          result.time_since_omnibox_started_ms + 'ms';
      output.appendChild(p1);
      var p2 = document.createElement('p');
      p2.textContent = 'all providers done = ' + result.done;
      output.appendChild(p2);
    }

    // Create a table to hold all the autocomplete items.
    var table = document.createElement('table');
    table.className = 'autocomplete-results-table';
    table.appendChild(createAutocompleteResultTableHeader());
    // Loop over every autocomplete item and add it as a row in the table.
    for (var i = 0; i < result.num_items; i++) {
      var autocompleteSuggestion = result['item_' + i];
      var row = document.createElement('tr');
      // Loop over all the columns/properties and output either them
      // all (if we're in detailed mode) or only the ones marked displayAlways.
      // Keep track of which properties we displayed.
      var displayedProperties = {};
      for (var j = 0; j < PROPERTY_OUTPUT_ORDER.length; j++) {
        if (inDetailedMode || PROPERTY_OUTPUT_ORDER[j].displayAlways) {
          row.appendChild(createCellForPropertyAndRemoveProperty(
              autocompleteSuggestion, PROPERTY_OUTPUT_ORDER[j].propertyName));
          displayedProperties[PROPERTY_OUTPUT_ORDER[j].propertyName] = true;
        }
      }

      // Now, if we're in detailed mode, add all the properties that
      // haven't already been output.  (We know which properties have
      // already been output because we delete the property when we output
      // it.  The only way we have properties left at this point if
      // we're in detailed mode and we're getting back properties
      // not listed in PROPERTY_OUTPUT_ORDER.  Perhaps someone added
      // something to the C++ code but didn't bother to update this
      // Javascript?  In any case, we want to display them.)
      if (inDetailedMode) {
        for (var key in autocompleteSuggestion) {
          if (!displayedProperties[key]) {
            var cell = document.createElement('td');
            cell.textContent = key + '=' + autocompleteSuggestion[key];
            row.appendChild(cell);
          }
        }
      }

      table.appendChild(row);
    }
    output.appendChild(table);
  }

  /* Repaints the page based on the contents of the array
   * progressiveAutocompleteResults, which represents consecutive
   * autocomplete results.  We only display the last (most recent)
   * entry unless we're asked to display incomplete results.  For an
   * example of the output, play with chrome://omnibox/
   */
  function refresh() {
    // Erase whatever is currently being displayed.
    var output = document.getElementById('omnibox-debug-text');
    output.innerHTML = '';

    // Display the results.
    var showIncompleteResults =
        document.getElementById('show-incomplete-results').checked;
    var startIndex = showIncompleteResults ? 0 :
        progressiveAutocompleteResults.length - 1;
    for (var i = startIndex; i < progressiveAutocompleteResults.length; i++) {
      addResultToOutput(progressiveAutocompleteResults[i]);
    }
  }

  return {
    initialize: initialize,
    startOmniboxQuery: startOmniboxQuery,
    handleNewAutocompleteResult: handleNewAutocompleteResult
  };
});

document.addEventListener('DOMContentLoaded', omniboxDebug.initialize);
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var moment = require('moment');

var cage = {
  name: 'cage'
  , label: 'Cannula Age'
  , pluginType: 'pill-minor'
};

function init() {
  return cage;
}

module.exports = init;

cage.getPrefs = function getPrefs(sbx) {
  // CAGE_INFO = 44 CAGE_WARN=48 CAGE_URGENT=70
  return {
    'info' : sbx.extendedSettings.info ? sbx.extendedSettings.info : 44,
    'warn' : sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 48,
    'urgent' : sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 72,
    'enableAlerts' : sbx.extendedSettings.enableAlerts
  };
};

cage.checkNotifications = function checkNotifications(sbx) {

  var cannulaInfo = cage.findLatestTimeChange(sbx);
  var prefs = cage.getPrefs(sbx);

  if (!prefs.enableAlerts || !cannulaInfo.checkForAlert) { return; }

  var sendNotification = false;
  var title = 'Cannula age ' + cannulaInfo.age +' hours';
  var sound = 'incoming';
  var msg = ', change due soon';
  var level = 1;

  if (cannulaInfo.age === prefs.info) { sendNotification = true; }
  if (cannulaInfo.age === prefs.warn) { sendNotification = true; msg = ', time to change'; }
  if (cannulaInfo.age === prefs.urgent) { sendNotification = true; msg = ', change overdue!'; sound = 'persistent'; level = 2;}

  if (sendNotification) {
    sbx.notifications.requestNotify({
      level: level
      , title: title
      , message: title + msg
      , pushoverSound: sound
      , plugin: cage
      , debug: {
        cannulaInfo: cannulaInfo
      }
    });
  }
};

cage.findLatestTimeChange = function findLatestTimeChange(sbx) {

  var returnValue = {'message':'', 'found': false, 'age': 0, 'treatmentDate': null, 'checkForAlert': false};

  var prevDate = 0;

  _.forEach(sbx.data.treatments, function eachTreatment (treatment) {
    var treatmentDate = treatment.mills;
    if (treatment.eventType === 'Site Change' && treatmentDate > prevDate && treatmentDate <= sbx.time) {

      prevDate = treatmentDate;
      returnValue.treatmentDate = treatmentDate;

      //allow for 10 minute period after a full hour during which we'll alert the user
      var a = moment(sbx.time);
      var b = moment(returnValue.treatmentDate);
      var hours = a.diff(b,'hours');
      var minFractions = a.diff(b,'minutes') - hours * 60;

      returnValue.checkForAlert = minFractions <= 10;

      if (!returnValue.found) {
        returnValue.found = true;
        returnValue.age = hours;
      } else {
        if (hours >= 0 && hours < returnValue.age) {
          returnValue.age = hours;
          if (treatment.notes) {
            returnValue.message = treatment.notes;
          } else {
            returnValue.message = '';
          }
        }
      }
    }
  });

  return returnValue;
};

cage.updateVisualisation = function updateVisualisation (sbx) {

  var cannulaInfo = cage.findLatestTimeChange(sbx);

  var info = [{label: 'Inserted:', value: moment(cannulaInfo.treatmentDate).format('lll')}];
  if (cannulaInfo.message !== '') { info.push({label: 'Notes:', value: cannulaInfo.message}); }

  var shownAge = cannulaInfo.found ? cannulaInfo.age : 'n/a ';

  sbx.pluginBase.updatePillText(cage, {
    value: shownAge + 'h'
    , label: 'CAGE'
    , info: info
  });
};<commit_msg>Fixes === comparison between a string and a number<commit_after>'use strict';

var _ = require('lodash');
var moment = require('moment');

var cage = {
  name: 'cage'
  , label: 'Cannula Age'
  , pluginType: 'pill-minor'
};

function init() {
  return cage;
}

module.exports = init;

cage.getPrefs = function getPrefs(sbx) {
  // CAGE_INFO = 44 CAGE_WARN=48 CAGE_URGENT=70
  return {
    'info' : sbx.extendedSettings.info ? sbx.extendedSettings.info : 44,
    'warn' : sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 48,
    'urgent' : sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 72,
    'enableAlerts' : sbx.extendedSettings.enableAlerts
  };
};

cage.checkNotifications = function checkNotifications(sbx) {

  var cannulaInfo = cage.findLatestTimeChange(sbx);
  var prefs = cage.getPrefs(sbx);

  if (!prefs.enableAlerts || !cannulaInfo.checkForAlert) { return; }

  var sendNotification = false;
  var title = 'Cannula age ' + cannulaInfo.age +' hours';
  var sound = 'incoming';
  var msg = ', change due soon';
  var level = 1;

  if (cannulaInfo.age === Number(prefs.info)) { sendNotification = true; }
  if (cannulaInfo.age === Number(prefs.warn)) { sendNotification = true; msg = ', time to change'; }
  if (cannulaInfo.age === Number(prefs.urgent)) { sendNotification = true; msg = ', change overdue!'; sound = 'persistent'; level = 2;}

  if (sendNotification) {
    sbx.notifications.requestNotify({
      level: level
      , title: title
      , message: title + msg
      , pushoverSound: sound
      , plugin: cage
      , debug: {
        cannulaInfo: cannulaInfo
      }
    });
  }
};

cage.findLatestTimeChange = function findLatestTimeChange(sbx) {

  var returnValue = {'message':'', 'found': false, 'age': 0, 'treatmentDate': null, 'checkForAlert': false};

  var prevDate = 0;

  _.forEach(sbx.data.treatments, function eachTreatment (treatment) {
    var treatmentDate = treatment.mills;
    if (treatment.eventType === 'Site Change' && treatmentDate > prevDate && treatmentDate <= sbx.time) {

      prevDate = treatmentDate;
      returnValue.treatmentDate = treatmentDate;

      //allow for 10 minute period after a full hour during which we'll alert the user
      var a = moment(sbx.time);
      var b = moment(returnValue.treatmentDate);
      var hours = a.diff(b,'hours');
      var minFractions = a.diff(b,'minutes') - hours * 60;

      returnValue.checkForAlert = minFractions <= 10;

      if (!returnValue.found) {
        returnValue.found = true;
        returnValue.age = hours;
      } else {
        if (hours >= 0 && hours < returnValue.age) {
          returnValue.age = hours;
          if (treatment.notes) {
            returnValue.message = treatment.notes;
          } else {
            returnValue.message = '';
          }
        }
      }
    }
  });

  return returnValue;
};

cage.updateVisualisation = function updateVisualisation (sbx) {

  var cannulaInfo = cage.findLatestTimeChange(sbx);

  var info = [{label: 'Inserted:', value: moment(cannulaInfo.treatmentDate).format('lll')}];
  if (cannulaInfo.message !== '') { info.push({label: 'Notes:', value: cannulaInfo.message}); }

  var shownAge = cannulaInfo.found ? cannulaInfo.age : 'n/a ';

  sbx.pluginBase.updatePillText(cage, {
    value: shownAge + 'h'
    , label: 'CAGE'
    , info: info
  });
};<|endoftext|>


<commit_before>var browserslist = require('../')

it('selects defaults by keywords', () => {
  expect(browserslist('defaults, ie 6'))
    .toEqual(browserslist(browserslist.defaults.concat(['ie 6'])))
})
<commit_msg>Improve defaults query tests<commit_after>var browserslist = require('../')

it('selects defaults by keywords', () => {
  expect(browserslist('defaults, ie 6'))
    .toEqual(browserslist(browserslist.defaults.concat(['ie 6'])))
})

it('selects defaults case insensitive', () => {
  expect(browserslist('Defaults')).toEqual(browserslist(browserslist.defaults))
})
<|endoftext|>


<commit_before>var combine = require('gulp-util').combine,
    gulpFilter = require('gulp-filter'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    bower = require('gulp-bower'),
    bowerSrc = require('gulp-bower-src'),
    cssmin = require('gulp-minify-css'),
    watch = require('gulp-watch'),
    changed = require('gulp-changed'),
    gulp = require('gulp'),
    es = require('event-stream');

var paths = {
    scripts: ['build/tmp/static/run.js'],
    css: {
        files: ['src/css/main.css'],
        root: 'src/css'
    },
    images: ['src/img/**/*'],
    assets: ['src/*.txt','src/*.html','src/font*/**','src/css*/filterable-list.css'],
    dest: './dist/'
};

// install bower dependencies listed in "bower.json" in ./src/lib
gulp.task('bower-install', function(){
    return bower().pipe(gulp.dest('./src/lib'));
});

// concat and minify CSS files
gulp.task('minify-css', function() {
    return gulp.src(paths.css.files)
        .pipe(cssmin({root:paths.css.root}))
        .pipe(gulp.dest(paths.dest+'css'));
});

// uglify JavaScript source files
gulp.task('minify-scripts', function() {
    return gulp.src(paths.scripts)
        .pipe(uglify())
        .pipe(gulp.dest(paths.dest));
});

// optimize images
gulp.task('optim-img', function() {
    return gulp.src(paths.images)
        .pipe(imagemin())
        .pipe(gulp.dest(paths.dest+'img'));
});

// copy main bower files (see bower.json) and optimize js
gulp.task('bower-files', function() {
    var filter = gulpFilter(["**/*.js", "!**/*.min/js"]);
    return bowerSrc()
        .pipe(filter)
        .pipe(uglify())
        .pipe(filter.restore())
        .pipe(gulp.dest(paths.dest+'lib'));
})

// copy assets
gulp.task('copy-assets', function() {
    return gulp.src(paths.assets)
        .pipe(gulp.dest(paths.dest));
})

gulp.task('default', ['bower-install'], function(){ });

gulp.task('build', ['bower-install', 'minify-css', 'minify-scripts', 'optim-img', 'copy-assets', 'bower-files'], function(){ });

// temp tasks, until live reloading is in configured
gulp.task('bower-gradle', function() {
    return bowerSrc()
        .pipe(gulp.dest('../sagan-site/build/resources/main/static/'+'lib'));
})

gulp.task('watch', ['bower-install', 'bower-gradle'], function(){
    return gulp.src(['src/**', '!src/lib/**'])
        .pipe(watch())
        .pipe(gulp.dest('../sagan-site/build/resources/main/static/'))
});
<commit_msg>add guide.css as a separate CSS file<commit_after>var combine = require('gulp-util').combine,
    gulpFilter = require('gulp-filter'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    bower = require('gulp-bower'),
    bowerSrc = require('gulp-bower-src'),
    cssmin = require('gulp-minify-css'),
    watch = require('gulp-watch'),
    changed = require('gulp-changed'),
    gulp = require('gulp'),
    es = require('event-stream');

var paths = {
    scripts: ['build/tmp/static/run.js'],
    css: {
        files: ['src/css/main.css', 'src/css/guide.css'],
        root: 'src/css'
    },
    images: ['src/img/**/*'],
    assets: ['src/*.txt','src/*.html','src/font*/**','src/css*/filterable-list.css'],
    dest: './dist/'
};

// install bower dependencies listed in "bower.json" in ./src/lib
gulp.task('bower-install', function(){
    return bower().pipe(gulp.dest('./src/lib'));
});

// concat and minify CSS files
gulp.task('minify-css', function() {
    return gulp.src(paths.css.files)
        .pipe(cssmin({root:paths.css.root}))
        .pipe(gulp.dest(paths.dest+'css'));
});

// uglify JavaScript source files
gulp.task('minify-scripts', function() {
    return gulp.src(paths.scripts)
        .pipe(uglify())
        .pipe(gulp.dest(paths.dest));
});

// optimize images
gulp.task('optim-img', function() {
    return gulp.src(paths.images)
        .pipe(imagemin())
        .pipe(gulp.dest(paths.dest+'img'));
});

// copy main bower files (see bower.json) and optimize js
gulp.task('bower-files', function() {
    var filter = gulpFilter(["**/*.js", "!**/*.min/js"]);
    return bowerSrc()
        .pipe(filter)
        .pipe(uglify())
        .pipe(filter.restore())
        .pipe(gulp.dest(paths.dest+'lib'));
})

// copy assets
gulp.task('copy-assets', function() {
    return gulp.src(paths.assets)
        .pipe(gulp.dest(paths.dest));
})

gulp.task('default', ['bower-install'], function(){ });

gulp.task('build', ['bower-install', 'minify-css', 'minify-scripts', 'optim-img', 'copy-assets', 'bower-files'], function(){ });

// temp tasks, until live reloading is in configured
gulp.task('bower-gradle', function() {
    return bowerSrc()
        .pipe(gulp.dest('../sagan-site/build/resources/main/static/'+'lib'));
})

gulp.task('watch', ['bower-install', 'bower-gradle'], function(){
    return gulp.src(['src/**', '!src/lib/**'])
        .pipe(watch())
        .pipe(gulp.dest('../sagan-site/build/resources/main/static/'))
});
<|endoftext|>


<commit_before>var braintree = require("braintree");

function showTransactionSummary(userData, scale) {
	var env = userData.env === "sandbox" ?  : braintree.Environment.Production;

	console.log("Showing transactions per " + scale);

	var startDate = new Date();
	var endDate = new Date();

	if(scale === "hour"){
		startDate.setDate(startDate.getDate() - 1);
		endDate.setDate(startDate.getDate() + 1);
	} else if(scale === "day") {
		startDate.setMonth(startDate.getMonth() - 1);
		endDate.setMonth(startDate.getMonth() + 1);
	} else { // Month
		startDate.setFullYear(startDate.getFullYear() - 1);
		endDate.setFullYear(startDate.getFullYear() + 1);
	}

	for(var i = 0; i < userData.merchants.length; i += 1) {
		var gateway = braintree.connect({
			environment: env,
			merchantId: userData.merchants[i],
			publicKey: userData.publicKey,
			privateKey: userData.privateKey
		});

		var sums = {};

		// Show summary
		var stream = gateway.transaction.search(function (search) {
			search.createdAt().min(startDate);
			search.createdAt().max(endDate);
		});

		stream.on("data", function (transaction) {
			var date = "";
			if(scale === "hour"){
				date = transaction.createdAt.substring(0, 13);
			} else if(scale === "day") {
				date = transaction.createdAt.substring(0, 10);
			} else { // Month
				date = transaction.createdAt.substring(0, 7);
			}

			sums[transaction.merchantAccountId] = sums[transaction.merchantAccountId] || {currency: transaction.currencyIsoCode};
			if(sums[transaction.merchantAccountId][date] === undefined) {
				sums[transaction.merchantAccountId][date] = parseFloat(transaction.amount);
			} else {
				sums[transaction.merchantAccountId][date] += parseFloat(transaction.amount);
			}
		});

		stream.on("end", function () {

			for(var merchantAccountId in sums){
				console.log("\nMerchant: " + merchantAccountId);
				console.log("Currency: " + sums[merchantAccountId].currency + "\n");

				var width = 80;

				// Autoscale graph
				var max = 0.0;
				for (var date in sums[merchantAccountId]) {
					if (sums[merchantAccountId].hasOwnProperty(date)) {
						if(sums[merchantAccountId][date] > max){
							max = sums[merchantAccountId][date];
						}
					}
				}

				for(var i = 20; i >= 0; i -= 1) {
					var line = "";

					if(i % 5 === 0) {
						var str = "" + (i/5) * max / 4;
						var pad = "         ";
						line += pad.substring(0, pad.length - str.length) + str + "|";
					} else {
						line += "         |";
					}

					if(scale === "hour"){
						for(var h = 0; h < 24; h += 1){

							var date = new Date(endDate);
							date.setHours(date.getHours() - 23 + h);
							var dateStr = date.toISOString().substring(0, 13);

							if(sums[merchantAccountId][dateStr] === undefined){
								sums[merchantAccountId][dateStr] = 0;
								line += "...";
							} else if(sums[merchantAccountId][dateStr] > (i * max /20) || sums[merchantAccountId][dateStr] === max){
								line += "XXX";
							} else {
								line += "...";
							}
						}
					} else if(scale === "day") {
						for(var d = 0; d < 31; d += 1){
							var date = new Date(endDate);
							date.setDate(date.getDate() - 30 + d);
							var dateStr = date.toISOString().substring(0, 10);

							if(sums[merchantAccountId][dateStr] === undefined){
								sums[merchantAccountId][dateStr] = 0;
								line += "..";
							} else if(sums[merchantAccountId][dateStr] > (i * max /20) || sums[merchantAccountId][dateStr] === max){
								line += "XX";
							} else {
								line += "..";
							}
						}
					} else {
						for(var m = 0; m < 12; m += 1){
							var date = new Date(endDate);
							date.setMonth(date.getMonth() - 11 + m);
							var dateStr = date.toISOString().substring(0, 7);

							if(sums[merchantAccountId][dateStr] === undefined){
								sums[merchantAccountId][dateStr] = 0;
								line += ".....";
							} else if(sums[merchantAccountId][dateStr] > (i * max /20) || sums[merchantAccountId][dateStr] === max){
								line += "XXXXX";
							} else {
								line += ".....";
							}
						}
					}

					console.log(line);
				}

				// Now print bottom line
				var line = "          ";
				var num = 0;
				if(scale === "hour"){
					num = 24 * 3;
				} else if(scale === "day") {
					num = 31 * 2;
				} else {
					num = 12 * 5;
				}
				for(var i = 0; i < num; i += 1){
					line += "¯";
				}

				console.log(line);

				// Now print the labels
				if(scale === "hour"){
					line = "     hour ";
					for(var h = 0; h < 24; h += 1){
						var date = new Date(endDate);
						date.setHours(date.getHours() - 23 + h);
						line += date.toISOString().substring(11, 13) + " ";
					}
				} else if(scale === "day") {
					line = "      day ";
					for(var d = 0; d < 31; d += 1){
						if(d % 2 == 0) {
							var date = new Date(endDate);
							date.setDate(date.getDate() - 30 + d);
							line += date.toISOString().substring(8, 10);
						} else {
							line += "  ";
						}
					}
				} else {
					line = "    month ";
					for(var m = 0; m < 12; m += 1){
						var date = new Date(endDate);
						date.setMonth(date.getMonth() - 11 + m);
						line += " " + date.toISOString().substring(5, 7) + "  ";
					}
				}

				console.log(line);

			}

		});
	}
}

module.exports = {
	showTransactionSummary: showTransactionSummary
}<commit_msg>Worked<commit_after>var braintree = require("braintree");

function showTransactionSummary(userData, scale) {
	var env = userData.env === "sandbox" ? braintree.Environment.Sandbox : braintree.Environment.Production;

	console.log("Showing transactions per " + scale);

	var startDate = new Date();
	var endDate = new Date();

	if(scale === "hour"){
		startDate.setDate(startDate.getDate() - 1);
		endDate.setDate(startDate.getDate() + 1);
	} else if(scale === "day") {
		startDate.setMonth(startDate.getMonth() - 1);
		endDate.setMonth(startDate.getMonth() + 1);
	} else { // Month
		startDate.setFullYear(startDate.getFullYear() - 1);
		endDate.setFullYear(startDate.getFullYear() + 1);
	}

	for(var i = 0; i < userData.merchants.length; i += 1) {
		var gateway = braintree.connect({
			environment: env,
			merchantId: userData.merchants[i],
			publicKey: userData.publicKey,
			privateKey: userData.privateKey
		});

		var sums = {};

		// Show summary
		var stream = gateway.transaction.search(function (search) {
			search.createdAt().min(startDate);
			search.createdAt().max(endDate);
		});

		stream.on("data", function (transaction) {
			var date = "";
			if(scale === "hour"){
				date = transaction.createdAt.substring(0, 13);
			} else if(scale === "day") {
				date = transaction.createdAt.substring(0, 10);
			} else { // Month
				date = transaction.createdAt.substring(0, 7);
			}

			sums[transaction.merchantAccountId] = sums[transaction.merchantAccountId] || {currency: transaction.currencyIsoCode};
			if(sums[transaction.merchantAccountId][date] === undefined) {
				sums[transaction.merchantAccountId][date] = parseFloat(transaction.amount);
			} else {
				sums[transaction.merchantAccountId][date] += parseFloat(transaction.amount);
			}
		});

		stream.on("end", function () {

			for(var merchantAccountId in sums){
				console.log("\nMerchant: " + merchantAccountId);
				console.log("Currency: " + sums[merchantAccountId].currency + "\n");

				var width = 80;

				// Autoscale graph
				var max = 0.0;
				for (var date in sums[merchantAccountId]) {
					if (sums[merchantAccountId].hasOwnProperty(date)) {
						if(sums[merchantAccountId][date] > max){
							max = sums[merchantAccountId][date];
						}
					}
				}

				for(var i = 20; i >= 0; i -= 1) {
					var line = "";

					if(i % 5 === 0) {
						var str = "" + (i/5) * max / 4;
						var pad = "         ";
						line += pad.substring(0, pad.length - str.length) + str + "|";
					} else {
						line += "         |";
					}

					if(scale === "hour"){
						for(var h = 0; h < 24; h += 1){

							var date = new Date(endDate);
							date.setHours(date.getHours() - 23 + h);
							var dateStr = date.toISOString().substring(0, 13);

							if(sums[merchantAccountId][dateStr] === undefined){
								sums[merchantAccountId][dateStr] = 0;
								line += "...";
							} else if(sums[merchantAccountId][dateStr] > (i * max /20) || sums[merchantAccountId][dateStr] === max){
								line += "XXX";
							} else {
								line += "...";
							}
						}
					} else if(scale === "day") {
						for(var d = 0; d < 31; d += 1){
							var date = new Date(endDate);
							date.setDate(date.getDate() - 30 + d);
							var dateStr = date.toISOString().substring(0, 10);

							if(sums[merchantAccountId][dateStr] === undefined){
								sums[merchantAccountId][dateStr] = 0;
								line += "..";
							} else if(sums[merchantAccountId][dateStr] > (i * max /20) || sums[merchantAccountId][dateStr] === max){
								line += "XX";
							} else {
								line += "..";
							}
						}
					} else {
						for(var m = 0; m < 12; m += 1){
							var date = new Date(endDate);
							date.setMonth(date.getMonth() - 11 + m);
							var dateStr = date.toISOString().substring(0, 7);

							if(sums[merchantAccountId][dateStr] === undefined){
								sums[merchantAccountId][dateStr] = 0;
								line += ".....";
							} else if(sums[merchantAccountId][dateStr] > (i * max /20) || sums[merchantAccountId][dateStr] === max){
								line += "XXXXX";
							} else {
								line += ".....";
							}
						}
					}

					console.log(line);
				}

				// Now print bottom line
				var line = "          ";
				var num = 0;
				if(scale === "hour"){
					num = 24 * 3;
				} else if(scale === "day") {
					num = 31 * 2;
				} else {
					num = 12 * 5;
				}
				for(var i = 0; i < num; i += 1){
					line += "¯";
				}

				console.log(line);

				// Now print the labels
				if(scale === "hour"){
					line = "     hour ";
					for(var h = 0; h < 24; h += 1){
						var date = new Date(endDate);
						date.setHours(date.getHours() - 23 + h);
						line += date.toISOString().substring(11, 13) + " ";
					}
				} else if(scale === "day") {
					line = "      day ";
					for(var d = 0; d < 31; d += 1){
						if(d % 2 == 0) {
							var date = new Date(endDate);
							date.setDate(date.getDate() - 30 + d);
							line += date.toISOString().substring(8, 10);
						} else {
							line += "  ";
						}
					}
				} else {
					line = "    month ";
					for(var m = 0; m < 12; m += 1){
						var date = new Date(endDate);
						date.setMonth(date.getMonth() - 11 + m);
						line += " " + date.toISOString().substring(5, 7) + "  ";
					}
				}

				console.log(line);

			}

		});
	}
}

module.exports = {
	showTransactionSummary: showTransactionSummary
}<|endoftext|>


<commit_before>import test from 'tape'
import { shallow } from 'enzyme'
import React from 'react'

import RepoHeader from './header'

test('<RepoHeader /> with no props should throw error', t => {
  t.throws(() => {
    shallow(<RepoHeader />)
  })
  t.end()
})
<commit_msg>Add types<commit_after>// @flow

import test from 'tape'
import { shallow } from 'enzyme'
import React from 'react'

import RepoHeader from './header'

test('<RepoHeader /> with no props should throw error', t => {
  t.throws(() => {
    shallow(<RepoHeader {...({}: any)} />)
  })
  t.end()
})
<|endoftext|>


<commit_before>describe('modules.Filters', function() {

  //===================================================
  // Filters
  //===================================================
  describe('cdb.admin.models.Filters', function() {

    var filters, table;
    beforeEach(function() {
      table = TestUtil.createTable('test');
      filters = new cdb.admin.models.Filters(null, {
        table: table
      });
    });

    it("should generate correct model for column type", function() {
      filters.reset([{
          'column_type': 'number',
          'column': 'test'
        }, {
          'column_type': 'string',
          'column': 'teststr'
        }]);

      expect(filters.models[0] instanceof cdb.admin.models.Filter).toEqual(true);
      expect(filters.models[1] instanceof cdb.admin.models.FilterDiscrete).toEqual(true);

    });
  });

  //===================================================
  // Filter
  //===================================================
  describe('cdb.admin.models.Filter', function() {

    var model, table, hist, bounds;

    beforeEach(function() {
      hist = [];
      bounds = {}

      bounds.upper = 10;
      bounds.lower = 3;
      bounds.bucket_size = 1
      for(var i = 0; i < 10; ++i) {
        hist.push(i);
      }
      table = TestUtil.createTable('test');
      stub = sinon.stub(table.data(), 'histogram')
      stub.yields(hist, bounds);
      model = new cdb.admin.models.Filter({
        table: table,
        column: 'c'
      });

    });

    it("should have a table", function() {
      expect(model.table).toEqual(table);
      expect(model.get('table')).toEqual(undefined);
    });

    it("should set bounds", function() {
      expect(model.get('lower')).toEqual(3);
      expect(model.get('upper')).toEqual(10);
    });

    it("should return the condition", function() {
      expect(model.getSQLCondition()).toEqual(" (c >= 3 AND c <= 10) ");
    });

    it("should fecth data when the table is saved", function() {
      spyOn(model, '_fetchHist');
      table.trigger('data:saved');
      expect(model._fetchHist).toHaveBeenCalled();
    });

    it("should adjust the bounds", function() {
      bounds.lower = 4;
      bounds.upper = 9;
      table.trigger('data:saved');
      expect(model.get('lower')).toEqual(4);
      expect(model.get('upper')).toEqual(9);

      bounds.lower = 1;
      bounds.upper = 12;
      table.trigger('data:saved');
      expect(model.get('lower')).toEqual(4);
      expect(model.get('upper')).toEqual(9);

    });

    it("should raise an error when histogram can't be generated", function() {
      spy = sinon.spy()
      model.bind('error', spy);
      stub.yields(null, null);
      table.trigger('data:saved');
      expect(spy.called).toEqual(true);
    });

    it("should serialize to json", function() {
      expect(model.toJSON()).toEqual({
        column: 'c',
        upper: 10,
        lower: 3
      });
    });

  });

  describe('FilterDiscrete', function() {
    var model, table, hist, stub;

    beforeEach(function() {

      hist = { };

      hist.rows = [
        { bucket: 'test1', value: 20 },
        { bucket: 'test2', value: 30 }
      ];

      table = TestUtil.createTable('test');
      stub  = sinon.stub(table.data(), 'discreteHistogram')

      stub.yields(hist);

      model = new cdb.admin.models.FilterDiscrete({
        table: table,
        column: 'c'
      });

    });

    it("should return sql condition", function() {
      var sql = model.getSQLCondition();
      expect(sql).toEqual("(COALESCE(c, 'null') IN ('test1','test2')) ");
    });

    it("should return sql with free_text", function() {
      model.set("free_text", "Valladolid");
      var sql = model.getSQLCondition();
      expect(sql).toEqual("c ILIKE '%Valladolid%' ");
    });

    it("should return a valid sql when free_text is empty", function() {
      model.set("free_text", "");
      var sql = model.getSQLCondition();
      expect(sql).toEqual("(COALESCE(c, 'null') IN ('test1','test2')) ");
    });

    it("should return sql condition with selected only", function() {
      model.items.models[0].set('selected', false);
      var sql = model.getSQLCondition();
      expect(sql).toEqual("(COALESCE(c, 'null') IN ('test2')) ");
    });

    it("should raise an error when histogram can't be generated", function() {
      spy = sinon.spy()
      model.bind('error', spy);
      stub.yields(null);
      table.trigger('data:saved');
      expect(spy.called).toEqual(true);
    });

    it("should keep selected values", function() {
      model = new cdb.admin.models.FilterDiscrete({
        table: table,
        column: 'c',
        items: [{
          bucket: 'test',
          value: 1,
          selected: false
        }]
      });

      hist = {};
      hist.rows = [
        {bucket: 'test', value: 20},
      ];

      stub.yields(hist);
      table.trigger('data:saved');
      expect(model.items.where({ bucket: 'test'})[0].get('selected')).toEqual(true)

    });
    it("should serialize to json", function() {
      expect(model.toJSON()).toEqual({
        column: 'c',
        items: [
          { bucket: 'test1', value: 20, selected: true },
          { bucket: 'test2', value: 30, selected: true }
        ]
      });
    });
  });
});

<commit_msg>Fixes spec<commit_after>describe('modules.Filters', function() {

  //===================================================
  // Filters
  //===================================================
  describe('cdb.admin.models.Filters', function() {

    var filters, table;
    beforeEach(function() {
      table = TestUtil.createTable('test');
      filters = new cdb.admin.models.Filters(null, {
        table: table
      });
    });

    it("should generate correct model for column type", function() {
      filters.reset([{
          'column_type': 'number',
          'column': 'test'
        }, {
          'column_type': 'string',
          'column': 'teststr'
        }]);

      expect(filters.models[0] instanceof cdb.admin.models.Filter).toEqual(true);
      expect(filters.models[1] instanceof cdb.admin.models.FilterDiscrete).toEqual(true);

    });
  });

  //===================================================
  // Filter
  //===================================================
  describe('cdb.admin.models.Filter', function() {

    var model, table, hist, bounds;

    beforeEach(function() {
      hist = [];
      bounds = {}

      bounds.upper = 10;
      bounds.lower = 3;
      bounds.bucket_size = 1
      for(var i = 0; i < 10; ++i) {
        hist.push(i);
      }
      table = TestUtil.createTable('test');
      stub = sinon.stub(table.data(), 'histogram')
      stub.yields(hist, bounds);
      model = new cdb.admin.models.Filter({
        table: table,
        column: 'c'
      });

    });

    it("should have a table", function() {
      expect(model.table).toEqual(table);
      expect(model.get('table')).toEqual(undefined);
    });

    it("should set bounds", function() {
      expect(model.get('lower')).toEqual(3);
      expect(model.get('upper')).toEqual(10);
    });

    it("should return the condition", function() {
      expect(model.getSQLCondition()).toEqual(" (c >= 3 AND c <= 10) ");
    });

    it("should fecth data when the table is saved", function() {
      spyOn(model, '_fetchHist');
      table.trigger('data:saved');
      expect(model._fetchHist).toHaveBeenCalled();
    });

    it("should adjust the bounds", function() {
      bounds.lower = 4;
      bounds.upper = 9;
      table.trigger('data:saved');
      expect(model.get('lower')).toEqual(4);
      expect(model.get('upper')).toEqual(9);

      bounds.lower = 1;
      bounds.upper = 12;
      table.trigger('data:saved');
      expect(model.get('lower')).toEqual(4);
      expect(model.get('upper')).toEqual(9);

    });

    it("should raise an error when histogram can't be generated", function() {
      spy = sinon.spy()
      model.bind('error', spy);
      stub.yields(null, null);
      table.trigger('data:saved');
      expect(spy.called).toEqual(true);
    });

    it("should serialize to json", function() {
      expect(model.toJSON()).toEqual({
        column: 'c',
        upper: 10,
        lower: 3
      });
    });

  });

  describe('FilterDiscrete', function() {
    var model, table, hist, stub;

    beforeEach(function() {

      hist = { };

      hist.rows = [
        { bucket: 'test1', value: 20 },
        { bucket: 'test2', value: 30 }
      ];

      table = TestUtil.createTable('test');
      stub  = sinon.stub(table.data(), 'discreteHistogram')

      stub.yields(hist);

      model = new cdb.admin.models.FilterDiscrete({
        table: table,
        column: 'c'
      });

    });

    it("should return sql condition", function() {
      var sql = model.getSQLCondition();
      expect(sql).toEqual("(COALESCE(c, 'null') IN ('test1','test2')) ");
    });

    it("should return sql with free_text", function() {
      model.set("free_text", "Valladolid");
      var sql = model.getSQLCondition();
      expect(sql).toEqual("c ILIKE '%Valladolid%' ");
    });

    it("should return a valid sql when free_text is empty", function() {
      model.set("free_text", "");
      var sql = model.getSQLCondition();
      expect(sql).toEqual(" (true) ");
    });

    it("should return sql condition with selected only", function() {
      model.items.models[0].set('selected', false);
      var sql = model.getSQLCondition();
      expect(sql).toEqual("(COALESCE(c, 'null') IN ('test2')) ");
    });

    it("should raise an error when histogram can't be generated", function() {
      spy = sinon.spy()
      model.bind('error', spy);
      stub.yields(null);
      table.trigger('data:saved');
      expect(spy.called).toEqual(true);
    });

    it("should keep selected values", function() {
      model = new cdb.admin.models.FilterDiscrete({
        table: table,
        column: 'c',
        items: [{
          bucket: 'test',
          value: 1,
          selected: false
        }]
      });

      hist = {};
      hist.rows = [
        {bucket: 'test', value: 20},
      ];

      stub.yields(hist);
      table.trigger('data:saved');
      expect(model.items.where({ bucket: 'test'})[0].get('selected')).toEqual(true)

    });
    it("should serialize to json", function() {
      expect(model.toJSON()).toEqual({
        column: 'c',
        items: [
          { bucket: 'test1', value: 20, selected: true },
          { bucket: 'test2', value: 30, selected: true }
        ]
      });
    });
  });
});

<|endoftext|>


<commit_before>/**
 * Created by qiaodandan on 2016/11/8.
 */
//var stage = new createjs.Stage("gameView");
//
//var gameView = new createjs.Container();
//stage.addChild(gameView);
//
//var s = new createjs.Shape();
//s.graphics.beginFill("#FF0000");
//s.graphics.drawCircle(50,50,25);
//s.graphics.endFill();
//gameView.addChild(s);
//
//createjs.Ticker.setFPS(30);
//createjs.Ticker.addEventListener("tick",stage);

var stage = new createjs.Stage("gameView");
createjs.Ticker.setFPS(30);
createjs.Ticker.addEventListener("tick",stage);

var gameView = new createjs.Container();
gameView.x = 30;
gameView.y = 30;
stage.addChild(gameView);

var circleArr = [[],[],[],[],[],[],[],[],[]];
var currentCat;

function circleClicked(event){
  if (event.target.getCircleType() == 1){
    //不是猫并且不是已经走过的位置,能跳的位置
    event.target.setCircleType(2);
  }
                      //这里书写格式错误
  var leftCircle = circleArr[currentCat.indexX-1][currentCat.indexY];
  var rightCircle = circleArr[currentCat.indexX+1][currentCat.indexY];
  var leftTopCircle = circleArr[currentCat.indexX][currentCat.indexY-1];
  var rightTopCircle = circleArr[currentCat.indexX-1][currentCat.indexY-1];
  var leftBottomCircle = circleArr[currentCat.indexX][currentCat.indexY+1];
  var rightBobbtomCircle = circleArr[currentCat.indexX-1][currentCat.indexY+1]
//alert(leftCircle.getCircleType());
  //以下逻辑需要优化---------

  var tmpCircleArr = [];
  //alert(tmpCircleArr.count);
  if (leftCircle.getCircleType() == 1){
    tmpCircleArr.push(leftCircle);
  }
  if (rightCircle.getCircleType() == 1){
    tmpCircleArr.push(rightCircle);
  }
  if (leftTopCircle.getCircleType() == 1){
    tmpCircleArr.push(leftTopCircle);
  }
  if (rightTopCircle.getCircleType() == 1){
    tmpCircleArr.push(rightTopCircle);
  }
  if (leftBottomCircle.getCircleType() == 1){
    tmpCircleArr.push(leftBottomCircle);
  }
  if (rightBobbtomCircle.getCircleType() == 1){
    tmpCircleArr.push(rightBobbtomCircle);
  }
  if (tmpCircleArr.length==0||!tmpCircleArr.length)
  var tmpIndex = Math.floor(tmpCircleArr.length*Math.random());
 var tmpCircle = tmpCircleArr[tmpIndex];
  tmpCircle.setCircleType(3);
  currentCat.setCircleType(1);
  currentCat = tmpCircleArr[tmpIndex];

  //if (leftCircle.getCircleType() == 1){
  //  leftCircle.setCircleType(3);
  //  currentCat.setCircleType(1);
  //  currentCat = leftCircle;
  //}else if (rightCircle.getCircleType() == 1){
  //  rightCircle.setCircleType(3);
  //  currentCat.setCircleType(1);
  //  currentCat = rightCircle;
  //}else if (leftTopCircle.getCircleType() == 1){
  //  leftTopCircle.setCircleType(3);
  //  currentCat.setCircleType(1);
  //  currentCat = leftTopCircle;
  //}else if (rightTopCircle.getCircleType() == 1){
  //  rightTopCircle.setCircleType(3);
  //  currentCat.setCircleType(1);
  //  currentCat = rightTopCircle;
  //}else if (leftBottomCircle.getCircleType() == 1){
  //  leftBottomCircle.setCircleType(3);
  //  currentCat.setCircleType(1);
  //  currentCat = leftBottomCircle;
  //}else if (rightBobbtomCircle.getCircleType() == 1){
  //  rightBobbtomCircle.setCircleType(3);
  //  currentCat.setCircleType(1);
  //  currentCat = rightBobbtomCircle;
  //}else {
  //  alert("成功啦!")
  //}
  //优化结束---------
  if (currentCat.indexX==0||currentCat.indexX==8||currentCat.indexY==0||currentCat.indexY==8){
    alert("游戏结束,你输了!");
  }
}

function addCiecles(){

  for (var indexY = 0;indexY < 9;indexY ++){
    for (var indexX = 0;indexX < 9;indexX ++){
      var  c = new Circle();
      gameView.addChild(c);
      circleArr[indexX][indexY] = c;
      c.indexX = indexX;
      c.indexY = indexY;//忘记了这里
      c.x = indexY % 2 ? indexX * 55 + 25 : indexX * 55;
      c.y = indexY * 55;
      if (indexX == 4 && indexY == 4){
        c.setCircleType(3);//是猫
        currentCat = c;
      }
      c.addEventListener("click",circleClicked);
    }
  }
}

addCiecles();<commit_msg>围住神经喵游戏的初步完成，<commit_after>/**
 * Created by qiaodandan on 2016/11/8.
 */
//var stage = new createjs.Stage("gameView");
//
//var gameView = new createjs.Container();
//stage.addChild(gameView);
//
//var s = new createjs.Shape();
//s.graphics.beginFill("#FF0000");
//s.graphics.drawCircle(50,50,25);
//s.graphics.endFill();
//gameView.addChild(s);
//
//createjs.Ticker.setFPS(30);
//createjs.Ticker.addEventListener("tick",stage);

var stage = new createjs.Stage("gameView");
createjs.Ticker.setFPS(30);
createjs.Ticker.addEventListener("tick",stage);

var gameView = new createjs.Container();
gameView.x = 30;
gameView.y = 30;
stage.addChild(gameView);

var circleArr = [[],[],[],[],[],[],[],[],[]];
var currentCat;
var stepNo = 0;
function circleClicked(event){
  if (event.target.getCircleType() == 1){
    //不是猫并且不是已经走过的位置,能跳的位置
    event.target.setCircleType(2);
  }

  var leftCircle = circleArr[currentCat.indexX-1][currentCat.indexY];
  var rightCircle = circleArr[currentCat.indexX+1][currentCat.indexY];

  var leftBottomCircle = circleArr[currentCat.indexX][currentCat.indexY+1];
  var leftTopCircle = circleArr[currentCat.indexX][currentCat.indexY-1];

  var rightTopCircleOU = circleArr[currentCat.indexX-1][currentCat.indexY-1];
  var rightBobbtomCircleOU = circleArr[currentCat.indexX-1][currentCat.indexY+1]

  var rightTopCircleJS = circleArr[currentCat.indexX+1][currentCat.indexY-1];
  var rightBobbtomCircleJS = circleArr[currentCat.indexX+1][currentCat.indexY+1]

//alert(leftCircle.getCircleType());

  var tmpCircleArr = [];
  //alert(tmpCircleArr.count);
  if (leftCircle.getCircleType() == 1){
    tmpCircleArr.push(leftCircle);
  }
  if (rightCircle.getCircleType() == 1){
    tmpCircleArr.push(rightCircle);
  }
  if (leftTopCircle.getCircleType() == 1){
    tmpCircleArr.push(leftTopCircle);
  }
  if (leftBottomCircle.getCircleType() == 1){
    tmpCircleArr.push(leftBottomCircle);
  }
  if(currentCat.indexY%2==0){
    //偶数行
    if (rightTopCircleOU.getCircleType() == 1){
      tmpCircleArr.push(rightTopCircleOU);
    }
    if (rightBobbtomCircleOU.getCircleType() == 1){
      tmpCircleArr.push(rightBobbtomCircleOU);
    }
  }else {
    //奇数行
    if (rightTopCircleJS.getCircleType() == 1){
      tmpCircleArr.push(rightTopCircleJS);
    }
    if (rightBobbtomCircleJS.getCircleType() == 1){
      tmpCircleArr.push(rightBobbtomCircleJS);
    }
  }

  if (tmpCircleArr.length!=0){
    var tmpIndex = Math.floor(tmpCircleArr.length*Math.random(1));
    var tmpCircle = tmpCircleArr[tmpIndex];
    tmpCircle.setCircleType(3);
    currentCat.setCircleType(1);
    currentCat = tmpCircleArr[tmpIndex];
    stepNo = stepNo + 1;
  }else {
    alert("你赢了!一共用了"+(stepNo+1)+"步!");
  }

  if (currentCat.indexX==0||currentCat.indexX==8||currentCat.indexY==0||currentCat.indexY==8){
    alert("游戏结束,你输了!");
  }
}

function addCiecles(){

  for (var indexY = 0;indexY < 9;indexY ++){
    for (var indexX = 0;indexX < 9;indexX ++){
      var  c = new Circle();
      gameView.addChild(c);
      circleArr[indexX][indexY] = c;
      c.indexX = indexX;
      c.indexY = indexY;//忘记了这里
      c.x = indexY % 2 ? indexX * 55 + 25 : indexX * 55;
      c.y = indexY * 55;
      if (indexX == 4 && indexY == 4){
        c.setCircleType(3);//是猫
        currentCat = c;
      }
      c.addEventListener("click",circleClicked);
    }
  }
}

addCiecles();<|endoftext|>