<commit_before>/**
 * notus v0.1.0
 *
 * Author: Kushal Pandya <kushalspandya@gmail.com> (https://doublslash.com)
 * Date: 12 February, 2016
 *
 * Main Notus Script.
 */

(function() {
    "use strict";

    var self = this,
        hasRequire = (typeof require !== 'undefined'),
        old_notus = self.notus,
        _n = {},
        notus;

    var htmlEntityMap = {
            '&': '&',
            '<': '<',
            '>': '>',
            '"': '"',
            "'": ''',
            '/': '/',
            '`': '`',
            '=': '='
        },
        notusTypeMap = {
            'popup': 'popup',
            'toast': 'toast',
            'snackbar': 'snackbar'
        },
        positionShorts = {
            'top-left': 'tl',
            'top-right': 'tr',
            'bottom-left': 'bl',
            'bottom-right': 'br',
            'top': 'top',
            'bottom': 'bottom',
            'tl': 'tl',
            'tr': 'tr',
            'bl': 'bl',
            'br': 'br',
            't': 'top',
            'b': 'bottom'
        },
        alertTypeMap = {
            'success': 'success',
            'failure': 'failure',
            'warning': 'warning',
            'custom': 'custom',
            'none': ''
        };

    var fnValidateConfig,
        fnGetParentClassList,
        fnCreateNotusContainer,
        fnGetEntryAnimatorStyle,
        fnGetExitAnimatorStyle,
        fnBindCloseHandler,
        fnBindCloseListener,
        fnBindActionHandler,
        fnCreateNotusEl;

    /**
     * Local Utility API.
     */
    _n = {
        /**
         * Generate timestamp based unique ID.
         */
        genId: function() {
            return 'notus-' + Math.floor(Math.random() * 10000000000000001);
        },

        /**
         * Simple Object extender (similar to jQuery's $.extend()).
         * courtesy; SO < http://stackoverflow.com/a/11197343/414749 >
         */
        extend: function() {
            var key, i;

            for (i = 1; i < arguments.length; i++)
            {
                for (key in arguments[i])
                {
                    if (arguments[i].hasOwnProperty(key))
                        arguments[0][key] = arguments[i][key];
                }
            }

            return arguments[0];
        },

        /**
         * String formatter.
         * Accepts first param as target string, and following params as strings to
         * replace placeholders (eg; {0}).
         * courtesy: SO <http://stackoverflow.com/a/1038930/414749>
         */
        format: function() {
            var str = arguments[0],
                reg,
                i;

            for (i = 0; i < arguments.length - 1; i++) {
                reg = new RegExp("\\{" + i + "\\}", "gm");
                str = str.replace(reg, arguments[i + 1]);
            }

            return str;
        },

        /**
         * Escapes HTML tags present in the string.
         * courtesy: Mustache.js < https://github.com/janl/mustache.js >
         */
        escapeHtml: function(string) {
            return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
                return htmlEntityMap[s];
            });
        },

        /**
         * Removes an Element from the DOM.
         * This method uses el.remove() internally in supported browsers
         * except for IE11, where it is using el.parent.removeChild().
         * courtesy: MDN < https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove >
         */
        removeEl: function(el) {
            if (typeof el.remove === 'function')
                el.remove();
            else if (el.parentElement)
                el.parentElement.removeChild(el);
        }
    };

    /** Helpers Begin **/

    /**
     * Validate user configuration and throw Errors accordingly.
     */
    fnValidateConfig = function(config) {
        if (notusTypeMap[config.notusType] === undefined)
            throw new Error('Unknown value for notusType');

        if (positionShorts[config.notusPosition] === undefined)
            throw new Error('Unknown value for notusPosition');

        if (alertTypeMap[config.alertType] === undefined)
            throw new Error('Unknown value for alertType');

        if (config.closeHandler &&
            typeof config.closeHandler !== "function")
            throw new Error("closeHandler is not a function");

        if (config.notusType === 'snackbar' &&
            config.actionable &&
            typeof config.actionHandler !== "function")
            throw new Error("actionHandler is not a function");
    };

    /**
     * Creates list of CSS classes which are to be applied main Notus element based on config.
     */
    fnGetParentClassList = function(config) {
        var classList = [],
            type = config.notusType,
            alertType = config.alertType;

        classList.push('notus');

        classList.push('notus-type-' + notusTypeMap[type]);

        if (alertTypeMap[alertType])
            classList.push('notus-alert-' + alertTypeMap[alertType]);

        if (config.themeClass)
            classList.push(config.themeClass);

        return classList;
    };

    /**
     * Creates new (or returns existing) Notus container elements to house Notus for a particular position.
     * Note that it creates containers for all position types that Notus supports, and it will return
     * reference to an existing container if it was previously created.
     */
    fnCreateNotusContainer = function(config) {
        var type = config.notusType,
            position = config.notusPosition,
            containerCls = ['notus-container'],
            containerEl;

        containerCls.push('notus-position-' + positionShorts[position]);

        if (type !== 'popup')
            containerCls.push((type === 'toast') ? 'notus-container-toast' : 'notus-container-snackbar');

        containerEl = document.querySelector('.' + containerCls.join('.'));

        if (!containerEl)
        {
            containerEl = document.createElement('div');
            containerEl.setAttribute('class', containerCls.join(' '));
            document.body.appendChild(containerEl);
        }

        return containerEl;
    };

    /**
     * Binds Timer event on each Notus that has autoClose set to true.
     * the timer waits for autoCloseDuration to finish before closing & destroying Notus.
     */
    fnBindCloseListener = function(config, notusEl) {
        var type = config.notusType,
            animationType = config.animationType;

        setTimeout(function() {
            notusEl.setAttribute('style', fnGetExitAnimatorStyle(config).join(';'));

            if (animationType === 'slide')
            {
                notusEl.classList.remove('notus-slide-in');
                notusEl.classList.add('notus-slide-out');
            }
            else
            {
                notusEl.classList.remove('notus-fade-in');
                notusEl.classList.add('notus-fade-out');
            }

            setTimeout(function() {
                _n.removeEl(notusEl);
            }, config.animationDuration);

        }, config.autoCloseDuration);
    };

    /**
     * Binds Mouse Click event handler on Close element of Notus.
     * it optionally supports closeHandler function that gets called before Notus is destroyed.
     * if closeHandler returns false (boolean false), Notus will be prevented from destroying.
     */
    fnBindCloseHandler = function(config, notusEl) {
        var closeEl = notusEl.querySelector('.notus-close');

        closeEl.onclick = function(e) {
            var doRemove = false,
                handlerReturnVal;

            if (config.closeHandler)
            {
                handlerReturnVal = config.closeHandler.apply(this, arguments);
                doRemove = (typeof handlerReturnVal === 'boolean') ? handlerReturnVal : true;
            }
            else
                doRemove = true;

            if (doRemove)
                _n.removeEl(notusEl);
        };
    };

    /**
     * Binds Mouse Click event handler on action element of Notus type Snackbar.
     */
    fnBindActionHandler = function(config, notusEl) {
        var actionEl = notusEl.querySelector('.notus-action');

        actionEl.onclick = function(e) {
            config.actionHandler.apply(this, arguments);
            _n.removeEl(notusEl);
        };
    };

    /**
     * Updates and applies Entry Animation CSS styles and Classes of Notus Element based on config.
     */
    fnGetEntryAnimatorStyle = function(config) {
        var type = config.notusType,
            animationType = config.animationType,
            animationFunction = config.animationFunction,
            animationDuration = config.animationDuration / 1000,
            isSlide = animationType === 'slide',
            animators = [];

        if (type === 'popup')
            animators.push(isSlide ? 'transform: translateX(0%)' : 'opacity: 1');
        else
            animators.push(isSlide ? 'transform: translateY(0%)' : 'opacity: 1');

        if (animationDuration > 0)
            animators.push(_n.format('animation-duration: {0}s', animationDuration));

        if (animationFunction)
            animators.push(_n.format('animation-timing-function: {0}', animationFunction));

        return animators;
    };

    /**
     * Updates and applies Exit Animation CSS styles and Classes of Notus Element based on config.
     */
    fnGetExitAnimatorStyle = function(config) {
        var type = config.notusType,
            position = config.notusPosition,
            animationType = config.animationType,
            animationFunction = config.animationFunction,
            animationDuration = config.animationDuration / 1000,
            isSlide = animationType === 'slide',
            animators = [];

        if (type === 'popup')
        {
            if (positionShorts[position].indexOf('l') > -1)
                animators.push(isSlide ? 'transform: translateX(-110%)' : 'opacity: 0');
            else
                animators.push(isSlide ? 'transform: translateX(110%)' : 'opacity: 0');
        }
        else
        {
            if (positionShorts[position] === 'top')
                animators.push(isSlide ? 'transform: translateY(-110%)' : 'opacity: 0');
            else
                animators.push(isSlide ? 'transform: translateY(110%)' : 'opacity: 0');
        }

        if (animationDuration > 0)
            animators.push(_n.format('animation-duration: {0}s', animationDuration));

        if (animationFunction)
            animators.push(_n.format('animation-timing-function: {0}', animationFunction));

        return animators;
    };

    /**
     * Creates main Notus DOM Element based with provided config.
     */
    fnCreateNotusEl = function(config) {
        var parentDiv = document.createElement('div'),
            isSlide = config.animationType === 'slide',
            htmlStringSupported = config.htmlString,
            classList = [],
            notusElTpl = '',
            notusTitleElTpl = '',
            actionElTpl = '',
            closeElTpl = '';

        classList = fnGetParentClassList(config);

        if (config.animate)
        {
            classList.push(isSlide ? 'notus-slide' : 'notus-fade');
            classList.push(isSlide ? 'notus-slide-in' : 'notus-fade-in');

            parentDiv.setAttribute('style', fnGetEntryAnimatorStyle(config).join(';'));
        }

        parentDiv.setAttribute('id', _n.genId());
        parentDiv.setAttribute('class', classList.join(' '));

        if (config.closable)
        {
            closeElTpl = [
                '
',
                    '×',
                '
'
            ].join('');
        }

        if (config.notusType === 'snackbar')
        {
            if (config.actionable) // actionable is only supported for Snackbar Notus.
            {
                actionElTpl = [
                    '
',
                        '{2}',
                    '
'
                ].join('');
            }
        }
        else
            notusTitleElTpl = '
{0}
';

        notusElTpl = [
            '
',
                notusTitleElTpl,
                '
',
                    '{1}',
                '
',
            '
',
            closeElTpl,
            actionElTpl
        ].join('');

        parentDiv.innerHTML = _n.format(notusElTpl,
                                    htmlStringSupported ? config.title : _n.escapeHtml(config.title),
                                    htmlStringSupported ? config.message : _n.escapeHtml(config.message),
                                    htmlStringSupported ? config.actionText : _n.escapeHtml(config.actionText)
                                );

        return parentDiv;
    };

    /** Helpers End **/

    /** Main Notus Object Begin **/

    notus = self.notus = function(userConfig) {
        var bodyEL = document.body,
            defaultConfig = {},
            thisNotus = {};

        /** Default Config options that Notus provides **/
        defaultConfig = {
            notusType: 'popup',                     /* Type can be anything from; 'popup', 'toast' or 'snackbar' */

            notusPosition: 'top-right',             /* Available positions for different notus types;
                                                       'popup'              => 'top-left' ('tl'), 'bottom-left' ('bl'),
                                                                               'top-right' ('tr') or 'bottom-right' ('br')
                                                       'toast' & 'snackbar' => 'top' ('t') or 'bottom' ('b') */

            alertType: 'none',                      /* Alert type can be; 'none', 'success', 'failure' or 'warning' */

            htmlString: false,                      /* Enable HTML support in strings provided for 'title' & 'message',
                                                       this is unsafe and hence, it is false by default */

            closable: true,                         /* Show close button to close Notus */

            autoClose: true,                        /* Automatically close Notus once autoCloseDuration completes */

            autoCloseDuration: 3000,                /* Milliseconds to wait before closing  */

            animate: true,                          /* Animate while showing/hiding Notus */

            animationType: 'slide',                 /* Animation Type while showing/hiding Notus; it can be 'slide' or 'fade' */

            animationDuration: 300,                 /* Animation Duration to apply while showing/hiding Notus,
                                                       it supports values in milliseconds.
                                                       which is then passed to CSS animation-duration */

            animationFunction: 'ease-out',          /* Animation Timing Function to use while showing/hiding Notus,
                                                       it supports any value that CSS animation-timing-function supports,
                                                       including cubic-bezier() & steps()
                                                       value is then passed to CSS animation-timing-function */

            themeClass: 'notus-material-light'      /* Provide custom CSS class that you want to apply on Parent element of Notus */
        };

        userConfig = _n.extend(defaultConfig, userConfig);

        /** Notus API begin **/

        /**
         * Resolve instance conflict in case module is loaded more than once.
         */
        thisNotus.noConflict = function() {
            self.notus = old_notus;
            return notus;
        };

        /**
         * Main Notus send() method to send supported type of notus Notifications.
         */
        thisNotus.send = function(config) {
            var containerEl,
                notusEl;

            config = _n.extend({}, userConfig, config);

            fnValidateConfig(config);

            containerEl = fnCreateNotusContainer(config);

            notusEl = fnCreateNotusEl(config);

            if (config.closable)
                fnBindCloseHandler(config, notusEl);

            if (config.notusType === 'snackbar' &&
                config.actionable)
                fnBindActionHandler(config, notusEl);

            if (config.notusPosition.indexOf('bottom') > -1)
                containerEl.insertBefore(notusEl, containerEl.firstChild);
            else
                containerEl.appendChild(notusEl);

            if (config.autoClose)
                fnBindCloseListener(config, notusEl);

            return notusEl.getAttribute('id');
        };
        /** Notus API end **/

        return thisNotus;
    };

    // UMD Definition < https://github.com/umdjs/umd >.
    if (typeof exports !== 'undefined')
    {
        if (typeof module !== 'undefined' &&
            module.exports)
        {
            exports = module.exports = notus;
        }
        exports.notus = notus;
    }
    else
    {
        self.notus = notus;
    }
}).call(this);
<commit_msg>Throw error for unsupported notusPosition value.<commit_after>/**
 * notus v0.1.0
 *
 * Author: Kushal Pandya <kushalspandya@gmail.com> (https://doublslash.com)
 * Date: 12 February, 2016
 *
 * Main Notus Script.
 */

(function() {
    "use strict";

    var self = this,
        hasRequire = (typeof require !== 'undefined'),
        old_notus = self.notus,
        _n = {},
        notus;

    var htmlEntityMap = {
            '&': '&',
            '<': '<',
            '>': '>',
            '"': '"',
            "'": ''',
            '/': '/',
            '`': '`',
            '=': '='
        },
        notusTypeMap = {
            'popup': 'popup',
            'toast': 'toast',
            'snackbar': 'snackbar'
        },
        positionShorts = {
            'top-left': 'tl',
            'top-right': 'tr',
            'bottom-left': 'bl',
            'bottom-right': 'br',
            'top': 'top',
            'bottom': 'bottom',
            'tl': 'tl',
            'tr': 'tr',
            'bl': 'bl',
            'br': 'br',
            't': 'top',
            'b': 'bottom'
        },
        positionForType = {
            'popup': ['tl', 'tr', 'bl', 'br'],
            'toast': ['t', 'b'],
            'snackbar': ['t', 'b'],
        },
        alertTypeMap = {
            'success': 'success',
            'failure': 'failure',
            'warning': 'warning',
            'custom': 'custom',
            'none': ''
        };

    var fnValidateConfig,
        fnGetParentClassList,
        fnCreateNotusContainer,
        fnGetEntryAnimatorStyle,
        fnGetExitAnimatorStyle,
        fnBindCloseHandler,
        fnBindCloseListener,
        fnBindActionHandler,
        fnCreateNotusEl;

    /**
     * Local Utility API.
     */
    _n = {
        /**
         * Generate timestamp based unique ID.
         */
        genId: function() {
            return 'notus-' + Math.floor(Math.random() * 10000000000000001);
        },

        /**
         * Simple Object extender (similar to jQuery's $.extend()).
         * courtesy; SO < http://stackoverflow.com/a/11197343/414749 >
         */
        extend: function() {
            var key, i;

            for (i = 1; i < arguments.length; i++)
            {
                for (key in arguments[i])
                {
                    if (arguments[i].hasOwnProperty(key))
                        arguments[0][key] = arguments[i][key];
                }
            }

            return arguments[0];
        },

        /**
         * String formatter.
         * Accepts first param as target string, and following params as strings to
         * replace placeholders (eg; {0}).
         * courtesy: SO <http://stackoverflow.com/a/1038930/414749>
         */
        format: function() {
            var str = arguments[0],
                reg,
                i;

            for (i = 0; i < arguments.length - 1; i++) {
                reg = new RegExp("\\{" + i + "\\}", "gm");
                str = str.replace(reg, arguments[i + 1]);
            }

            return str;
        },

        /**
         * Escapes HTML tags present in the string.
         * courtesy: Mustache.js < https://github.com/janl/mustache.js >
         */
        escapeHtml: function(string) {
            return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
                return htmlEntityMap[s];
            });
        },

        /**
         * Removes an Element from the DOM.
         * This method uses el.remove() internally in supported browsers
         * except for IE11, where it is using el.parent.removeChild().
         * courtesy: MDN < https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove >
         */
        removeEl: function(el) {
            if (typeof el.remove === 'function')
                el.remove();
            else if (el.parentElement)
                el.parentElement.removeChild(el);
        }
    };

    /** Helpers Begin **/

    /**
     * Validate user configuration and throw Errors accordingly.
     */
    fnValidateConfig = function(config) {
        var type = config.notusType,
            position = config.notusPosition;

        if (notusTypeMap[type] === undefined)
            throw new Error('Unknown value for notusType');

        if (positionShorts[position] === undefined)
            throw new Error('Unknown value for notusPosition');

        if (alertTypeMap[config.alertType] === undefined)
            throw new Error('Unknown value for alertType');

        if (positionForType[type].indexOf(positionShorts[position]) < 0)
            throw new Error('Unsupported position "' + position + '" for notusType "' + type + '"');

        if (config.closeHandler &&
            typeof config.closeHandler !== "function")
            throw new Error("closeHandler is not a function");

        if (config.notusType === 'snackbar' &&
            config.actionable &&
            typeof config.actionHandler !== "function")
            throw new Error("actionHandler is not a function");
    };

    /**
     * Creates list of CSS classes which are to be applied main Notus element based on config.
     */
    fnGetParentClassList = function(config) {
        var classList = [],
            type = config.notusType,
            alertType = config.alertType;

        classList.push('notus');

        classList.push('notus-type-' + notusTypeMap[type]);

        if (alertTypeMap[alertType])
            classList.push('notus-alert-' + alertTypeMap[alertType]);

        if (config.themeClass)
            classList.push(config.themeClass);

        return classList;
    };

    /**
     * Creates new (or returns existing) Notus container elements to house Notus for a particular position.
     * Note that it creates containers for all position types that Notus supports, and it will return
     * reference to an existing container if it was previously created.
     */
    fnCreateNotusContainer = function(config) {
        var type = config.notusType,
            position = config.notusPosition,
            containerCls = ['notus-container'],
            containerEl;

        containerCls.push('notus-position-' + positionShorts[position]);

        if (type !== 'popup')
            containerCls.push((type === 'toast') ? 'notus-container-toast' : 'notus-container-snackbar');

        containerEl = document.querySelector('.' + containerCls.join('.'));

        if (!containerEl)
        {
            containerEl = document.createElement('div');
            containerEl.setAttribute('class', containerCls.join(' '));
            document.body.appendChild(containerEl);
        }

        return containerEl;
    };

    /**
     * Binds Timer event on each Notus that has autoClose set to true.
     * the timer waits for autoCloseDuration to finish before closing & destroying Notus.
     */
    fnBindCloseListener = function(config, notusEl) {
        var type = config.notusType,
            animationType = config.animationType;

        setTimeout(function() {
            notusEl.setAttribute('style', fnGetExitAnimatorStyle(config).join(';'));

            if (animationType === 'slide')
            {
                notusEl.classList.remove('notus-slide-in');
                notusEl.classList.add('notus-slide-out');
            }
            else
            {
                notusEl.classList.remove('notus-fade-in');
                notusEl.classList.add('notus-fade-out');
            }

            setTimeout(function() {
                _n.removeEl(notusEl);
            }, config.animationDuration);

        }, config.autoCloseDuration);
    };

    /**
     * Binds Mouse Click event handler on Close element of Notus.
     * it optionally supports closeHandler function that gets called before Notus is destroyed.
     * if closeHandler returns false (boolean false), Notus will be prevented from destroying.
     */
    fnBindCloseHandler = function(config, notusEl) {
        var closeEl = notusEl.querySelector('.notus-close');

        closeEl.onclick = function(e) {
            var doRemove = false,
                handlerReturnVal;

            if (config.closeHandler)
            {
                handlerReturnVal = config.closeHandler.apply(this, arguments);
                doRemove = (typeof handlerReturnVal === 'boolean') ? handlerReturnVal : true;
            }
            else
                doRemove = true;

            if (doRemove)
                _n.removeEl(notusEl);
        };
    };

    /**
     * Binds Mouse Click event handler on action element of Notus type Snackbar.
     */
    fnBindActionHandler = function(config, notusEl) {
        var actionEl = notusEl.querySelector('.notus-action');

        actionEl.onclick = function(e) {
            config.actionHandler.apply(this, arguments);
            _n.removeEl(notusEl);
        };
    };

    /**
     * Updates and applies Entry Animation CSS styles and Classes of Notus Element based on config.
     */
    fnGetEntryAnimatorStyle = function(config) {
        var type = config.notusType,
            animationType = config.animationType,
            animationFunction = config.animationFunction,
            animationDuration = config.animationDuration / 1000,
            isSlide = animationType === 'slide',
            animators = [];

        if (type === 'popup')
            animators.push(isSlide ? 'transform: translateX(0%)' : 'opacity: 1');
        else
            animators.push(isSlide ? 'transform: translateY(0%)' : 'opacity: 1');

        if (animationDuration > 0)
            animators.push(_n.format('animation-duration: {0}s', animationDuration));

        if (animationFunction)
            animators.push(_n.format('animation-timing-function: {0}', animationFunction));

        return animators;
    };

    /**
     * Updates and applies Exit Animation CSS styles and Classes of Notus Element based on config.
     */
    fnGetExitAnimatorStyle = function(config) {
        var type = config.notusType,
            position = config.notusPosition,
            animationType = config.animationType,
            animationFunction = config.animationFunction,
            animationDuration = config.animationDuration / 1000,
            isSlide = animationType === 'slide',
            animators = [];

        if (type === 'popup')
        {
            if (positionShorts[position].indexOf('l') > -1)
                animators.push(isSlide ? 'transform: translateX(-110%)' : 'opacity: 0');
            else
                animators.push(isSlide ? 'transform: translateX(110%)' : 'opacity: 0');
        }
        else
        {
            if (positionShorts[position] === 'top')
                animators.push(isSlide ? 'transform: translateY(-110%)' : 'opacity: 0');
            else
                animators.push(isSlide ? 'transform: translateY(110%)' : 'opacity: 0');
        }

        if (animationDuration > 0)
            animators.push(_n.format('animation-duration: {0}s', animationDuration));

        if (animationFunction)
            animators.push(_n.format('animation-timing-function: {0}', animationFunction));

        return animators;
    };

    /**
     * Creates main Notus DOM Element based with provided config.
     */
    fnCreateNotusEl = function(config) {
        var parentDiv = document.createElement('div'),
            isSlide = config.animationType === 'slide',
            htmlStringSupported = config.htmlString,
            classList = [],
            notusElTpl = '',
            notusTitleElTpl = '',
            actionElTpl = '',
            closeElTpl = '';

        classList = fnGetParentClassList(config);

        if (config.animate)
        {
            classList.push(isSlide ? 'notus-slide' : 'notus-fade');
            classList.push(isSlide ? 'notus-slide-in' : 'notus-fade-in');

            parentDiv.setAttribute('style', fnGetEntryAnimatorStyle(config).join(';'));
        }

        parentDiv.setAttribute('id', _n.genId());
        parentDiv.setAttribute('class', classList.join(' '));

        if (config.closable)
        {
            closeElTpl = [
                '
',
                    '×',
                '
'
            ].join('');
        }

        if (config.notusType === 'snackbar')
        {
            if (config.actionable) // actionable is only supported for Snackbar Notus.
            {
                actionElTpl = [
                    '
',
                        '{2}',
                    '
'
                ].join('');
            }
        }
        else
            notusTitleElTpl = '
{0}
';

        notusElTpl = [
            '
',
                notusTitleElTpl,
                '
',
                    '{1}',
                '
',
            '
',
            closeElTpl,
            actionElTpl
        ].join('');

        parentDiv.innerHTML = _n.format(notusElTpl,
                                    htmlStringSupported ? config.title : _n.escapeHtml(config.title),
                                    htmlStringSupported ? config.message : _n.escapeHtml(config.message),
                                    htmlStringSupported ? config.actionText : _n.escapeHtml(config.actionText)
                                );

        return parentDiv;
    };

    /** Helpers End **/

    /** Main Notus Object Begin **/

    notus = self.notus = function(userConfig) {
        var bodyEL = document.body,
            defaultConfig = {},
            thisNotus = {};

        /** Default Config options that Notus provides **/
        defaultConfig = {
            notusType: 'popup',                     /* Type can be anything from; 'popup', 'toast' or 'snackbar' */

            notusPosition: 'top-right',             /* Available positions for different notus types;
                                                       'popup'              => 'top-left' ('tl'), 'bottom-left' ('bl'),
                                                                               'top-right' ('tr') or 'bottom-right' ('br')
                                                       'toast' & 'snackbar' => 'top' ('t') or 'bottom' ('b') */

            alertType: 'none',                      /* Alert type can be; 'none', 'success', 'failure' or 'warning' */

            htmlString: false,                      /* Enable HTML support in strings provided for 'title' & 'message',
                                                       this is unsafe and hence, it is false by default */

            closable: true,                         /* Show close button to close Notus */

            autoClose: true,                        /* Automatically close Notus once autoCloseDuration completes */

            autoCloseDuration: 3000,                /* Milliseconds to wait before closing  */

            animate: true,                          /* Animate while showing/hiding Notus */

            animationType: 'slide',                 /* Animation Type while showing/hiding Notus; it can be 'slide' or 'fade' */

            animationDuration: 300,                 /* Animation Duration to apply while showing/hiding Notus,
                                                       it supports values in milliseconds.
                                                       which is then passed to CSS animation-duration */

            animationFunction: 'ease-out',          /* Animation Timing Function to use while showing/hiding Notus,
                                                       it supports any value that CSS animation-timing-function supports,
                                                       including cubic-bezier() & steps()
                                                       value is then passed to CSS animation-timing-function */

            themeClass: 'notus-material-light'      /* Provide custom CSS class that you want to apply on Parent element of Notus */
        };

        userConfig = _n.extend(defaultConfig, userConfig);

        /** Notus API begin **/

        /**
         * Resolve instance conflict in case module is loaded more than once.
         */
        thisNotus.noConflict = function() {
            self.notus = old_notus;
            return notus;
        };

        /**
         * Main Notus send() method to send supported type of notus Notifications.
         */
        thisNotus.send = function(config) {
            var containerEl,
                notusEl;

            config = _n.extend({}, userConfig, config);

            fnValidateConfig(config);

            containerEl = fnCreateNotusContainer(config);

            notusEl = fnCreateNotusEl(config);

            if (config.closable)
                fnBindCloseHandler(config, notusEl);

            if (config.notusType === 'snackbar' &&
                config.actionable)
                fnBindActionHandler(config, notusEl);

            if (config.notusPosition.indexOf('bottom') > -1)
                containerEl.insertBefore(notusEl, containerEl.firstChild);
            else
                containerEl.appendChild(notusEl);

            if (config.autoClose)
                fnBindCloseListener(config, notusEl);

            return notusEl.getAttribute('id');
        };
        /** Notus API end **/

        return thisNotus;
    };

    // UMD Definition < https://github.com/umdjs/umd >.
    if (typeof exports !== 'undefined')
    {
        if (typeof module !== 'undefined' &&
            module.exports)
        {
            exports = module.exports = notus;
        }
        exports.notus = notus;
    }
    else
    {
        self.notus = notus;
    }
}).call(this);
<|endoftext|>


