<commit_before>define([
    'underscore',
    'arches',
    'models/abstract',
    'models/node',
    'models/card-widget',
    'knockout',
    'knockout-mapping',
    'card-components',
    'viewmodels/card-constraints',
    'utils/dispose'
], function(_, arches, AbstractModel, NodeModel, CardWidgetModel, ko, koMapping, cardComponentLookup, CardConstraintsViewModel, dispose) {
    var CardModel = AbstractModel.extend({
        /**
        * A backbone model to manage card data
        * @augments AbstractModel
        * @constructor
        * @name CardModel
        */

        url: arches.urls.card,

        initialize: function(attributes) {
            var self = this;
            this.cards = ko.observableArray();
            this.nodes = attributes.data.nodes;
            this.widgets = ko.observableArray();
            this.tiles = ko.observableArray();

            this.cardid = ko.observable();
            this.nodegroup_id = ko.observable();
            this.name = ko.observable();
            this.instructions = ko.observable();
            this.cssclass = ko.observable();
            this.helptext = ko.observable();
            this.helpenabled = ko.observable();
            this.helptitle = ko.observable();
            this.helpactive = ko.observable(false);
            this.cardinality = ko.observable();
            if (attributes.data.nodegroup) {
                this.cardinality(attributes.data.nodegroup.cardinality);
            }
            this.visible = ko.observable();
            this.active = ko.observable();
            this.ontologyproperty = ko.observable();
            this.sortorder = ko.observable();
            this.disabled = ko.observable();
            this.component_id = ko.observable();
            this.constraints = ko.observableArray();

            this.set('cards', this.cards);
            this.set('nodes', this.nodes);
            this.set('widgets', this.widgets);
            this.set('tiles', this.tiles);

            this.set('cardid', this.cardid);
            this.set('nodegroup_id', this.nodegroup_id);
            this.set('cardinality', this.cardinality);
            this.set('name', this.name);
            this.set('instructions', this.instructions);
            this.set('cssclass', this.cssclass);
            this.set('helptext', this.helptext);
            this.set('helpenabled', this.helpenabled);
            this.set('helptitle', this.helptitle);
            this.set('helpactive', this.helpactive);
            this.set('cardinality', this.cardinality);
            this.set('visible', this.visible);
            this.set('active', this.active);
            this.set('ontologyproperty', this.ontologyproperty);
            this.set('sortorder', this.sortorder);
            this.set('disabled', this.disabled);
            this.set('component_id', this.component_id);
            this.set('config', {});
            this.set('constraints', this.constraints);

            this.cardComponentLookup = cardComponentLookup;
            this.configKeys = ko.observableArray();
            this.disposables = [];

            this.configJSON = ko.computed({
                read: function() {
                    var configJSON = {};
                    var config = this.get('config');
                    _.each(this.configKeys(), function(key) {
                        configJSON[key] = ko.unwrap(config[key]);
                    });
                    return configJSON;
                },
                write: function(value) {
                    var config = this.get('config');
                    for (var key in value) {
                        if (config[key] && config[key]() !== value[key]) {
                            config[key](value[key]);
                        }
                    }
                },
                owner: this
            });

            var componentIdSubscription = this.get('component_id').subscribe(function(value) {
                var key;
                var defaultConfig = JSON.parse(self.cardComponentLookup[value].defaultconfig);
                for (key in defaultConfig) {
                    defaultConfig[key] = ko.observable(defaultConfig[key]);
                }
                var currentConfig = self.get('config');
                self.set('config', _.defaults(currentConfig, defaultConfig));
                for (key in defaultConfig) {
                    self.configKeys.push(key);
                }
            });

            this._card = ko.observable('{}');

            this.dirty = ko.computed(function() {
                return JSON.stringify(
                    _.extend(
                        JSON.parse(self._card()),
                        self.toJSON())
                ) !== self._card();
            });

            this.isContainer = ko.computed(function() {
                return !!self.get('cards')().length;
            });

            this.setConstraints = function(arr) {
                var self = this;
                arr.forEach(function(constraint){
                    var constraintViewModel = new CardConstraintsViewModel({
                        constraint: koMapping.fromJS(constraint),
                        widgets: self.widgets()
                    });
                    constraintViewModel.constraint.nodes.subscribe(function(val){
                        console.log(val);
                        self.toJSON();
                    }, self)
                    self.constraints.push(constraintViewModel);
                });
            };

            this.sourceData = attributes;
            this.parse(attributes);
            this.parseNodes.call(this, attributes);

            var cardSubscription = this.get('cards').subscribe(function(cards) {
                _.each(cards, function(card, i) {
                    card.get('sortorder')(i);
                });
            });

            var widgetSubscription = this.get('widgets').subscribe(function(widgets) {
                _.each(widgets, function(widget, i) {
                    widget.get('sortorder')(i);
                });
            });

            var nodes = ko.computed(function() {
                return attributes.data.nodes();
            }, this).extend({ throttle: 100 });

            var nodesSubscription = nodes.subscribe(function(){
                this.parseNodes(attributes);
            }, this);

            this.disposables.push(componentIdSubscription);
            this.disposables.push(cardSubscription);
            this.disposables.push(widgetSubscription);
            this.disposables.push(nodes);
            this.disposables.push(nodesSubscription);
            this.disposables.push(this.configJSON);
            this.disposables.push(this.dirty);
            this.disposables.push(this.isContainer);

            this.dispose = function() {
                //console.log('disposing CardModel');
                dispose(self);
            };
        },

        /**
         * parse - parses the passed in attributes into a {@link CardModel}
         * @memberof CardModel.prototype
         * @param  {object} attributes - the properties to seed a {@link CardModel} with
         */
        parse: function(attributes) {
            var self = this;
            // console.log(attributes.data.constraints[0].nodes)
            // console.log(attributes.data.constraints[0].nodes)
            this._attributes = attributes;

            _.each(attributes.data, function(value, key) {
                switch (key) {
                case 'config':
                    var config = {};
                    var configKeys = [];
                    self.configKeys.removeAll();
                    _.each(value, function(configVal, configKey) {
                        if (!ko.isObservable(configVal)) {
                            configVal = ko.observable(configVal);
                        }
                        config[configKey] = configVal;
                        configKeys.push(configKey);
                    });
                    this.set(key, config);
                    self.configKeys(configKeys);
                    break;
                case 'cards':
                    var cards = [];
                    var cardData = _.sortBy(value, 'sortorder');
                    cardData.forEach(function(card) {
                        var cardModel = new CardModel({
                            data: _.extend(card, {
                                nodes: attributes.data.nodes
                            }),
                            datatypelookup: attributes.datatypelookup,
                            cardComponents: attributes.cardComponents
                        });
                        cards.push(cardModel);
                    }, this);
                    this.get('cards')(cards);
                    break;
                case 'widgets':
                    break;
                case 'cardid':
                    this.set('id', value);
                    this.get(key)(value);
                    break;
                case 'constraints':
                    if (this.constraints().length === 0) {
                        this.setConstraints(value);
                    } else {
                        this.constraints().forEach(function(constraint, i){
                            constraint.update(value[i]);
                        });
                    }
                    break;
                case 'name':
                case 'nodegroup_id':
                case 'instructions':
                case 'cssclass':
                case 'helptext':
                case 'helpenabled':
                case 'helptitle':
                case 'cardinality':
                case 'visible':
                case 'active':
                case 'ontologyproperty':
                case 'sortorder':
                case 'component_id':
                    this.get(key)(value);
                    break;
                case 'ontology_properties':
                case 'tiles':
                    this.set(key, koMapping.fromJS(value));
                    break;
                default:
                    this.set(key, value);
                }
            }, this);
            this._card(JSON.stringify(this.toJSON()));
        },

        parseNodes: function() {
            var attributes = this.sourceData;
            var self = this;
            var _nodeIDs = ko.unwrap(this.get('widgets')).map(function(widget) {
                return ko.unwrap(widget.node_id);
            });
            ko.unwrap(this.nodes).forEach(function(node) {
                var widget;
                // TODO: it would be nice to normalize the nodegroup_id names (right now we have several different versions)
                if((ko.unwrap(node.nodeGroupId) || ko.unwrap(node.nodegroup_id)) === ko.unwrap(attributes.data.nodegroup_id) && _nodeIDs.indexOf(node.nodeid) < 0){
                    var datatype = attributes.datatypelookup[ko.unwrap(node.datatype)];
                    var nodeDatatypeSubscription = node.datatype.subscribe(function(){
                        this._card(JSON.stringify(this.toJSON()));
                    }, this);
                    this.disposables.push(nodeDatatypeSubscription);

                    if (datatype.defaultwidget_id) {
                        var cardWidgetData = _.find(attributes.data.widgets, function(widget) {
                            return widget.node_id === node.nodeid;
                        });
                        widget = new CardWidgetModel(cardWidgetData, {
                            node: node,
                            card: this,
                            datatype: datatype,
                            disabled: attributes.data.disabled
                        });
                        this.get('widgets').push(widget);
                    }
                } else if (_nodeIDs.indexOf(node.nodeid) >= 0 && (ko.unwrap(node.nodeGroupId) || ko.unwrap(node.nodegroup_id)) !== ko.unwrap(attributes.data.nodegroup_id)) {
                    widget = ko.unwrap(this.get('widgets')).find(function(widget) {
                        return ko.unwrap(widget.node_id) === node.nodeid;
                    });
                    this.get('widgets').remove(widget);
                }
            }, this);
            this.get('widgets').sort(function(w, ww) {
                return w.get('sortorder')() - ww.get('sortorder')();
            });
            this._card(JSON.stringify(this.toJSON()));
        },

        reset: function() {
            this._attributes.data = JSON.parse(this._card());
            this.get('widgets')().forEach(function(widget){
                var originalWidgetData = _.find(this._attributes.data.widgets, function(origwidget){
                    return widget.node_id() === origwidget.node_id;
                });
                if (originalWidgetData) {
                    widget.configKeys().forEach(function(configKey){
                        widget.config[configKey](originalWidgetData.config[configKey]);
                    });
                    widget.label(originalWidgetData.label);
                    widget.widget_id(originalWidgetData.widget_id);
                }
            }, this);
            this.parse(this._attributes);
        },

        toJSON: function() {
            var self = this;
            var ret = {};
            for (var key in this.attributes) {
                if (key !== 'datatypelookup' && key !== 'ontology_properties' && key !== 'nodes' &&
                 key !== 'widgets' && key !== 'datatypes' && key !== 'data' && key !== 'helpactive' &&
                 key !== 'config') {
                    if (ko.isObservable(this.attributes[key])) {
                        if (key === 'constraints') {
                            ret[key] = [];
                            this.attributes[key]().forEach(function(constraint) {
                                ret[key].push(constraint.toJSON());
                            }, this);
                        } else if (key === 'cards') {
                            ret[key] = [];
                            this.attributes[key]().forEach(function(card) {
                                ret[key].push(card.toJSON());
                            }, this);
                        } else {
                            ret[key] = this.attributes[key]();
                        }
                    } else {
                        ret[key] = this.attributes[key];
                    }
                } else if (key === 'widgets') {
                    var widgets = this.attributes[key]();
                    ret[key] = _.map(widgets, function(widget) {
                        return widget.toJSON();
                    });
                } else if (key === 'config') {
                    var configKeys = this.configKeys();
                    var config = null;
                    if (configKeys.length > 0) {
                        config = {};
                        _.each(configKeys, function(configKey) {
                            config[configKey] = ko.unwrap(self.get('config')[configKey]);
                        });
                    }
                    ret[key] = config;
                }
            }
            return ret;
        },

        save: function(callback) {
            AbstractModel.prototype.save.call(this, function(request, status, self) {
                if (status === 'success') {
                    this._card(JSON.stringify(this.toJSON()));
                }
                if (typeof callback === 'function') {
                    callback(request, status, self);
                }
            }, this);
        }

    });
    return CardModel;
});
<commit_msg>Removes log statement.<commit_after>define([
    'underscore',
    'arches',
    'models/abstract',
    'models/node',
    'models/card-widget',
    'knockout',
    'knockout-mapping',
    'card-components',
    'viewmodels/card-constraints',
    'utils/dispose'
], function(_, arches, AbstractModel, NodeModel, CardWidgetModel, ko, koMapping, cardComponentLookup, CardConstraintsViewModel, dispose) {
    var CardModel = AbstractModel.extend({
        /**
        * A backbone model to manage card data
        * @augments AbstractModel
        * @constructor
        * @name CardModel
        */

        url: arches.urls.card,

        initialize: function(attributes) {
            var self = this;
            this.cards = ko.observableArray();
            this.nodes = attributes.data.nodes;
            this.widgets = ko.observableArray();
            this.tiles = ko.observableArray();

            this.cardid = ko.observable();
            this.nodegroup_id = ko.observable();
            this.name = ko.observable();
            this.instructions = ko.observable();
            this.cssclass = ko.observable();
            this.helptext = ko.observable();
            this.helpenabled = ko.observable();
            this.helptitle = ko.observable();
            this.helpactive = ko.observable(false);
            this.cardinality = ko.observable();
            if (attributes.data.nodegroup) {
                this.cardinality(attributes.data.nodegroup.cardinality);
            }
            this.visible = ko.observable();
            this.active = ko.observable();
            this.ontologyproperty = ko.observable();
            this.sortorder = ko.observable();
            this.disabled = ko.observable();
            this.component_id = ko.observable();
            this.constraints = ko.observableArray();

            this.set('cards', this.cards);
            this.set('nodes', this.nodes);
            this.set('widgets', this.widgets);
            this.set('tiles', this.tiles);

            this.set('cardid', this.cardid);
            this.set('nodegroup_id', this.nodegroup_id);
            this.set('cardinality', this.cardinality);
            this.set('name', this.name);
            this.set('instructions', this.instructions);
            this.set('cssclass', this.cssclass);
            this.set('helptext', this.helptext);
            this.set('helpenabled', this.helpenabled);
            this.set('helptitle', this.helptitle);
            this.set('helpactive', this.helpactive);
            this.set('cardinality', this.cardinality);
            this.set('visible', this.visible);
            this.set('active', this.active);
            this.set('ontologyproperty', this.ontologyproperty);
            this.set('sortorder', this.sortorder);
            this.set('disabled', this.disabled);
            this.set('component_id', this.component_id);
            this.set('config', {});
            this.set('constraints', this.constraints);

            this.cardComponentLookup = cardComponentLookup;
            this.configKeys = ko.observableArray();
            this.disposables = [];

            this.configJSON = ko.computed({
                read: function() {
                    var configJSON = {};
                    var config = this.get('config');
                    _.each(this.configKeys(), function(key) {
                        configJSON[key] = ko.unwrap(config[key]);
                    });
                    return configJSON;
                },
                write: function(value) {
                    var config = this.get('config');
                    for (var key in value) {
                        if (config[key] && config[key]() !== value[key]) {
                            config[key](value[key]);
                        }
                    }
                },
                owner: this
            });

            var componentIdSubscription = this.get('component_id').subscribe(function(value) {
                var key;
                var defaultConfig = JSON.parse(self.cardComponentLookup[value].defaultconfig);
                for (key in defaultConfig) {
                    defaultConfig[key] = ko.observable(defaultConfig[key]);
                }
                var currentConfig = self.get('config');
                self.set('config', _.defaults(currentConfig, defaultConfig));
                for (key in defaultConfig) {
                    self.configKeys.push(key);
                }
            });

            this._card = ko.observable('{}');

            this.dirty = ko.computed(function() {
                return JSON.stringify(
                    _.extend(
                        JSON.parse(self._card()),
                        self.toJSON())
                ) !== self._card();
            });

            this.isContainer = ko.computed(function() {
                return !!self.get('cards')().length;
            });

            this.setConstraints = function(arr) {
                var self = this;
                arr.forEach(function(constraint){
                    var constraintViewModel = new CardConstraintsViewModel({
                        constraint: koMapping.fromJS(constraint),
                        widgets: self.widgets()
                    });
                    constraintViewModel.constraint.nodes.subscribe(function(val){
                        self.toJSON();
                    }, self)
                    self.constraints.push(constraintViewModel);
                });
            };

            this.sourceData = attributes;
            this.parse(attributes);
            this.parseNodes.call(this, attributes);

            var cardSubscription = this.get('cards').subscribe(function(cards) {
                _.each(cards, function(card, i) {
                    card.get('sortorder')(i);
                });
            });

            var widgetSubscription = this.get('widgets').subscribe(function(widgets) {
                _.each(widgets, function(widget, i) {
                    widget.get('sortorder')(i);
                });
            });

            var nodes = ko.computed(function() {
                return attributes.data.nodes();
            }, this).extend({ throttle: 100 });

            var nodesSubscription = nodes.subscribe(function(){
                this.parseNodes(attributes);
            }, this);

            this.disposables.push(componentIdSubscription);
            this.disposables.push(cardSubscription);
            this.disposables.push(widgetSubscription);
            this.disposables.push(nodes);
            this.disposables.push(nodesSubscription);
            this.disposables.push(this.configJSON);
            this.disposables.push(this.dirty);
            this.disposables.push(this.isContainer);

            this.dispose = function() {
                //console.log('disposing CardModel');
                dispose(self);
            };
        },

        /**
         * parse - parses the passed in attributes into a {@link CardModel}
         * @memberof CardModel.prototype
         * @param  {object} attributes - the properties to seed a {@link CardModel} with
         */
        parse: function(attributes) {
            var self = this;
            // console.log(attributes.data.constraints[0].nodes)
            // console.log(attributes.data.constraints[0].nodes)
            this._attributes = attributes;

            _.each(attributes.data, function(value, key) {
                switch (key) {
                case 'config':
                    var config = {};
                    var configKeys = [];
                    self.configKeys.removeAll();
                    _.each(value, function(configVal, configKey) {
                        if (!ko.isObservable(configVal)) {
                            configVal = ko.observable(configVal);
                        }
                        config[configKey] = configVal;
                        configKeys.push(configKey);
                    });
                    this.set(key, config);
                    self.configKeys(configKeys);
                    break;
                case 'cards':
                    var cards = [];
                    var cardData = _.sortBy(value, 'sortorder');
                    cardData.forEach(function(card) {
                        var cardModel = new CardModel({
                            data: _.extend(card, {
                                nodes: attributes.data.nodes
                            }),
                            datatypelookup: attributes.datatypelookup,
                            cardComponents: attributes.cardComponents
                        });
                        cards.push(cardModel);
                    }, this);
                    this.get('cards')(cards);
                    break;
                case 'widgets':
                    break;
                case 'cardid':
                    this.set('id', value);
                    this.get(key)(value);
                    break;
                case 'constraints':
                    if (this.constraints().length === 0) {
                        this.setConstraints(value);
                    } else {
                        this.constraints().forEach(function(constraint, i){
                            constraint.update(value[i]);
                        });
                    }
                    break;
                case 'name':
                case 'nodegroup_id':
                case 'instructions':
                case 'cssclass':
                case 'helptext':
                case 'helpenabled':
                case 'helptitle':
                case 'cardinality':
                case 'visible':
                case 'active':
                case 'ontologyproperty':
                case 'sortorder':
                case 'component_id':
                    this.get(key)(value);
                    break;
                case 'ontology_properties':
                case 'tiles':
                    this.set(key, koMapping.fromJS(value));
                    break;
                default:
                    this.set(key, value);
                }
            }, this);
            this._card(JSON.stringify(this.toJSON()));
        },

        parseNodes: function() {
            var attributes = this.sourceData;
            var self = this;
            var _nodeIDs = ko.unwrap(this.get('widgets')).map(function(widget) {
                return ko.unwrap(widget.node_id);
            });
            ko.unwrap(this.nodes).forEach(function(node) {
                var widget;
                // TODO: it would be nice to normalize the nodegroup_id names (right now we have several different versions)
                if((ko.unwrap(node.nodeGroupId) || ko.unwrap(node.nodegroup_id)) === ko.unwrap(attributes.data.nodegroup_id) && _nodeIDs.indexOf(node.nodeid) < 0){
                    var datatype = attributes.datatypelookup[ko.unwrap(node.datatype)];
                    var nodeDatatypeSubscription = node.datatype.subscribe(function(){
                        this._card(JSON.stringify(this.toJSON()));
                    }, this);
                    this.disposables.push(nodeDatatypeSubscription);

                    if (datatype.defaultwidget_id) {
                        var cardWidgetData = _.find(attributes.data.widgets, function(widget) {
                            return widget.node_id === node.nodeid;
                        });
                        widget = new CardWidgetModel(cardWidgetData, {
                            node: node,
                            card: this,
                            datatype: datatype,
                            disabled: attributes.data.disabled
                        });
                        this.get('widgets').push(widget);
                    }
                } else if (_nodeIDs.indexOf(node.nodeid) >= 0 && (ko.unwrap(node.nodeGroupId) || ko.unwrap(node.nodegroup_id)) !== ko.unwrap(attributes.data.nodegroup_id)) {
                    widget = ko.unwrap(this.get('widgets')).find(function(widget) {
                        return ko.unwrap(widget.node_id) === node.nodeid;
                    });
                    this.get('widgets').remove(widget);
                }
            }, this);
            this.get('widgets').sort(function(w, ww) {
                return w.get('sortorder')() - ww.get('sortorder')();
            });
            this._card(JSON.stringify(this.toJSON()));
        },

        reset: function() {
            this._attributes.data = JSON.parse(this._card());
            this.get('widgets')().forEach(function(widget){
                var originalWidgetData = _.find(this._attributes.data.widgets, function(origwidget){
                    return widget.node_id() === origwidget.node_id;
                });
                if (originalWidgetData) {
                    widget.configKeys().forEach(function(configKey){
                        widget.config[configKey](originalWidgetData.config[configKey]);
                    });
                    widget.label(originalWidgetData.label);
                    widget.widget_id(originalWidgetData.widget_id);
                }
            }, this);
            this.parse(this._attributes);
        },

        toJSON: function() {
            var self = this;
            var ret = {};
            for (var key in this.attributes) {
                if (key !== 'datatypelookup' && key !== 'ontology_properties' && key !== 'nodes' &&
                 key !== 'widgets' && key !== 'datatypes' && key !== 'data' && key !== 'helpactive' &&
                 key !== 'config') {
                    if (ko.isObservable(this.attributes[key])) {
                        if (key === 'constraints') {
                            ret[key] = [];
                            this.attributes[key]().forEach(function(constraint) {
                                ret[key].push(constraint.toJSON());
                            }, this);
                        } else if (key === 'cards') {
                            ret[key] = [];
                            this.attributes[key]().forEach(function(card) {
                                ret[key].push(card.toJSON());
                            }, this);
                        } else {
                            ret[key] = this.attributes[key]();
                        }
                    } else {
                        ret[key] = this.attributes[key];
                    }
                } else if (key === 'widgets') {
                    var widgets = this.attributes[key]();
                    ret[key] = _.map(widgets, function(widget) {
                        return widget.toJSON();
                    });
                } else if (key === 'config') {
                    var configKeys = this.configKeys();
                    var config = null;
                    if (configKeys.length > 0) {
                        config = {};
                        _.each(configKeys, function(configKey) {
                            config[configKey] = ko.unwrap(self.get('config')[configKey]);
                        });
                    }
                    ret[key] = config;
                }
            }
            return ret;
        },

        save: function(callback) {
            AbstractModel.prototype.save.call(this, function(request, status, self) {
                if (status === 'success') {
                    this._card(JSON.stringify(this.toJSON()));
                }
                if (typeof callback === 'function') {
                    callback(request, status, self);
                }
            }, this);
        }

    });
    return CardModel;
});
<|endoftext|>