<commit_before>const assert = require('assert');
const chai = require('chai');
const chaiHttp = require('chai-http');
const oauth2 = require('simple-oauth2');
const request = require('request-promise-native');

const app = require('../../../src/app');
const logger = require('../../../src/logger/');

const clientsService = app.service('oauth2/clients');
const loginService = app.service('oauth2/loginRequest');
const introspectService = app.service('oauth2/introspect');
const consentService = app.service('oauth2/auth/sessions/consent');
const toolService = app.service('ltiTools');

chai.use(chaiHttp);

// todo enable if that works on travis
describe.skip('oauth2 service', function oauthTest() {
	this.timeout(10000);

	const testUser2 = {
		_id: '0000d224816abba584714c9c',
	};

	const testClient = {
		client_id: 'unit_test',
		client_name: 'Unit Test Client',
		client_secret: 'xxxxxxxxxxxxx',
		redirect_uris: [
			'https://localhost:8888',
		],
		token_endpoint_auth_method: 'client_secret_basic',
		subject_type: 'pairwise',
	};

	const testClient2 = {
		client_id: 'unit_test_2',
		client_name: 'Unit Test Client',
		client_secret: 'xxxxxxxxxxxxx',
		redirect_uris: [
			'https://localhost:8888',
		],
		token_endpoint_auth_method: 'client_secret_basic',
		subject_type: 'pairwise',
	};

	const testTool1 = {
		_id: '5a79cb15c3874f9aea14daa5',
		name: 'test1',
		url: 'https://tool.com?pseudonym={PSEUDONYM}',
		isLocal: true,
		isTemplate: true,
		resource_link_id: 1,
		lti_version: '1p0',
		lti_message_type: 'basic-start-request',
		secret: '1',
		key: '1',
		oAuthClientId: testClient2.client_id,
	};

	const testTool2 = {
		_id: '5a79cb15c3874f9aea14daa6',
		originTool: '5a79cb15c3874f9aea14daa5',
		name: 'test2',
		url: 'https://tool.com?pseudonym={PSEUDONYM}',
		isLocal: true,
		resource_link_id: 1,
		lti_version: '1p0',
		lti_message_type: 'basic-start-request',
		secret: '1',
		key: '1',
	};

	let loginRequest1 = null;
	let loginRequest2 = null;
	// let redirectTo = null;

	before((done) => {
		this.timeout(10000);
		Promise.all([
			toolService.create(testTool1),
			toolService.create(testTool2),
		]).then(() => {
			clientsService.create(testClient2).then((client) => {
				const oauth = oauth2.create({
					client: {
						id: client.client_id,
						secret: client.client_secret,
					},
					auth: {
						tokenHost: 'http://localhost:9000',
						tokenPath: '/oauth2/token',
						authorizePath: '/oauth2/auth',
					},
				});
				const authorizationUri = oauth.authorizationCode.authorizeURL({
					redirect_uri: client.redirect_uris[0],
					scope: 'openid',
					state: '12345678',
				});
				request({
					uri: authorizationUri,
					method: 'GET',
					followRedirect: false,
				}).catch((res) => {
					const position = res.error.indexOf('login_challenge=')
							+ 'login_challenge'.length + 1;
					loginRequest1 = res.error.substr(position, 32);
					request({
						uri: authorizationUri,
						method: 'GET',
						followRedirect: false,
					}).catch((res2) => {
						const position2 = res2.error.indexOf('login_challenge=')
								+ 'login_challenge'.length + 1;
						loginRequest2 = res2.error.substr(position2, 32);
						done();
					});
				});
			}).catch((err) => {
				logger.warning('Can not execute oauth2 before all hook.', err);
				done();
			});
		});
	});

	after((done) => {
		Promise.all([
			toolService.remove(testTool1._id),
			toolService.remove(testTool2._id),
			clientsService.remove(testClient2.client_id),
		]).then(() => {
			done();
		}).catch((err) => {
			logger.warning('Can not execute oauth2 after all hook.', err);
			done();
		});
	});

	it('is registered', () => {
		assert.ok(clientsService);
		assert.ok(loginService);
	});

	it('CREATE Client', () => clientsService.create(testClient).then((result) => {
		assert.strictEqual(result.client_id, testClient.client_id);
	}));

	it('FIND Clients', () => clientsService.find().then((result) => {
		const foundTestClient = JSON.parse(result)
			.find(client => (client.client_id === testClient.client_id));
		assert(foundTestClient);
	}));

	it('DELETE Client', () => clientsService.remove(testClient.client_id).then((result) => {
		assert(true);
	}));

	it('GET Login Request', () => loginService.get(loginRequest1).then((result) => {
		assert.strictEqual(result.challenge, loginRequest1);
	}));

	it('PATCH Login Request Accept', () => loginService.patch(loginRequest1, {}, {
		query: { accept: 1 },
		account: { userId: testUser2._id },
	}).then((result) => {
		// redirectTo = result.redirect_to;
		assert.ok(result.redirect_to.indexOf(testClient2.client_id) !== -1);
	}));

	it('PATCH Login Request Reject', () => loginService.patch(loginRequest2, {}, {
		query: { accept: 0 },
		account: { userId: '0000d224816abba584714c9c' },
	}).then(() => {
		assert.ok(true);
	}));

	// it('GET and PATCH Consent Request', (done) => {
	// 	return request({
	// 		uri: redirectTo,
	// 		method: 'GET',
	// 		followRedirect: false,
	// 	}).then(response => {
	// 		console.log(response);
	// 		assert.ok(true);
	// 		done();
	// 	}).catch(err => {
	// 		console.log(err);
	// 	});
	// });

	it('Introspect Inactive Token', () => introspectService.create({ token: 'xxx' }).then((res) => {
		assert((res.active === false));
	}));

	it('GET Consent', () => consentService.get(testUser2._id, {
		account: { userId: testUser2._id },
	}).then((consents) => {
		assert.ok(consents);
	}));

	it('REMOVE Consent', () => consentService.remove(testUser2._id, {
		account: { userId: testUser2._id },
		query: { client: testClient.client_id },
	}).then(() => {
		throw new Error('Was not supposed to succeed');
	}).catch((err) => {
		assert.strictEqual(404, err.statusCode);
	}));
});
<commit_msg>removed skip<commit_after>const assert = require('assert');
const chai = require('chai');
const chaiHttp = require('chai-http');
const oauth2 = require('simple-oauth2');
const request = require('request-promise-native');