<commit_before>describe('Drilldown Menu', function() {
  var plugin;
  var $html;
  var template = `

      Item 1

          Item 1A

Item 1Aa

Item 1Ba

Item 1B

Item 1C

      Item 2

Item 2A

Item 2B

 Item 3

`;

  afterEach(function() {
    plugin.destroy();
    $html.remove();
  });

  describe('constructor()', function() {
    it('stores the element and plugin options', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin.$element.should.be.an('object');
      plugin.options.should.be.an('object');
    });
  });

  describe('init()', function() {
    it('stores additional elements', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin.$submenuAnchors.should.be.an('object');
      plugin.$submenus.should.be.an('object');
      plugin.$menuItems.should.be.an('object');
    });
  });

  describe('prepareMenu()', function() {
    it('wraps the submenus', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

    });
    it('adds ARIA attributes', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin.$element.should.have.attr('role', 'tree');

      plugin.$element.find('[data-submenu]').each(function() {
        $(this).should.have.attr('role', 'group');
        $(this).should.have.attr('aria-hidden', 'true');
      });

      plugin.$element.find('.is-drilldown-submenu-parent').each(function() {
        $(this).should.have.attr('aria-haspopup', 'true');
        $(this).should.have.attr('aria-expanded', 'false');
        $(this).should.have.attr('aria-label', $(this).children('a').first().text());
      });

      plugin.$element.find('li:not(.js-drilldown-back)').each(function() {
        $(this).should.have.attr('role', 'treeitem');
      });
    });
  });

  describe('show()', function() {
    it('shows the given submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(0).find('ul').should.have.class('is-active');
    });
    it('toggles ARIA attributes', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      $html.find('li.is-drilldown-submenu-parent').eq(0).should.have.attr('aria-expanded', 'true');
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.attr('aria-hidden', 'false');
    });
    it('fires open.zf.drilldown event', function(done) {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.on('open.zf.drilldown', function() {
        // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
        $html.find('li.is-drilldown-submenu-parent').eq(0).find('ul').should.have.class('is-active');
        done();
      });

      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));
    });
  });

  describe('hide()', function() {
    it('hides the given submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      plugin._hide($html.find('li.is-drilldown-submenu-parent').eq(0).children('ul'));

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.class('is-closing');
    });
    it('toggles ARIA attributes', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      plugin._hide($html.find('li.is-drilldown-submenu-parent').eq(0).children('ul'));

      $html.find('li.is-drilldown-submenu-parent').eq(0).should.have.attr('aria-expanded', 'false');
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.attr('aria-hidden', 'true');
    });
    it('fires hide.zf.drilldown event', function(done) {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      $html.on('hide.zf.drilldown', function() {
        // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
        $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.class('is-closing');
        done();
      });

      plugin._hide($html.find('li.is-drilldown-submenu-parent').eq(0).children('ul'));
    });
  });

  describe('hideAll()', function() {
    it('hides all submenus', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(2));

      plugin._hideAll();

      $html.find('ul[data-submenu].is-active').each(function() {
        // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
        $(this).should.have.class('is-closing');
      });
    });
    it('fires closed.zf.drilldown event', function(done) {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(2));

      $html.one('closed.zf.drilldown', function() {
        $html.find('ul[data-submenu].is-active').each(function() {
          // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
          $(this).should.have.class('is-closing');
        });
        done();
      });

      plugin._hideAll();
    });
  });

  describe('back()', function() {
    it('hides current submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(1));

      $html.find('li.is-drilldown-submenu-parent').eq(1).children('ul').children('.js-drilldown-back').trigger('click');

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(1).children('ul').should.have.class('is-closing');
    });
    it('shows parent submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(1));

      $html.find('li.is-drilldown-submenu-parent').eq(1).children('ul').children('.js-drilldown-back').trigger('click');

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.class('is-active');
    });
  });

  describe('keyboard events', function() {
    // Currently not testable, as triggered event won't move on focus
    it.skip('does not trap focus on root element going down', function() {
      $html = $(template).appendTo('body');
      let $dummyElement = $('Dummy').appendTo('body'); // Dummy target to see if focus is not trapped
      plugin = new Foundation.Drilldown($html, {});

      // Focus last element
      $dummyElement.focus();
      $html.find('> li:last-child > a').trigger(window.mockKeyboardEvent('TAB'));

      document.activeElement.should.not.be.equal($html.find('> li:last-child > a')[0]);

      $dummyElement.remove();
    });
    // Currently not testable, as triggered event won't move on focus
    it.skip('does not trap focus on root element going up', function() {
      let $dummyElement = $('Dummy').appendTo('body'); // Dummy target to see if focus is not trapped
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Focus first element
      $dummyElement.focus();
      $html.find('> li:first-child > a').trigger(window.mockKeyboardEvent('TAB', {shift: true}));

      document.activeElement.should.not.be.equal($html.find('> li:first-child > a')[0]);

      $dummyElement.remove();
    });
    it('closes current sub menu using ESC', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('> li:nth-child(1)'))

      $html.find('> li:nth-child(1) > ul > li:first-child > a').focus()
        .trigger(window.mockKeyboardEvent('ESCAPE'));

      $html.find('> li:nth-child(1) > ul').should.have.class('is-closing');
    });
    it('moves focus to next element on TAB', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(1) > a').focus()
        .trigger(window.mockKeyboardEvent('TAB'));

      document.activeElement.should.be.equal($html.find('> li:nth-child(2) > a')[0]);
    });
    it('moves focus to previous element on TAB', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(2) > a').focus()
        .trigger(window.mockKeyboardEvent('TAB', {shift: true}));

      document.activeElement.should.be.equal($html.find('> li:nth-child(1) > a')[0]);
    });
    it('moves focus to next element on ARROW_DOWN', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(1) > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_DOWN'));

      document.activeElement.should.be.equal($html.find('> li:nth-child(2) > a')[0]);
    });
    it('moves focus to previous element on ARROW_UP', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(2) > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_UP'));

      document.activeElement.should.be.equal($html.find('> li:nth-child(1) > a')[0]);
    });
    it('opens child element on ARROW_RIGHT', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(1) > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_RIGHT'));

      $html.find('> li:nth-child(1) > ul').should.have.class('is-active');
    });
    it('closes child element on ARROW_LEFT', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('> li:nth-child(1)'))

      $html.find('> li:nth-child(1) > ul > li:first-child > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_LEFT'));

      $html.find('> li:nth-child(1) > ul').should.have.class('is-closing');
    });
  });
});<commit_msg>Added test for fix for  #10803.<commit_after>describe('Drilldown Menu', function() {
  var plugin;
  var $html;
  var template = `

      Item 1

          Item 1A

Item 1Aa

Item 1Ba

Item 1B

Item 1C

      Item 2

Item 2A

Item 2B

 Item 3

`;

  afterEach(function() {
    plugin.destroy();
    $html.remove();
  });

  describe('constructor()', function() {
    it('stores the element and plugin options', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin.$element.should.be.an('object');
      plugin.options.should.be.an('object');
    });
  });

  describe('init()', function() {
    it('stores additional elements', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin.$submenuAnchors.should.be.an('object');
      plugin.$submenus.should.be.an('object');
      plugin.$menuItems.should.be.an('object');
    });
  });

  describe('prepareMenu()', function() {
    it('wraps the submenus', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

    });
    it('adds ARIA attributes', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin.$element.should.have.attr('role', 'tree');

      plugin.$element.find('[data-submenu]').each(function() {
        $(this).should.have.attr('role', 'group');
        $(this).should.have.attr('aria-hidden', 'true');
      });

      plugin.$element.find('.is-drilldown-submenu-parent').each(function() {
        $(this).should.have.attr('aria-haspopup', 'true');
        $(this).should.have.attr('aria-expanded', 'false');
        $(this).should.have.attr('aria-label', $(this).children('a').first().text());
      });

      plugin.$element.find('li:not(.js-drilldown-back)').each(function() {
        $(this).should.have.attr('role', 'treeitem');
      });
    });
  });

  describe('show()', function() {
    it('shows the given submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(0).find('ul').should.have.class('is-active');
    });
    it('toggles ARIA attributes', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      $html.find('li.is-drilldown-submenu-parent').eq(0).should.have.attr('aria-expanded', 'true');
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.attr('aria-hidden', 'false');
    });
    it('fires open.zf.drilldown event', function(done) {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.on('open.zf.drilldown', function() {
        // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
        $html.find('li.is-drilldown-submenu-parent').eq(0).find('ul').should.have.class('is-active');
        done();
      });

      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));
    });
  });

  describe('hide()', function() {
    it('hides the given submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      plugin._hide($html.find('li.is-drilldown-submenu-parent').eq(0).children('ul'));

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.class('is-closing');
    });
    it('toggles ARIA attributes', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      plugin._hide($html.find('li.is-drilldown-submenu-parent').eq(0).children('ul'));

      $html.find('li.is-drilldown-submenu-parent').eq(0).should.have.attr('aria-expanded', 'false');
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.attr('aria-hidden', 'true');
    });
    it('fires hide.zf.drilldown event', function(done) {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(0));

      $html.on('hide.zf.drilldown', function() {
        // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
        $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.class('is-closing');
        done();
      });

      plugin._hide($html.find('li.is-drilldown-submenu-parent').eq(0).children('ul'));
    });
  });

  describe('hideAll()', function() {
    it('hides all submenus', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(2));

      plugin._hideAll();

      $html.find('ul[data-submenu].is-active').each(function() {
        // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
        $(this).should.have.class('is-closing');
      });
    });
    it('fires closed.zf.drilldown event', function(done) {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(2));

      $html.one('closed.zf.drilldown', function() {
        $html.find('ul[data-submenu].is-active').each(function() {
          // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
          $(this).should.have.class('is-closing');
        });
        done();
      });

      plugin._hideAll();
    });
  });

  describe('back()', function() {
    it('hides current submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(1));

      $html.find('li.is-drilldown-submenu-parent').eq(1).children('ul').children('.js-drilldown-back').trigger('click');

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(1).children('ul').should.have.class('is-closing');
    });
    it('shows parent submenu', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open one first
      plugin._show($html.find('li.is-drilldown-submenu-parent').eq(1));

      $html.find('li.is-drilldown-submenu-parent').eq(1).children('ul').children('.js-drilldown-back').trigger('click');

      // Checking with .be.hidden is not possible because they don't have display: block but z-index: -1
      $html.find('li.is-drilldown-submenu-parent').eq(0).children('ul').should.have.class('is-active');
    });
  });

  describe('keyboard events', function() {
    // Currently not testable, as triggered event won't move on focus
    it.skip('does not trap focus on root element going down', function() {
      $html = $(template).appendTo('body');
      let $dummyElement = $('Dummy').appendTo('body'); // Dummy target to see if focus is not trapped
      plugin = new Foundation.Drilldown($html, {});

      // Focus last element
      $dummyElement.focus();
      $html.find('> li:last-child > a').trigger(window.mockKeyboardEvent('TAB'));

      document.activeElement.should.not.be.equal($html.find('> li:last-child > a')[0]);

      $dummyElement.remove();
    });
    // Currently not testable, as triggered event won't move on focus
    it.skip('does not trap focus on root element going up', function() {
      let $dummyElement = $('Dummy').appendTo('body'); // Dummy target to see if focus is not trapped
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Focus first element
      $dummyElement.focus();
      $html.find('> li:first-child > a').trigger(window.mockKeyboardEvent('TAB', {shift: true}));

      document.activeElement.should.not.be.equal($html.find('> li:first-child > a')[0]);

      $dummyElement.remove();
    });
    it('closes current sub menu using ESC', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('> li:nth-child(1)'))

      $html.find('> li:nth-child(1) > ul > li:first-child > a').focus()
        .trigger(window.mockKeyboardEvent('ESCAPE'));

      $html.find('> li:nth-child(1) > ul').should.have.class('is-closing');
    });
    it('moves focus to next element on TAB', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(1) > a').focus()
        .trigger(window.mockKeyboardEvent('TAB'));

      document.activeElement.should.be.equal($html.find('> li:nth-child(2) > a')[0]);
    });
    it('moves focus to previous element on TAB', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(2) > a').focus()
        .trigger(window.mockKeyboardEvent('TAB', {shift: true}));

      document.activeElement.should.be.equal($html.find('> li:nth-child(1) > a')[0]);
    });
    it('moves focus to next element on ARROW_DOWN', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(1) > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_DOWN'));

      document.activeElement.should.be.equal($html.find('> li:nth-child(2) > a')[0]);
    });
    it('moves focus to previous element on ARROW_UP', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(2) > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_UP'));

      document.activeElement.should.be.equal($html.find('> li:nth-child(1) > a')[0]);
    });
    it('opens child element on ARROW_RIGHT', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      $html.find('> li:nth-child(1) > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_RIGHT'));

      $html.find('> li:nth-child(1) > ul').should.have.class('is-active');
    });
    it('focuses parent link if parentLink is true', function(done) {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {parentLink: true});

      $html.find('> li:nth-child(1) > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_RIGHT'));

      setTimeout(function() { // Timeout to make sure transition has ended
        $html.find('> li:nth-child(1) > ul > li[data-is-parent-link] a')[0].should.be.equal(document.activeElement);
        done();
      }, 500);
    });
    it('closes child element on ARROW_LEFT', function() {
      $html = $(template).appendTo('body');
      plugin = new Foundation.Drilldown($html, {});

      // Open it first
      plugin._show($html.find('> li:nth-child(1)'))

      $html.find('> li:nth-child(1) > ul > li:first-child > a').focus()
        .trigger(window.mockKeyboardEvent('ARROW_LEFT'));

      $html.find('> li:nth-child(1) > ul').should.have.class('is-closing');
    });
  });
});<|endoftext|>


<commit_before>/**
 * @license Licensed under the Apache License, Version 2.0 (the "License"):
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 * @fileoverview Front end code relevant only to the Desktop version of
*                Ardublockly.
 */
'use strict';

/** Create a namespace for the application. */
var Ardublockly = Ardublockly || {};

/**
 * Checks if the current JavaScript is loaded in the rendered process of
 * Electron. Works even if the node integration is turned off.
 * @return {!boolean} True if Ardublockly running in Electron application
 */
Ardublockly.isRunningElectron = function() {
  return navigator.userAgent.toLowerCase().indexOf('ardublockly') > -1;
};

/**
 * Because the Node integration causes conflicts with the way JavaScript
 * libraries are declared as modules, this declares them in the window context.
 * This function is to be executed as soon as this file is loaded, and because
 * of that this file must be called in the HTML before the Materialize library
 * is loaded.
 */
(function loadJsInElectron(){
  if (Ardublockly.isRunningElectron()) {
    var projectLocator = require('electron').remote.require(
        './projectlocator.js');
    var projectRoot = projectLocator.getProjectRootPath();
    window.$ = window.jQuery = require(projectRoot +
        '/ardublockly/js_libs/jquery-2.1.3.min.js');
    window.Hammer = require(projectRoot + '/ardublockly/js_libs/hammer.min.js');
    window.JsDiff = require(projectRoot + '/ardublockly/js_libs/diff.js');
  }
})();