const app = require('../../../src/app');
const logger = require('../../../src/logger/');

const clientsService = app.service('oauth2/clients');
const loginService = app.service('oauth2/loginRequest');
const introspectService = app.service('oauth2/introspect');
const consentService = app.service('oauth2/auth/sessions/consent');
const toolService = app.service('ltiTools');

chai.use(chaiHttp);

// todo enable if that works on travis
describe('oauth2 service', function oauthTest() {
	this.timeout(10000);

	const testUser2 = {
		_id: '0000d224816abba584714c9c',
	};

	const testClient = {
		client_id: 'unit_test',
		client_name: 'Unit Test Client',
		client_secret: 'xxxxxxxxxxxxx',
		redirect_uris: [
			'https://localhost:8888',
		],
		token_endpoint_auth_method: 'client_secret_basic',
		subject_type: 'pairwise',
	};

	const testClient2 = {
		client_id: 'unit_test_2',
		client_name: 'Unit Test Client',
		client_secret: 'xxxxxxxxxxxxx',
		redirect_uris: [
			'https://localhost:8888',
		],
		token_endpoint_auth_method: 'client_secret_basic',
		subject_type: 'pairwise',
	};

	const testTool1 = {
		_id: '5a79cb15c3874f9aea14daa5',
		name: 'test1',
		url: 'https://tool.com?pseudonym={PSEUDONYM}',
		isLocal: true,
		isTemplate: true,
		resource_link_id: 1,
		lti_version: '1p0',
		lti_message_type: 'basic-start-request',
		secret: '1',
		key: '1',
		oAuthClientId: testClient2.client_id,
	};

	const testTool2 = {
		_id: '5a79cb15c3874f9aea14daa6',
		originTool: '5a79cb15c3874f9aea14daa5',
		name: 'test2',
		url: 'https://tool.com?pseudonym={PSEUDONYM}',
		isLocal: true,
		resource_link_id: 1,
		lti_version: '1p0',
		lti_message_type: 'basic-start-request',
		secret: '1',
		key: '1',
	};

	let loginRequest1 = null;
	let loginRequest2 = null;
	// let redirectTo = null;

	before((done) => {
		this.timeout(10000);
		Promise.all([
			toolService.create(testTool1),
			toolService.create(testTool2),
		]).then(() => {
			clientsService.create(testClient2).then((client) => {
				const oauth = oauth2.create({
					client: {
						id: client.client_id,
						secret: client.client_secret,
					},
					auth: {
						tokenHost: 'http://localhost:9000',
						tokenPath: '/oauth2/token',
						authorizePath: '/oauth2/auth',
					},
				});
				const authorizationUri = oauth.authorizationCode.authorizeURL({
					redirect_uri: client.redirect_uris[0],
					scope: 'openid',
					state: '12345678',
				});
				request({
					uri: authorizationUri,
					method: 'GET',
					followRedirect: false,
				}).catch((res) => {
					const position = res.error.indexOf('login_challenge=')
							+ 'login_challenge'.length + 1;
					loginRequest1 = res.error.substr(position, 32);
					request({
						uri: authorizationUri,
						method: 'GET',
						followRedirect: false,
					}).catch((res2) => {
						const position2 = res2.error.indexOf('login_challenge=')
								+ 'login_challenge'.length + 1;
						loginRequest2 = res2.error.substr(position2, 32);
						done();
					});
				});
			}).catch((err) => {
				logger.warning('Can not execute oauth2 before all hook.', err);
				done();
			});
		});
	});

	after((done) => {
		Promise.all([
			toolService.remove(testTool1._id),
			toolService.remove(testTool2._id),
			clientsService.remove(testClient2.client_id),
		]).then(() => {
			done();
		}).catch((err) => {
			logger.warning('Can not execute oauth2 after all hook.', err);
			done();
		});
	});

	it('is registered', () => {
		assert.ok(clientsService);
		assert.ok(loginService);
	});

	it('CREATE Client', () => clientsService.create(testClient).then((result) => {
		assert.strictEqual(result.client_id, testClient.client_id);
	}));

	it('FIND Clients', () => clientsService.find().then((result) => {
		const foundTestClient = JSON.parse(result)
			.find(client => (client.client_id === testClient.client_id));
		assert(foundTestClient);
	}));

	it('DELETE Client', () => clientsService.remove(testClient.client_id).then((result) => {
		assert(true);
	}));

	it('GET Login Request', () => loginService.get(loginRequest1).then((result) => {
		assert.strictEqual(result.challenge, loginRequest1);
	}));

	it('PATCH Login Request Accept', () => loginService.patch(loginRequest1, {}, {
		query: { accept: 1 },
		account: { userId: testUser2._id },
	}).then((result) => {
		// redirectTo = result.redirect_to;
		assert.ok(result.redirect_to.indexOf(testClient2.client_id) !== -1);
	}));

	it('PATCH Login Request Reject', () => loginService.patch(loginRequest2, {}, {
		query: { accept: 0 },
		account: { userId: '0000d224816abba584714c9c' },
	}).then(() => {
		assert.ok(true);
	}));

	// it('GET and PATCH Consent Request', (done) => {
	// 	return request({
	// 		uri: redirectTo,
	// 		method: 'GET',
	// 		followRedirect: false,
	// 	}).then(response => {
	// 		console.log(response);
	// 		assert.ok(true);
	// 		done();
	// 	}).catch(err => {
	// 		console.log(err);
	// 	});
	// });

	it('Introspect Inactive Token', () => introspectService.create({ token: 'xxx' }).then((res) => {
		assert((res.active === false));
	}));

	it('GET Consent', () => consentService.get(testUser2._id, {
		account: { userId: testUser2._id },
	}).then((consents) => {
		assert.ok(consents);
	}));

	it('REMOVE Consent', () => consentService.remove(testUser2._id, {
		account: { userId: testUser2._id },
		query: { client: testClient.client_id },
	}).then(() => {
		throw new Error('Was not supposed to succeed');
	}).catch((err) => {
		assert.strictEqual(404, err.statusCode);
	}));
});
<|endoftext|>


<commit_before>import LRU from 'lru-cache'
import { VueSSRServerPlugin } from 'vue-ssr-webpack-plugin'
import { compileWithWebpack } from './compile-with-webpack'
import { createBundleRenderer } from '../../packages/vue-server-renderer'

export function createRenderer (file, options, cb) {
  if (typeof options === 'function') {
    cb = options
    options = undefined
  }
  const asBundle = !!(options && options.asBundle)
  if (options) delete options.asBundle

  compileWithWebpack(file, {
    target: 'node',
    devtool: asBundle ? '#source-map' : false,
    output: {
      path: '/',
      filename: 'bundle.js',
      libraryTarget: 'commonjs2'
    },
    externals: [require.resolve('../../dist/vue.runtime.common.js')],
    plugins: asBundle
      ? [new VueSSRServerPlugin()]
      : []
  }, fs => {
    const bundle = asBundle
      ? JSON.parse(fs.readFileSync('/vue-ssr-bundle.json', 'utf-8'))
      : fs.readFileSync('/bundle.js', 'utf-8')
    const renderer = createBundleRenderer(bundle, options)
    cb(renderer)
  })
}

describe('SSR: bundle renderer', () => {
  it('renderToString', done => {
    createRenderer('app.js', renderer => {
      const context = { url: '/test' }
      renderer.renderToString(context, (err, res) => {
        expect(err).toBeNull()
        expect(res).toBe('
/test
')
        expect(context.msg).toBe('hello')
        done()
      })
    })
  })

  it('renderToStream', done => {
    createRenderer('app.js', renderer => {
      const context = { url: '/test' }
      const stream = renderer.renderToStream(context)
      let res = ''
      stream.on('data', chunk => {
        res += chunk.toString()
      })
      stream.on('end', () => {
        expect(res).toBe('
/test
')
        expect(context.msg).toBe('hello')
        done()
      })
    })
  })

  it('renderToString catch error', done => {
    createRenderer('error.js', renderer => {
      renderer.renderToString(err => {
        expect(err.message).toBe('foo')
        done()
      })
    })
  })

  it('renderToStream catch error', done => {
    createRenderer('error.js', renderer => {
      const stream = renderer.renderToStream()
      stream.on('error', err => {
        expect(err.message).toBe('foo')
        done()
      })
    })
  })

  it('render with cache (get/set)', done => {
    const cache = {}
    const get = jasmine.createSpy('get')
    const set = jasmine.createSpy('set')
    const options = {
      cache: {
        // async
        get: (key, cb) => {
          setTimeout(() => {
            get(key)
            cb(cache[key])
          }, 0)
        },
        set: (key, val) => {
          set(key, val)
          cache[key] = val
        }
      }
    }
    createRenderer('cache.js', options, renderer => {
      const expected = '
/test
'
      const key = 'app::1'
      renderer.renderToString((err, res) => {
        expect(err).toBeNull()
        expect(res).toBe(expected)
        expect(get).toHaveBeenCalledWith(key)
        const setArgs = set.calls.argsFor(0)
        expect(setArgs[0]).toBe(key)
        expect(setArgs[1].html).toBe(expected)
        expect(cache[key].html).toBe(expected)
        renderer.renderToString((err, res) => {
          expect(err).toBeNull()
          expect(res).toBe(expected)
          expect(get.calls.count()).toBe(2)
          expect(set.calls.count()).toBe(1)
          done()
        })
      })
    })
  })

  it('render with cache (get/set/has)', done => {
    const cache = {}
    const has = jasmine.createSpy('has')
    const get = jasmine.createSpy('get')
    const set = jasmine.createSpy('set')
    const options = {
      cache: {
        // async
        has: (key, cb) => {
          has(key)
          cb(!!cache[key])
        },
        // sync
        get: key => {
          get(key)
          return cache[key]
        },
        set: (key, val) => {
          set(key, val)
          cache[key] = val
        }
      }
    }
    createRenderer('cache.js', options, renderer => {
      const expected = '
/test
'
      const key = 'app::1'
      renderer.renderToString((err, res) => {
        expect(err).toBeNull()
        expect(res).toBe(expected)
        expect(has).toHaveBeenCalledWith(key)
        expect(get).not.toHaveBeenCalled()
        const setArgs = set.calls.argsFor(0)
        expect(setArgs[0]).toBe(key)
        expect(setArgs[1].html).toBe(expected)
        expect(cache[key].html).toBe(expected)
        renderer.renderToString((err, res) => {
          expect(err).toBeNull()
          expect(res).toBe(expected)
          expect(has.calls.count()).toBe(2)
          expect(get.calls.count()).toBe(1)
          expect(set.calls.count()).toBe(1)
          done()
        })
      })
    })
  })

  it('render with cache (nested)', done => {
    const cache = LRU({ maxAge: Infinity })
    spyOn(cache, 'get').and.callThrough()
    spyOn(cache, 'set').and.callThrough()
    const options = { cache }
    createRenderer('nested-cache.js', options, renderer => {
      const expected = '
/test
'
      const key = 'app::1'
      const context1 = { registered: [] }
      const context2 = { registered: [] }
      renderer.renderToString(context1, (err, res) => {
        expect(err).toBeNull()
        expect(res).toBe(expected)
        expect(cache.set.calls.count()).toBe(3) // 3 nested components cached
        const cached = cache.get(key)
        expect(cached.html).toBe(expected)
        expect(cache.get.calls.count()).toBe(1)

        // assert component usage registration for nested children
        expect(context1.registered).toEqual(['app', 'child', 'grandchild'])

        renderer.renderToString(context2, (err, res) => {
          expect(err).toBeNull()
          expect(res).toBe(expected)
          expect(cache.set.calls.count()).toBe(3) // no new cache sets
          expect(cache.get.calls.count()).toBe(2) // 1 get for root

          console.log(context1)
          console.log(context2)
          done()
        })
      })
    })
  })

  it('renderToString (bundle format with code split)', done => {
    createRenderer('split.js', { asBundle: true }, renderer => {
      const context = { url: '/test' }
      renderer.renderToString(context, (err, res) => {
        expect(err).toBeNull()
        expect(res).toBe('
/test
async test.woff2 test.png

')
        done()
      })
    })
  })

  it('renderToStream (bundle format with code split)', done => {
    createRenderer('split.js', { asBundle: true }, renderer => {
      const context = { url: '/test' }
      const stream = renderer.renderToStream(context)
      let res = ''
      stream.on('data', chunk => {
        res += chunk.toString()
      })
      stream.on('end', () => {
        expect(res).toBe('
/test
async test.woff2 test.png

')
        done()
      })
    })
  })

  it('renderToString catch error (bundle format with source map)', done => {
    createRenderer('error.js', { asBundle: true }, renderer => {
      renderer.renderToString(err => {
        expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
        expect(err.message).toBe('foo')
        done()
      })
    })
  })

  it('renderToString catch error (bundle format with source map)', done => {
    createRenderer('error.js', { asBundle: true }, renderer => {
      const stream = renderer.renderToStream()
      stream.on('error', err => {
        expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
        expect(err.message).toBe('foo')
        done()
      })
    })
  })
})
<commit_msg>fix test<commit_after>import LRU from 'lru-cache'
import { VueSSRServerPlugin } from 'vue-ssr-webpack-plugin'
import { compileWithWebpack } from './compile-with-webpack'
import { createBundleRenderer } from '../../packages/vue-server-renderer'

export function createRenderer (file, options, cb) {
  if (typeof options === 'function') {
    cb = options
    options = undefined
  }
  const asBundle = !!(options && options.asBundle)
  if (options) delete options.asBundle

  compileWithWebpack(file, {
    target: 'node',
    devtool: asBundle ? '#source-map' : false,
    output: {
      path: '/',
      filename: 'bundle.js',
      libraryTarget: 'commonjs2'
    },
    externals: [require.resolve('../../dist/vue.runtime.common.js')],
    plugins: asBundle
      ? [new VueSSRServerPlugin()]
      : []
  }, fs => {
    const bundle = asBundle
      ? JSON.parse(fs.readFileSync('/vue-ssr-bundle.json', 'utf-8'))
      : fs.readFileSync('/bundle.js', 'utf-8')
    const renderer = createBundleRenderer(bundle, options)
    cb(renderer)
  })
}

describe('SSR: bundle renderer', () => {
  it('renderToString', done => {
    createRenderer('app.js', renderer => {
      const context = { url: '/test' }
      renderer.renderToString(context, (err, res) => {
        expect(err).toBeNull()
        expect(res).toBe('
/test
')
        expect(context.msg).toBe('hello')
        done()
      })
    })
  })

  it('renderToStream', done => {
    createRenderer('app.js', renderer => {
      const context = { url: '/test' }
      const stream = renderer.renderToStream(context)
      let res = ''
      stream.on('data', chunk => {
        res += chunk.toString()
      })
      stream.on('end', () => {
        expect(res).toBe('
/test
')
        expect(context.msg).toBe('hello')
        done()
      })
    })
  })

  it('renderToString catch error', done => {
    createRenderer('error.js', renderer => {
      renderer.renderToString(err => {
        expect(err.message).toBe('foo')
        done()
      })
    })
  })

  it('renderToStream catch error', done => {
    createRenderer('error.js', renderer => {
      const stream = renderer.renderToStream()
      stream.on('error', err => {
        expect(err.message).toBe('foo')
        done()
      })
    })
  })

  it('render with cache (get/set)', done => {
    const cache = {}
    const get = jasmine.createSpy('get')
    const set = jasmine.createSpy('set')
    const options = {
      cache: {
        // async
        get: (key, cb) => {
          setTimeout(() => {
            get(key)
            cb(cache[key])
          }, 0)
        },
        set: (key, val) => {
          set(key, val)
          cache[key] = val
        }
      }
    }
    createRenderer('cache.js', options, renderer => {
      const expected = '
/test
'
      const key = 'app::1'
      renderer.renderToString((err, res) => {
        expect(err).toBeNull()
        expect(res).toBe(expected)
        expect(get).toHaveBeenCalledWith(key)
        const setArgs = set.calls.argsFor(0)
        expect(setArgs[0]).toBe(key)
        expect(setArgs[1].html).toBe(expected)
        expect(cache[key].html).toBe(expected)
        renderer.renderToString((err, res) => {
          expect(err).toBeNull()
          expect(res).toBe(expected)
          expect(get.calls.count()).toBe(2)
          expect(set.calls.count()).toBe(1)
          done()
        })
      })
    })
  })

  it('render with cache (get/set/has)', done => {
    const cache = {}
    const has = jasmine.createSpy('has')
    const get = jasmine.createSpy('get')
    const set = jasmine.createSpy('set')
    const options = {
      cache: {
        // async
        has: (key, cb) => {
          has(key)
          cb(!!cache[key])
        },
        // sync
        get: key => {
          get(key)
          return cache[key]
        },
        set: (key, val) => {
          set(key, val)
          cache[key] = val
        }
      }
    }
    createRenderer('cache.js', options, renderer => {
      const expected = '
/test
'
      const key = 'app::1'
      renderer.renderToString((err, res) => {
        expect(err).toBeNull()
        expect(res).toBe(expected)
        expect(has).toHaveBeenCalledWith(key)
        expect(get).not.toHaveBeenCalled()
        const setArgs = set.calls.argsFor(0)
        expect(setArgs[0]).toBe(key)
        expect(setArgs[1].html).toBe(expected)
        expect(cache[key].html).toBe(expected)
        renderer.renderToString((err, res) => {
          expect(err).toBeNull()
          expect(res).toBe(expected)
          expect(has.calls.count()).toBe(2)
          expect(get.calls.count()).toBe(1)
          expect(set.calls.count()).toBe(1)
          done()
        })
      })
    })
  })

  it('render with cache (nested)', done => {
    const cache = LRU({ maxAge: Infinity })
    spyOn(cache, 'get').and.callThrough()
    spyOn(cache, 'set').and.callThrough()
    const options = { cache }
    createRenderer('nested-cache.js', options, renderer => {
      const expected = '
/test
'
      const key = 'app::1'
      const context1 = { registered: [] }
      const context2 = { registered: [] }
      renderer.renderToString(context1, (err, res) => {
        expect(err).toBeNull()
        expect(res).toBe(expected)
        expect(cache.set.calls.count()).toBe(3) // 3 nested components cached
        const cached = cache.get(key)
        expect(cached.html).toBe(expected)
        expect(cache.get.calls.count()).toBe(1)

        // assert component usage registration for nested children
        expect(context1.registered).toEqual(['app', 'child', 'grandchild'])

        renderer.renderToString(context2, (err, res) => {
          expect(err).toBeNull()
          expect(res).toBe(expected)
          expect(cache.set.calls.count()).toBe(3) // no new cache sets
          expect(cache.get.calls.count()).toBe(2) // 1 get for root

          expect(context2.registered).toEqual(['app', 'child', 'grandchild'])
          done()
        })
      })
    })
  })

  it('renderToString (bundle format with code split)', done => {
    createRenderer('split.js', { asBundle: true }, renderer => {
      const context = { url: '/test' }
      renderer.renderToString(context, (err, res) => {
        expect(err).toBeNull()
        expect(res).toBe('
/test
async test.woff2 test.png

')
        done()
      })
    })
  })

  it('renderToStream (bundle format with code split)', done => {
    createRenderer('split.js', { asBundle: true }, renderer => {
      const context = { url: '/test' }
      const stream = renderer.renderToStream(context)
      let res = ''
      stream.on('data', chunk => {
        res += chunk.toString()
      })
      stream.on('end', () => {
        expect(res).toBe('
/test
async test.woff2 test.png

')
        done()
      })
    })
  })

  it('renderToString catch error (bundle format with source map)', done => {
    createRenderer('error.js', { asBundle: true }, renderer => {
      renderer.renderToString(err => {
        expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
        expect(err.message).toBe('foo')
        done()
      })
    })
  })

  it('renderToString catch error (bundle format with source map)', done => {
    createRenderer('error.js', { asBundle: true }, renderer => {
      const stream = renderer.renderToStream()
      stream.on('error', err => {
        expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
        expect(err.message).toBe('foo')
        done()
      })
    })
  })
})
<|endoftext|>


<commit_before>(function(win, TableFilter){
    // issue 162 - https://github.com/koalyptus/TableFilter/issues/162
    var tf = new TableFilter('demo', {
        base_path: '../dist/tablefilter/',
        col_date_type: ['DMY']
    });
    tf.init();

    module('Sanity checks');
    test('TableFilter instance', function() {
        deepEqual(tf instanceof TableFilter, true, 'TableFilter type');
    });
    test('Can filter empty cells', function(){
        // act
        tf.setFilterValue(1, 'test');
        tf.filter();
        // assert
        deepEqual(tf.getValidRows().length, 0, 'Expected filtered rows');
    });
    test('Can filter DMY date type', function(){
        // setup
        tf.clearFilters();
        // act
        tf.setFilterValue(0, '18.07.2020');
        tf.filter();
        // assert
        deepEqual(tf.getValidRows().length, 2, 'Expected filtered rows');
    });
    test('Can clear filters', function(){
        // act
        tf.clearFilters();
        // assert
        deepEqual(tf.getValidRows().length, 2167, 'Expected filtered rows');
    });

    module('Tear-down');
    test('Can destroy TF', function() {
        // act
        tf.destroy();

        // assert
        deepEqual(tf.isInitialized(), false, 'Filters removed');
    });
})(window, TableFilter);
<commit_msg>Removed col_date_type option from test-empty-cells-filtering<commit_after>(function(win, TableFilter){
    // issue 162 - https://github.com/koalyptus/TableFilter/issues/162
    var tf = new TableFilter('demo', {
        base_path: '../dist/tablefilter/',
        locale: 'en-GB',
        col_types: ['date', 'string']
    });
    tf.init();

    module('Sanity checks');
    test('TableFilter instance', function() {
        deepEqual(tf instanceof TableFilter, true, 'TableFilter type');
    });
    test('Can filter empty cells', function(){
        // act
        tf.setFilterValue(1, 'test');
        tf.filter();
        // assert
        deepEqual(tf.getValidRows().length, 0, 'Expected filtered rows');
    });
    test('Can filter dd.MM.yyyy date type', function(){
        // setup
        tf.clearFilters();
        // act
        tf.setFilterValue(0, '18.07.2020');
        tf.filter();
        // assert
        deepEqual(tf.getValidRows().length, 2, 'Expected filtered rows');
    });
    test('Can clear filters', function(){
        // act
        tf.clearFilters();
        // assert
        deepEqual(tf.getValidRows().length, 2167, 'Expected filtered rows');
    });

    module('Tear-down');
    test('Can destroy TF', function() {
        // act
        tf.destroy();

        // assert
        deepEqual(tf.isInitialized(), false, 'Filters removed');
    });
})(window, TableFilter);
<|endoftext|>


<commit_before>/*
 * test.lb.base.template.html.js - Unit Tests of lb.base.template.html module
 *
 * Author:    Eric Bréchemier <legalbox@eric.brechemier.name>
 * Copyright: Legal-Box (c) 2010-2011, All Rights Reserved
 * License:   BSD License - http://creativecommons.org/licenses/BSD/
 * Version:   2011-01-24
 *
 * Based on Test Runner from bezen.org JavaScript library
 * CC-BY: Eric Bréchemier - http://bezen.org/javascript/
 */

/*requires lb.base.template.html.js */
/*jslint white:false, onevar:false, plusplus:false */
/*global lb, bezen, window, document */
(function() {
  // Builder of
  // Closure object for Test of lb.base.template.html

  // Define aliases
      /*requires bezen.assert.js */
  var assert = bezen.assert,
      /*requires bezen.object.js */
      object = bezen.object,
      /*requires bezen.testrunner.js */
      testrunner = bezen.testrunner,
      /*requires bezen.dom.js*/
      element = bezen.dom.element,
      ELEMENT_NODE = bezen.dom.ELEMENT_NODE,
      ATTRIBUTE_NODE = bezen.dom.ATTRIBUTE_NODE,
      TEXT_NODE = bezen.dom.TEXT_NODE;

  function testNamespace(){

    assert.isTrue( object.exists(window,'lb','base','template','html'),
                            "lb.base.template.html namespace was not found");
  }

  function testTopDownParsing(){
    var ut = lb.base.template.html.topDownParsing;

    var captured = [];
    var catchFilter = function(){
      captured.push(arguments);
    };

    try {
      ut();
      ut(null);
      ut(null,null);
      ut(null,[]);
    } catch (e0) {
     assert.fail("No error expected when required arguments are missing: "+e0);
    }

    try {
      ut({},[]);
    } catch (e1) {
      assert.fail("No error expected when given node is not a node: "+e1);
    }

    try {
      assert.equals(
        ut( document.createElement('div'), [ut,catchFilter] ),
        undefined,
                                  "no processing expected on single element");
      assert.equals(
        ut( document.createAttribute('title'), [ut,catchFilter] ),
        undefined,
                                "no processing expected on single attribute");
      assert.equals(
        ut( document.createTextNode('Text'), [ut,catchFilter] ),
        undefined,
                                "no processing expected on single text node");

      assert.equals(
        ut( document.createComment('Text'), [ut,catchFilter] ),
        undefined,
                             "no processing expected on single comment node");

      assert.equals(
        ut( document.createDocumentFragment(), [ut,catchFilter] ),
        undefined,
                   "no processing expected on single document fragment node");

      // Operations not supported in FF,Chrome,Safari,IE:
      //   * document.createCDATASection('Text')
      //   * document.createProcessingInstruction('target','data')
      // Only supported in Opera.

    } catch(e) {
      assert.fail("top down parsing must not fail on any type of node: "+e);
    }
    assert.arrayEquals( captured, [],
                                   "no recursion expected for single nodes");

    var capturedNames = {};
    function catchAttributes(attribute){
      capturedNames[attribute.name] = true;
    }

    var filters = [ut,catchFilter,catchAttributes];
    var elementWithAttributesOnly = element('div',
      {id:'id value',title:'title value',lang:'lang value'}
    );
    var one = {}, two = 2, three = 'three';
    ut(elementWithAttributesOnly, one, two, three, filters);
    assert.equals( captured.length, 3,
                                    "3 attributes expected to be processed");
    for(var i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],      "params and filters expected in each call (attributes only)");
    }
    assert.objectEquals(capturedNames,{id:true,title:true,lang:true},
                    "attributes id, title and lang expected to be processed");

    var capturedNodeNames = [];
    function captureNodeNames(node){
      capturedNodeNames.push(node.nodeName);
    }

    var deepElement =
      element('h1',{},
        element('h2',{},
          element('h3',{})
        )
      );

    captured = [];
    filters = [ut,catchFilter,captureNodeNames];
    ut(deepElement,one,two,three,filters);
    assert.equals( captured.length, 2,
                                 "2 elements deep expected to be processed");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],   "params and filters expected in each call (elements deep only)");
    }
    assert.arrayEquals(capturedNodeNames, ['H2','H3'],
                          "names of two nodes deep expected to be processed");

    var wideElement =
      element('div',{},
        element('h1'),
        element('h2'),
        element('h3')
      );
    captured = [];
    capturedNodeNames = [];
    ut(wideElement,one,two,three,filters);
    assert.equals( captured.length, 3,
                                 "3 elements wide expected to be processed");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],   "params and filters expected in each call (elements wide only)");
    }
    assert.arrayEquals(capturedNodeNames, ['H1','H2','H3'],
                       "names of three nodes wide expected to be processed");

    var wideAndDeepElement =
      element('div',{},
        element('h1',{},
          element('a'),
          element('b'),
          element('cite')
        ),
        element('h2',{},
          element('dd'),
          element('em'),
          element('form')
        ),
        element('h3',{},
          element('h4'),
          element('h5'),
          element('h6')
        )
      );
    captured = [];
    capturedNodeNames = [];
    ut(wideAndDeepElement,one,two,three,filters);
    assert.equals( captured.length, 12,
                        "12 elements expected to be processed wide and deep");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],
         "params and filters expected in each call (elements wide and deep)");
    }
    assert.arrayEquals(capturedNodeNames,
      ['H1',
         'A','B','CITE',
       'H2',
         'DD','EM','FORM',
       'H3',
         'H4','H5','H6'
      ],
                 "titles of 12 nodes expected to be processed wide and deep");

    var capturedNodeTypes = [];
    function captureNodeTypes(node){
      capturedNodeTypes.push(node.nodeType);
    }
    filters = [ut,catchFilter,captureNodeTypes];

    var mixedElement =
      element('div',{id:'id',title:'title',lang:'lang'},
          'text1',
          element('div'),
          'text2',
          element('div'),
          'text3',
          element('div')
      );
    captured = [];
    ut(mixedElement,one,two,three,filters);
    assert.equals( captured.length, 9,
                "9 nodes expected to be processed in mixed content element");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],       "params and filters expected in each call (mixed content)");
    }
    assert.arrayEquals(capturedNodeTypes,
      [ATTRIBUTE_NODE,ATTRIBUTE_NODE,ATTRIBUTE_NODE,
       TEXT_NODE,ELEMENT_NODE,
       TEXT_NODE,ELEMENT_NODE,
       TEXT_NODE,ELEMENT_NODE],
           "node types of 9 nodes expected to be processed (mixed content)");

    // check that all attributes are processed even if some are removed by a
    // filter
    var parent = null;
    function removeAttributeFromParent(htmlNode){
      if (parent && htmlNode.nodeType === ATTRIBUTE_NODE){
        parent.removeAttributeNode(htmlNode);
      }
    }

    var elementWithAttributes =
      element('div',{id:'British',lang:'en-GB',title:'Sir',dir:'rtl'});

    parent = elementWithAttributes;
    capturedNames = {};
    ut( elementWithAttributes,
        [removeAttributeFromParent, catchAttributes]
    );
    assert.objectEquals(
      capturedNames,
      {id:true,lang:true,title:true,dir:true},
   "all 3 attributes expected to be processed even if attributes are removed");

    // check that all child nodes are processed even if some are removed by a
    // filter
    function removeLastSibling(htmlNode){
      if (htmlNode.parentNode){
        htmlNode.parentNode.removeChild(
          htmlNode.parentNode.lastChild
        );
      }
    }

    var elementWithChildNodes = element('div',{},
      element('h1'),
      element('h2'),
      element('h3'),
      element('h4'),
      element('h5')
    );
    capturedNodeNames = [];
    ut(elementWithChildNodes, [removeLastSibling, captureNodeNames]);
    assert.arrayEquals(capturedNodeNames, ['H1','H2','H3','H4','H5'],
    "all 5 child nodes expected to be processed, even with elements removed");

    // check that all attributes are processed even if a filter fails while
    // processing an attribute
    function failOnAttribute(htmlNode){
      if (htmlNode.nodeType === ATTRIBUTE_NODE){
        throw new Error('Expected error on attribute');
      }
    }

    elementWithAttributes =
      element('div',{id:'British',lang:'en-GB',title:'Sir',dir:'rtl'});
    capturedNames = {};
    ut( elementWithAttributes,
        [catchAttributes, failOnAttribute]
    );
    assert.objectEquals(
      capturedNames,
      {id:true,lang:true,title:true,dir:true},
          "all 3 attributes expected to be processed even if a filter fails");

    // check that all child nodes are processed even if a filter fails while
    // processing a child node
    function failOnElement(htmlNode){
      if (htmlNode.nodeType === ELEMENT_NODE){
        throw new Error('Expected error on element');
      }
    }

    elementWithChildNodes = element('div',{},
      element('h1'),
      element('h2'),
      element('h3'),
      element('h4'),
      element('h5')
    );
    capturedNodeNames = [];
    ut(elementWithChildNodes, [failOnElement, captureNodeNames]);
    assert.arrayEquals(capturedNodeNames, ['H1','H2','H3','H4','H5'],
        "all 5 child nodes expected to be processed, even if a filter fails");
  }

  function testReplaceParams(){
    var ut = lb.base.template.html.replaceParams;

    assert.equals( ut(), null,
                            "null expected when required getter is missing");
    assert.equals( ut(null), null,
                               "null expected when required getter is null");
    assert.equals( ut({}), null,
                     "null expected when required getter is not a function");

    var captured = [];
    var returnValues = [];
    function captureParams(key){
      captured.push(key);
      return returnValues.shift();
    }

    var filter = ut(captureParams);
    assert.equals( typeof filter, 'function',
                              "a filter function is expected to be returned");

    var htmlNode = element('div',{id:'theOne',title:'#param1#'},'#param2#');
    captured = [];
    returnValues = ['value1','value2'];
    filter(htmlNode);
    assert.arrayEquals([
      htmlNode.nodeName,
      htmlNode.getAttribute('id'),
      htmlNode.getAttribute('title'),
      htmlNode.innerHTML
    ],
    [
      'DIV',
      'theOne',
      '#param1#',
      '#param2#'
    ],                  "no replacement expected at the element node level");
    assert.arrayEquals(captured,[],
                     "no call to getter expected at the element node level");

    filter( htmlNode.getAttributeNode('title') );
    assert.arrayEquals([
      htmlNode.nodeName,
      htmlNode.getAttribute('id'),
      htmlNode.getAttribute('title'),
      htmlNode.innerHTML
    ],
    [
      'DIV',
      'theOne',
      'value1',
      '#param2#'
    ],                "parameter in title attribute expected to be replaced");
    assert.arrayEquals(captured,['param1'],
                   "call to getter with param1 expected for title attribute");

    filter( htmlNode.firstChild );
    assert.arrayEquals([
      htmlNode.nodeName,
      htmlNode.getAttribute('id'),
      htmlNode.getAttribute('title'),
      htmlNode.innerHTML
    ],
    [
      'DIV',
      'theOne',
      'value1',
      'value2'
    ],                    "parameter in text node expected to be replaced");
    assert.arrayEquals(captured,['param1','param2'],
                       "call to getter with param2 expected for text node");
  }

  var tests = {
    testNamespace: testNamespace,
    testTopDownParsing: testTopDownParsing,
    testReplaceParams: testReplaceParams
  };

  testrunner.define(tests, "lb.base.template.html");
  return tests;

}());
<commit_msg>Fixed unit tests for failure in attribute filter in testTopDownParsing()<commit_after>/*
 * test.lb.base.template.html.js - Unit Tests of lb.base.template.html module
 *
 * Author:    Eric Bréchemier <legalbox@eric.brechemier.name>
 * Copyright: Legal-Box (c) 2010-2011, All Rights Reserved
 * License:   BSD License - http://creativecommons.org/licenses/BSD/
 * Version:   2011-01-24
 *
 * Based on Test Runner from bezen.org JavaScript library
 * CC-BY: Eric Bréchemier - http://bezen.org/javascript/
 */