/** Sets all the elements using the container class to have a width of 100%. */
Ardublockly.containerFullWidth = function() {
  var containers = $('.container');
  for (var i = 0; i < containers.length; i++) {
    containers[i].style.width = '100%';
  }
};

/** Hides the side menu button. */
Ardublockly.hideSideMenuButton = function() {
  var sideMenuButton = document.getElementById('button-collapse');
  sideMenuButton.style.setProperty ('display', 'none', 'important');
};

/**
 * Launches a materialize modal as a text prompt
 * @param {string} message Main text message for the window prompt.
 * @param {string=} defaultValue Input string to be displayed by default.
 * @param {function} callback To process the user input.
 */
Ardublockly.htmlPrompt = function(message, defaultValue, callback) {
  $('#gen_prompt_message').text('');
  $('#gen_prompt_message').append(message);
  $('#gen_prompt_input').val(defaultValue);
  // Bind callback events to buttons
  $('#gen_prompt_ok_link').bind('click', function() {
    callback($('#gen_prompt_input').val());
  });
  $('#gen_prompt_cancel_link').bind('click', function() {
    callback(null);
  });
  $('#gen_prompt').openModal();
  window.location.hash = '';
};

/**
 * Add click listeners to the Compiler and Sketch input fields to launch the
 * Electron file/folder browsers.
 */