/*requires lb.base.template.html.js */
/*jslint white:false, onevar:false, plusplus:false */
/*global lb, bezen, window, document */
(function() {
  // Builder of
  // Closure object for Test of lb.base.template.html

  // Define aliases
      /*requires bezen.assert.js */
  var assert = bezen.assert,
      /*requires bezen.object.js */
      object = bezen.object,
      /*requires bezen.testrunner.js */
      testrunner = bezen.testrunner,
      /*requires bezen.dom.js*/
      element = bezen.dom.element,
      ELEMENT_NODE = bezen.dom.ELEMENT_NODE,
      ATTRIBUTE_NODE = bezen.dom.ATTRIBUTE_NODE,
      TEXT_NODE = bezen.dom.TEXT_NODE;

  function testNamespace(){

    assert.isTrue( object.exists(window,'lb','base','template','html'),
                            "lb.base.template.html namespace was not found");
  }

  function testTopDownParsing(){
    var ut = lb.base.template.html.topDownParsing;

    var captured = [];
    var catchFilter = function(){
      captured.push(arguments);
    };

    try {
      ut();
      ut(null);
      ut(null,null);
      ut(null,[]);
    } catch (e0) {
     assert.fail("No error expected when required arguments are missing: "+e0);
    }

    try {
      ut({},[]);
    } catch (e1) {
      assert.fail("No error expected when given node is not a node: "+e1);
    }

    try {
      assert.equals(
        ut( document.createElement('div'), [ut,catchFilter] ),
        undefined,
                                  "no processing expected on single element");
      assert.equals(
        ut( document.createAttribute('title'), [ut,catchFilter] ),
        undefined,
                                "no processing expected on single attribute");
      assert.equals(
        ut( document.createTextNode('Text'), [ut,catchFilter] ),
        undefined,
                                "no processing expected on single text node");

      assert.equals(
        ut( document.createComment('Text'), [ut,catchFilter] ),
        undefined,
                             "no processing expected on single comment node");

      assert.equals(
        ut( document.createDocumentFragment(), [ut,catchFilter] ),
        undefined,
                   "no processing expected on single document fragment node");

      // Operations not supported in FF,Chrome,Safari,IE:
      //   * document.createCDATASection('Text')
      //   * document.createProcessingInstruction('target','data')
      // Only supported in Opera.

    } catch(e) {
      assert.fail("top down parsing must not fail on any type of node: "+e);
    }
    assert.arrayEquals( captured, [],
                                   "no recursion expected for single nodes");

    var capturedNames = {};
    function catchAttributes(attribute){
      capturedNames[attribute.name] = true;
    }

    var filters = [ut,catchFilter,catchAttributes];
    var elementWithAttributesOnly = element('div',
      {id:'id value',title:'title value',lang:'lang value'}
    );
    var one = {}, two = 2, three = 'three';
    ut(elementWithAttributesOnly, one, two, three, filters);
    assert.equals( captured.length, 3,
                                    "3 attributes expected to be processed");
    for(var i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],      "params and filters expected in each call (attributes only)");
    }
    assert.objectEquals(capturedNames,{id:true,title:true,lang:true},
                    "attributes id, title and lang expected to be processed");

    var capturedNodeNames = [];
    function captureNodeNames(node){
      capturedNodeNames.push(node.nodeName);
    }

    var deepElement =
      element('h1',{},
        element('h2',{},
          element('h3',{})
        )
      );

    captured = [];
    filters = [ut,catchFilter,captureNodeNames];
    ut(deepElement,one,two,three,filters);
    assert.equals( captured.length, 2,
                                 "2 elements deep expected to be processed");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],   "params and filters expected in each call (elements deep only)");
    }
    assert.arrayEquals(capturedNodeNames, ['H2','H3'],
                          "names of two nodes deep expected to be processed");

    var wideElement =
      element('div',{},
        element('h1'),
        element('h2'),
        element('h3')
      );
    captured = [];
    capturedNodeNames = [];
    ut(wideElement,one,two,three,filters);
    assert.equals( captured.length, 3,
                                 "3 elements wide expected to be processed");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],   "params and filters expected in each call (elements wide only)");
    }
    assert.arrayEquals(capturedNodeNames, ['H1','H2','H3'],
                       "names of three nodes wide expected to be processed");

    var wideAndDeepElement =
      element('div',{},
        element('h1',{},
          element('a'),
          element('b'),
          element('cite')
        ),
        element('h2',{},
          element('dd'),
          element('em'),
          element('form')
        ),
        element('h3',{},
          element('h4'),
          element('h5'),
          element('h6')
        )
      );
    captured = [];
    capturedNodeNames = [];
    ut(wideAndDeepElement,one,two,three,filters);
    assert.equals( captured.length, 12,
                        "12 elements expected to be processed wide and deep");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],
         "params and filters expected in each call (elements wide and deep)");
    }
    assert.arrayEquals(capturedNodeNames,
      ['H1',
         'A','B','CITE',
       'H2',
         'DD','EM','FORM',
       'H3',
         'H4','H5','H6'
      ],
                 "titles of 12 nodes expected to be processed wide and deep");

    var capturedNodeTypes = [];
    function captureNodeTypes(node){
      capturedNodeTypes.push(node.nodeType);
    }
    filters = [ut,catchFilter,captureNodeTypes];

    var mixedElement =
      element('div',{id:'id',title:'title',lang:'lang'},
          'text1',
          element('div'),
          'text2',
          element('div'),
          'text3',
          element('div')
      );
    captured = [];
    ut(mixedElement,one,two,three,filters);
    assert.equals( captured.length, 9,
                "9 nodes expected to be processed in mixed content element");
    for (i=0; i<captured.length; i++){
      assert.arrayEquals(
        [
          captured[i][1],
          captured[i][2],
          captured[i][3],
          captured[i][4]
        ],
        [
          one, two, three, filters
        ],       "params and filters expected in each call (mixed content)");
    }
    assert.arrayEquals(capturedNodeTypes,
      [ATTRIBUTE_NODE,ATTRIBUTE_NODE,ATTRIBUTE_NODE,
       TEXT_NODE,ELEMENT_NODE,
       TEXT_NODE,ELEMENT_NODE,
       TEXT_NODE,ELEMENT_NODE],
           "node types of 9 nodes expected to be processed (mixed content)");

    // check that all attributes are processed even if some are removed by a
    // filter
    var parent = null;
    function removeAttributeFromParent(htmlNode){
      if (parent && htmlNode.nodeType === ATTRIBUTE_NODE){
        parent.removeAttributeNode(htmlNode);
      }
    }

    var elementWithAttributes =
      element('div',{id:'British',lang:'en-GB',title:'Sir',dir:'rtl'});

    parent = elementWithAttributes;
    capturedNames = {};
    ut( elementWithAttributes,
        [removeAttributeFromParent, catchAttributes]
    );
    assert.objectEquals(
      capturedNames,
      {id:true,lang:true,title:true,dir:true},
   "all 3 attributes expected to be processed even if attributes are removed");

    // check that all child nodes are processed even if some are removed by a
    // filter
    function removeLastSibling(htmlNode){
      if (htmlNode.parentNode){
        htmlNode.parentNode.removeChild(
          htmlNode.parentNode.lastChild
        );
      }
    }

    var elementWithChildNodes = element('div',{},
      element('h1'),
      element('h2'),
      element('h3'),
      element('h4'),
      element('h5')
    );
    capturedNodeNames = [];
    ut(elementWithChildNodes, [removeLastSibling, captureNodeNames]);
    assert.arrayEquals(capturedNodeNames, ['H1','H2','H3','H4','H5'],
    "all 5 child nodes expected to be processed, even with elements removed");

    // check that all attributes are processed even if a filter fails while
    // processing an attribute
    function failOnAttribute(htmlNode){
      if (htmlNode.nodeType === ATTRIBUTE_NODE){
        throw new Error('Expected error on attribute');
      }
    }

    elementWithAttributes =
      element('div',{id:'British',lang:'en-GB',title:'Sir',dir:'rtl'});
    capturedNames = {};
    ut( elementWithAttributes,
        [failOnAttribute, catchAttributes]
    );
    assert.objectEquals(
      capturedNames,
      {id:true,lang:true,title:true,dir:true},
          "all 3 attributes expected to be processed even if a filter fails");

    // check that all child nodes are processed even if a filter fails while
    // processing a child node
    function failOnElement(htmlNode){
      if (htmlNode.nodeType === ELEMENT_NODE){
        throw new Error('Expected error on element');
      }
    }

    elementWithChildNodes = element('div',{},
      element('h1'),
      element('h2'),
      element('h3'),
      element('h4'),
      element('h5')
    );
    capturedNodeNames = [];
    ut(elementWithChildNodes, [failOnElement, captureNodeNames]);
    assert.arrayEquals(capturedNodeNames, ['H1','H2','H3','H4','H5'],
        "all 5 child nodes expected to be processed, even if a filter fails");
  }

  function testReplaceParams(){
    var ut = lb.base.template.html.replaceParams;

    assert.equals( ut(), null,
                            "null expected when required getter is missing");
    assert.equals( ut(null), null,
                               "null expected when required getter is null");
    assert.equals( ut({}), null,
                     "null expected when required getter is not a function");

    var captured = [];
    var returnValues = [];
    function captureParams(key){
      captured.push(key);
      return returnValues.shift();
    }

    var filter = ut(captureParams);
    assert.equals( typeof filter, 'function',
                              "a filter function is expected to be returned");

    var htmlNode = element('div',{id:'theOne',title:'#param1#'},'#param2#');
    captured = [];
    returnValues = ['value1','value2'];
    filter(htmlNode);
    assert.arrayEquals([
      htmlNode.nodeName,
      htmlNode.getAttribute('id'),
      htmlNode.getAttribute('title'),
      htmlNode.innerHTML
    ],
    [
      'DIV',
      'theOne',
      '#param1#',
      '#param2#'
    ],                  "no replacement expected at the element node level");
    assert.arrayEquals(captured,[],
                     "no call to getter expected at the element node level");

    filter( htmlNode.getAttributeNode('title') );
    assert.arrayEquals([
      htmlNode.nodeName,
      htmlNode.getAttribute('id'),
      htmlNode.getAttribute('title'),
      htmlNode.innerHTML
    ],
    [
      'DIV',
      'theOne',
      'value1',
      '#param2#'
    ],                "parameter in title attribute expected to be replaced");
    assert.arrayEquals(captured,['param1'],
                   "call to getter with param1 expected for title attribute");

    filter( htmlNode.firstChild );
    assert.arrayEquals([
      htmlNode.nodeName,
      htmlNode.getAttribute('id'),
      htmlNode.getAttribute('title'),
      htmlNode.innerHTML
    ],
    [
      'DIV',
      'theOne',
      'value1',
      'value2'
    ],                    "parameter in text node expected to be replaced");
    assert.arrayEquals(captured,['param1','param2'],
                       "call to getter with param2 expected for text node");
  }

  var tests = {
    testNamespace: testNamespace,
    testTopDownParsing: testTopDownParsing,
    testReplaceParams: testReplaceParams
  };

  testrunner.define(tests, "lb.base.template.html");
  return tests;

}());
<|endoftext|>