Ardublockly.bindSettingsPathInputs = function() {
  var dialog = require('electron').remote.dialog;

  // Compiler path
  var compilerEl = document.getElementById('settings_compiler_location');
  compilerEl.readOnly = true;
  Ardublockly.bindClick_(compilerEl, function() {
    dialog.showOpenDialog({
      title: 'Select the Arduino IDE executable',
      buttonLabel: 'Select',
      properties: ['openFile']
    }, function (files) {
      if (files && files[0]) {
        ArdublocklyServer.setCompilerLocation(files[0], function(jsonObj) {
          Ardublockly.setCompilerLocationHtml(
              ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
        });
      }
    })
  });
  // Sketch path
  var sketchEl = document.getElementById('settings_sketch_location');
  sketchEl.readOnly = true;
  Ardublockly.bindClick_(sketchEl, function() {
    dialog.showOpenDialog({
      title: 'Select the Arduino IDE executable',
      buttonLabel: 'Select',
      properties: ['openDirectory']
    }, function (folders) {
      if (folders && folders[0]) {
        ArdublocklyServer.setSketchLocation(folders[0], function(jsonObj) {
          Ardublockly.setSketchLocationHtml(
              ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
        });
      }
    })
  });
};

/** Initialize Ardublockly code required for Electron on page load. */
window.addEventListener('load', function load(event) {
  window.removeEventListener('load', load, false);
  if (Ardublockly.isRunningElectron()) {
    // Edit the page layout for better appearance on desktop
    Ardublockly.containerFullWidth();
    Ardublockly.hideSideMenuButton();

    // Open the file or directory browsers when clicking on the Settings inputs
    Ardublockly.bindSettingsPathInputs();

    // Prevent browser zoom changes like pinch-to-zoom
    var webFrame = require('electron').webFrame;
    webFrame.setZoomLevelLimits(1, 1);

    // Electron does not offer a prompt, so replace Blocks version with modal
    // Original signature: function(message, opt_defaultInput, opt_callback)
    Blockly.prompt = Ardublockly.htmlPrompt;
  }
});
<commit_msg>Electron: Add rendered logging to the winston log file.<commit_after>/**
 * @license Licensed under the Apache License, Version 2.0 (the "License"):
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 * @fileoverview Front end code relevant only to the Desktop version of
*                Ardublockly.
 */
//'use strict';

/** Create a namespace for the application. */
var Ardublockly = Ardublockly || {};

/**
 * Checks if the current JavaScript is loaded in the rendered process of
 * Electron. Works even if the node integration is turned off.
 * @return {!boolean} True if Ardublockly running in Electron application
 */
Ardublockly.isRunningElectron = function() {
  return navigator.userAgent.toLowerCase().indexOf('ardublockly') > -1;
};

/**
 * Because the Node integration causes conflicts with the way JavaScript
 * libraries are declared as modules, this declares them in the window context.
 * This function is to be executed as soon as this file is loaded, and because
 * of that this file must be called in the HTML before the Materialize library
 * is loaded.
 */
(function loadJsInElectron(){
  if (Ardublockly.isRunningElectron()) {
    var projectLocator = require('electron').remote.require(
        './projectlocator.js');
    var projectRoot = projectLocator.getProjectRootPath();
    window.$ = window.jQuery = require(projectRoot +
        '/ardublockly/js_libs/jquery-2.1.3.min.js');
    window.Hammer = require(projectRoot + '/ardublockly/js_libs/hammer.min.js');
    window.JsDiff = require(projectRoot + '/ardublockly/js_libs/diff.js');
  }
})();

/** Sets all the elements using the container class to have a width of 100%. */
Ardublockly.containerFullWidth = function() {
  var containers = $('.container');
  for (var i = 0; i < containers.length; i++) {
    containers[i].style.width = '100%';
  }
};

/** Hides the side menu button. */
Ardublockly.hideSideMenuButton = function() {
  var sideMenuButton = document.getElementById('button-collapse');
  sideMenuButton.style.setProperty ('display', 'none', 'important');
};

/**
 * Launches a materialize modal as a text prompt
 * @param {string} message Main text message for the window prompt.
 * @param {string=} defaultValue Input string to be displayed by default.
 * @param {function} callback To process the user input.
 */
Ardublockly.htmlPrompt = function(message, defaultValue, callback) {
  $('#gen_prompt_message').text('');
  $('#gen_prompt_message').append(message);
  $('#gen_prompt_input').val(defaultValue);
  // Bind callback events to buttons
  $('#gen_prompt_ok_link').bind('click', function() {
    callback($('#gen_prompt_input').val());
  });
  $('#gen_prompt_cancel_link').bind('click', function() {
    callback(null);
  });
  $('#gen_prompt').openModal();
  window.location.hash = '';
};

/**
 * Add click listeners to the Compiler and Sketch input fields to launch the
 * Electron file/folder browsers.
 */
Ardublockly.bindSettingsPathInputs = function() {
  var dialog = require('electron').remote.dialog;

  // Compiler path
  var compilerEl = document.getElementById('settings_compiler_location');
  compilerEl.readOnly = true;
  Ardublockly.bindClick_(compilerEl, function() {
    dialog.showOpenDialog({
      title: 'Select the Arduino IDE executable',
      buttonLabel: 'Select',
      properties: ['openFile']
    }, function (files) {
      if (files && files[0]) {
        ArdublocklyServer.setCompilerLocation(files[0], function(jsonObj) {
          Ardublockly.setCompilerLocationHtml(
              ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
        });
      }
    })
  });
  // Sketch path
  var sketchEl = document.getElementById('settings_sketch_location');
  sketchEl.readOnly = true;
  Ardublockly.bindClick_(sketchEl, function() {
    dialog.showOpenDialog({
      title: 'Select the Arduino IDE executable',
      buttonLabel: 'Select',
      properties: ['openDirectory']
    }, function (folders) {
      if (folders && folders[0]) {
        ArdublocklyServer.setSketchLocation(folders[0], function(jsonObj) {
          Ardublockly.setSketchLocationHtml(
              ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
        });
      }
    })
  });
};

/** Wraps the console.log warn and errors to send data to logging file. */
Ardublockly.redirectConsoleLogging = function() {
  var winston = require('electron').remote.require('winston');
  var consoleLog = console.log;
  var consoleWarning = console.warning;
  var consoleError = console.error;

  // This is magic from Stack Overflow
  // http://stackoverflow.com/questions/14172455/get-name-and-line-of-calling-function-in-node-js
  Object.defineProperty(global, '__stack', {
    get: function() {
      var orig = Error.prepareStackTrace;
      Error.prepareStackTrace = function(_, stack) {
        return stack;
      };
      var err = new Error;
      Error.captureStackTrace(err, arguments.callee);
      var stack = err.stack;
      Error.prepareStackTrace = orig;
      return stack;
    }
  });
  Object.defineProperty(global, '__stackfilename', {
    get: function() {
      return __stack[2].getFileName();
    }
  });
  Object.defineProperty(global, '__line', {
    get: function() {
      return __stack[2].getLineNumber();
    }
  });
  Object.defineProperty(global, '__function', {
    get: function() {
      return __stack[2].getFunctionName();
    }
  });

  // Wrapping console logging
  console.log = function(logMessage){
      consoleLog.apply(console, arguments);
      var tagRenderer = '[Renderer "' + __stackfilename + ':' + __function +
                        '():L' + __line + '"] ';
      winston.info(tagRenderer + logMessage);
  };
  console.warning = function(warnMessage){
     consoleWarning.apply(console, arguments);
     var tagRenderer = '[Renderer "' + __stackfilename + ':' + __function +
                        '():L' + __line + '"] ';
     winston.warn(tagRenderer + warnMessage);
  };
  console.error = function(errMessage){
     consoleError.apply(console, arguments);
     var tagRenderer = '[Renderer "' + __stackfilename + ':' + __function +
                        '():L' + __line + '"] ';
     winston.error(tagRenderer + errMessage);
  };
};

/** Initialize Ardublockly code required for Electron on page load. */
window.addEventListener('load', function load(event) {
  window.removeEventListener('load', load, false);
  if (Ardublockly.isRunningElectron()) {
    // Edit the page layout for better appearance on desktop
    Ardublockly.containerFullWidth();
    Ardublockly.hideSideMenuButton();

    // Open the file or directory browsers when clicking on the Settings inputs
    Ardublockly.bindSettingsPathInputs();

    // Prevent browser zoom changes like pinch-to-zoom
    var webFrame = require('electron').webFrame;
    webFrame.setZoomLevelLimits(1, 1);

    Ardublockly.redirectConsoleLogging();

    // Electron does not offer a prompt, so replace Blocks version with modal
    // Original signature: function(message, opt_defaultInput, opt_callback)
    Blockly.prompt = Ardublockly.htmlPrompt;
  }
});
<|endoftext|>


<commit_before>var httpProxy = require('../lib/http-proxy'),
    expect    = require('expect.js'),
    http      = require('http');

describe('lib/http-proxy.js', function() {

  describe('#createProxyServer with target options for integration into existing server', function () {
    it('should proxy the request using the web proxy handler', function (done) {
      var proxy = httpProxy.createProxyServer({
        target: 'http://127.0.0.1:8080'
      });

      function requestHandler(req, res) {
        proxy.web(req, res);
      }

      var proxyServer = http.createServer(requestHandler);

      var source = http.createServer(function(req, res) {
        source.close();
        proxyServer.close();
        expect(req.method).to.eql('GET');
        expect(req.headers.host.split(':')[1]).to.eql('8082');
        done();
      });

      proxyServer.listen('8082');
      source.listen('8080');

      http.request('http://127.0.0.1:8082', function() {}).end();
    });
  });

  describe('#createProxyServer() for integration into existing server with error response', function () {
    it('should proxy the request and handle error via callback', function(done) {
      var proxy = httpProxy.createProxyServer({
        target: 'http://127.0.0.1:8080'
      });

      var proxyServer = http.createServer(requestHandler);

      function requestHandler(req, res) {
        proxy.web(req, res, function (err) {
          proxyServer.close();
          expect(err).to.be.an(Error);
          expect(err.code).to.be('ECONNREFUSED');
          done();
        });
      }

      proxyServer.listen('8082');

      http.request({
        hostname: '127.0.0.1',
        port: '8082',
        method: 'GET',
      }, function() {}).end();
    });

    it('should proxy the request and handle error event listener', function(done) {
      var proxy = httpProxy.createProxyServer({
        target: 'http://127.0.0.1:8080'
      });

      var proxyServer = http.createServer(requestHandler);

      function requestHandler(req, res) {
        proxy.once('error', function (err, errReq, errRes) {
          proxyServer.close();
          expect(err).to.be.an(Error);
          expect(errReq).to.be.equal(req);
          expect(errRes).to.be.equal(res);
          expect(err.code).to.be('ECONNREFUSED');
          done();
        });

        proxy.web(req, res);
      }

      proxyServer.listen('8082');

      http.request({
        hostname: '127.0.0.1',
        port: '8082',
        method: 'GET',
      }, function() {}).end();
    });

  });

});<commit_msg>[tests] this file is not necessary anymore<commit_after><|endoftext|>


<commit_before>/*jshint expr:true*/
var T = require('../..');
var fs = T.fs;
var should = T.should;

describe('FileStub', function() {
  describe('stubTree()', function() {
    it('should fill in missing intermediate dirs', function(testDone) {
      var incomplete = [
        '/root',
        '/root/d1/d2/f1.js',
        '/root/d1/d2/d3/d4/f2.js'
      ];
      var complete = [
        '/root',
        '/root/d1',
        '/root/d1/d2',
        '/root/d1/d2/d3',
        '/root/d1/d2/d3/d4',
        '/root/d1/d2/f1.js',
        '/root/d1/d2/d3/d4/f2.js'
      ];
      this.stubTree(incomplete);
      complete.forEach(function(path) {
        fs.existsSync(path).should.equal(true);
      });
      fs.readdirSync('/root').should.deep.equal(['d1']);
      fs.readdirSync('/root/d1').should.deep.equal(['d2']);
      fs.readdirSync('/root/d1/d2').should.deep.equal(['f1.js', 'd3']);
      fs.readdirSync('/root/d1/d2/d3/d4').should.deep.equal(['f2.js']);
      testDone();
    });
  });

  describe('FileStub()', function() {
    it('should pass defaults to fs.statsSync', function(testDone) {
      this.stubFile(this.paths[0]).make();
      assertDefaultsMatch(fs.statSync(this.paths[0]));
      testDone();
    });

    it('should pass defaults to fs.stats', function(testDone) {
      this.stubFile(this.paths[0]).make();
      fs.stat(this.paths[0], function(err, stats) {
        T.should.equal(err, null);
        assertDefaultsMatch(stats);
        testDone();
      });
    });
  });

  describe('stat()', function() {
    it('should set stats property', function(testDone) {
      this.stubFile(this.paths[0]).stat('size', 1).stat('gid', 1000).make();
      fs.statSync(this.paths[0]).size.should.equal(1);
      fs.statSync(this.paths[0]).gid.should.equal(1000);

      // Redefine stats for the same file.
      this.stubFile(this.paths[0]).stat('size', 2).stat('gid', 2000).make();
      fs.statSync(this.paths[0]).size.should.equal(2);
      fs.statSync(this.paths[0]).gid.should.equal(2000);
      testDone();
    });
  });

  describe('buffer()', function() {
    it('should set readFileSync output from string', function(testDone) {
      this.stubFile(this.paths[0]).buffer(this.strings[0]).make();
      fs.readFileSync(this.paths[0]).toString().should.equal(this.strings[0]);
      testDone();
    });

    it('should set readFileSync output from buffer', function(testDone) {
      this.stubFile(this.paths[0]).buffer(new Buffer(this.strings[0])).make();
      fs.readFileSync(this.paths[0]).toString().should.equal(this.strings[0]);
      testDone();
    });

    it('should set readFile output', function(testDone) {
      var self = this;
      this.stubFile(this.paths[0]).buffer(this.strings[0]).make();
      fs.readFile(this.paths[0], function(err, data) {
        T.should.equal(err, null);
        data.toString().should.equal(self.strings[0]);
        testDone();
      });
    });

    it('should set stat size', function(testDone) {
      this.stubFile(this.paths[0]).buffer(this.strings[0]).make();
      fs.statSync(this.paths[0]).size.should.equal(this.strings[0].length + 1);
      testDone();
    });
  });

  describe('readdir()', function() {
    it('should stub readdirSync when passed false', function(testDone) {
      var self = this;
      this.stubFile(this.paths[0]).readdir(false).make();
      (function() {
        fs.readdirSync(self.paths[0]);
      }).should.Throw(Error, 'ENOTDIR, not a directory ' + this.paths[0]);
      testDone();
    });

    it('should stub isFile/isDirectory when passed false', function(testDone) {
      this.stubFile(this.paths[0]).make(); // Without paths.
      var stats = fs.statSync(this.paths[0]);
      stats.isDirectory().should.equal(false);
      stats.isFile().should.equal(true);
      testDone();
    });

    it('should accept array of FileStub objects to support trees', function(testDone) {
      /**
       * Create this tree:
       *
       * /a
       * /a/b
       * /a/b2
       * /a2
       * /a3
       * /a3/b4
       * /a3/b4/c
       *
       */
      this.stubFile(this.paths[0]).readdir([
        this.stubFile(this.paths[0] + '/a').readdir([
          this.stubFile(this.paths[0] + '/a/b'),
          this.stubFile(this.paths[0] + '/a/b2')
        ]),
        this.stubFile(this.paths[0] + '/a2'),
        this.stubFile(this.paths[0] + '/a3').readdir([
          this.stubFile(this.paths[0] + '/a3/b4').readdir([
            this.stubFile(this.paths[0] + '/a3/b4/c')
          ])
        ])
      ]).make();

      // Verify file types and directory contents.
      fs.statSync(this.paths[0] + '/a').isDirectory().should.equal(true);
      fs.readdirSync(this.paths[0] + '/a').should.deep.equal([
        'b',
        'b2'
      ]);
      fs.statSync(this.paths[0] + '/a/b').isFile().should.equal(true);
      fs.statSync(this.paths[0] + '/a/b2').isFile().should.equal(true);
      fs.statSync(this.paths[0] + '/a2').isFile().should.equal(true);
      fs.statSync(this.paths[0] + '/a3').isDirectory().should.equal(true);
      fs.readdirSync(this.paths[0] + '/a3').should.deep.equal([
        'b4'
      ]);
      fs.statSync(this.paths[0] + '/a3/b4').isDirectory().should.equal(true);
      fs.readdirSync(this.paths[0] + '/a3/b4').should.deep.equal([
        'c'
      ]);
      fs.statSync(this.paths[0] + '/a3/b4/c').isFile().should.equal(true);
      testDone();
    });
  });
});

function assertDefaultsMatch(stats) {
  stats.dev.should.equal(2114);
  stats.ino.should.equal(48064969);
  stats.mode.should.equal(33188);
  stats.nlink.should.equal(1);
  stats.uid.should.equal(85);
  stats.gid.should.equal(100);
  stats.rdev.should.equal(0);
  stats.size.should.equal(527);
  stats.blksize.should.equal(4096);
  stats.blocks.should.equal(8);
  var time = 'Mon, 10 Oct 2011 23:24:11 GMT';
  stats.atime.should.equal(time);
  stats.mtime.should.equal(time);
  stats.ctime.should.equal(time);
  stats.isFile().should.equal(true);
  stats.isDirectory().should.equal(false);
}
<commit_msg>test(FileStub): Add coverage<commit_after>/*jshint expr:true*/
var T = require('../..');
var fs = T.fs;
var should = T.should;

describe('FileStub', function() {
  describe('stubTree()', function() {
    it('should fill in missing intermediate dirs', function(testDone) {
      var incomplete = [
        '/root',
        '/root/d1/d2/f1.js',
        '/root/d1/d2/d3/d4/f2.js'
      ];
      var complete = [
        '/root',
        '/root/d1',
        '/root/d1/d2',
        '/root/d1/d2/d3',
        '/root/d1/d2/d3/d4',
        '/root/d1/d2/f1.js',
        '/root/d1/d2/d3/d4/f2.js'
      ];
      this.stubTree(incomplete);
      complete.forEach(function(path) {
        fs.existsSync(path).should.equal(true);
      });
      fs.readdirSync('/root').should.deep.equal(['d1']);
      fs.statSync('/root').isDirectory().should.deep.equal(true);
      fs.readdirSync('/root/d1').should.deep.equal(['d2']);
      fs.statSync('/root/d1').isDirectory().should.deep.equal(true);
      fs.readdirSync('/root/d1/d2').should.deep.equal(['f1.js', 'd3']);
      fs.statSync('/root/d1/d2').isDirectory().should.deep.equal(true);
      fs.statSync('/root/d1/d2/f1.js').isDirectory().should.deep.equal(false);
      fs.readdirSync('/root/d1/d2/d3/d4').should.deep.equal(['f2.js']);
      fs.statSync('/root/d1/d2/d3/d4').isDirectory().should.deep.equal(true);
      fs.statSync('/root/d1/d2/d3/d4/f2.js').isDirectory().should.deep.equal(false);
      testDone();
    });
  });

  describe('FileStub()', function() {
    it('should pass defaults to fs.statsSync', function(testDone) {
      this.stubFile(this.paths[0]).make();
      assertDefaultsMatch(fs.statSync(this.paths[0]));
      testDone();
    });

    it('should pass defaults to fs.stats', function(testDone) {
      this.stubFile(this.paths[0]).make();
      fs.stat(this.paths[0], function(err, stats) {
        T.should.equal(err, null);
        assertDefaultsMatch(stats);
        testDone();
      });
    });
  });

  describe('stat()', function() {
    it('should set stats property', function(testDone) {
      this.stubFile(this.paths[0]).stat('size', 1).stat('gid', 1000).make();
      fs.statSync(this.paths[0]).size.should.equal(1);
      fs.statSync(this.paths[0]).gid.should.equal(1000);

      // Redefine stats for the same file.
      this.stubFile(this.paths[0]).stat('size', 2).stat('gid', 2000).make();
      fs.statSync(this.paths[0]).size.should.equal(2);
      fs.statSync(this.paths[0]).gid.should.equal(2000);
      testDone();
    });
  });

  describe('buffer()', function() {
    it('should set readFileSync output from string', function(testDone) {
      this.stubFile(this.paths[0]).buffer(this.strings[0]).make();
      fs.readFileSync(this.paths[0]).toString().should.equal(this.strings[0]);
      testDone();
    });

    it('should set readFileSync output from buffer', function(testDone) {
      this.stubFile(this.paths[0]).buffer(new Buffer(this.strings[0])).make();
      fs.readFileSync(this.paths[0]).toString().should.equal(this.strings[0]);
      testDone();
    });

    it('should set readFile output', function(testDone) {
      var self = this;
      this.stubFile(this.paths[0]).buffer(this.strings[0]).make();
      fs.readFile(this.paths[0], function(err, data) {
        T.should.equal(err, null);
        data.toString().should.equal(self.strings[0]);
        testDone();
      });
    });

    it('should set stat size', function(testDone) {
      this.stubFile(this.paths[0]).buffer(this.strings[0]).make();
      fs.statSync(this.paths[0]).size.should.equal(this.strings[0].length + 1);
      testDone();
    });
  });

  describe('readdir()', function() {
    it('should stub readdirSync when passed false', function(testDone) {
      var self = this;
      this.stubFile(this.paths[0]).readdir(false).make();
      (function() {
        fs.readdirSync(self.paths[0]);
      }).should.Throw(Error, 'ENOTDIR, not a directory ' + this.paths[0]);
      testDone();
    });

    it('should stub isFile/isDirectory when passed false', function(testDone) {
      this.stubFile(this.paths[0]).make(); // Without paths.
      var stats = fs.statSync(this.paths[0]);
      stats.isDirectory().should.equal(false);
      stats.isFile().should.equal(true);
      testDone();
    });

    it('should accept array of FileStub objects to support trees', function(testDone) {
      /**
       * Create this tree:
       *
       * /a
       * /a/b
       * /a/b2
       * /a2
       * /a3
       * /a3/b4
       * /a3/b4/c
       *
       */
      this.stubFile(this.paths[0]).readdir([
        this.stubFile(this.paths[0] + '/a').readdir([
          this.stubFile(this.paths[0] + '/a/b'),
          this.stubFile(this.paths[0] + '/a/b2')
        ]),
        this.stubFile(this.paths[0] + '/a2'),
        this.stubFile(this.paths[0] + '/a3').readdir([
          this.stubFile(this.paths[0] + '/a3/b4').readdir([
            this.stubFile(this.paths[0] + '/a3/b4/c')
          ])
        ])
      ]).make();

      // Verify file types and directory contents.
      fs.statSync(this.paths[0] + '/a').isDirectory().should.equal(true);
      fs.readdirSync(this.paths[0] + '/a').should.deep.equal([
        'b',
        'b2'
      ]);
      fs.statSync(this.paths[0] + '/a/b').isFile().should.equal(true);
      fs.statSync(this.paths[0] + '/a/b2').isFile().should.equal(true);
      fs.statSync(this.paths[0] + '/a2').isFile().should.equal(true);
      fs.statSync(this.paths[0] + '/a3').isDirectory().should.equal(true);
      fs.readdirSync(this.paths[0] + '/a3').should.deep.equal([
        'b4'
      ]);
      fs.statSync(this.paths[0] + '/a3/b4').isDirectory().should.equal(true);
      fs.readdirSync(this.paths[0] + '/a3/b4').should.deep.equal([
        'c'
      ]);
      fs.statSync(this.paths[0] + '/a3/b4/c').isFile().should.equal(true);
      testDone();
    });
  });
});

function assertDefaultsMatch(stats) {
  stats.dev.should.equal(2114);
  stats.ino.should.equal(48064969);
  stats.mode.should.equal(33188);
  stats.nlink.should.equal(1);
  stats.uid.should.equal(85);
  stats.gid.should.equal(100);
  stats.rdev.should.equal(0);
  stats.size.should.equal(527);
  stats.blksize.should.equal(4096);
  stats.blocks.should.equal(8);
  var time = 'Mon, 10 Oct 2011 23:24:11 GMT';
  stats.atime.should.equal(time);
  stats.mtime.should.equal(time);
  stats.ctime.should.equal(time);
  stats.isFile().should.equal(true);
  stats.isDirectory().should.equal(false);
}
<|endoftext|>