<commit_before>describe('ax5.util.getType TEST', function() {
    var testCases = [
        {
            args: [ 1 ],
            expect: 'number'
        },
        {
            args: [ '1' ],
            expect: 'string'
        },
        {
            args: [ [0, 1, 2] ],
            expect: 'array'
        },
        {
            args: [ {a: 1} ],
            expect: 'object'
        },
        {
            args: [ function(){} ],
            expect: 'function'
        },
        {
            args: [ document.querySelectorAll("div") ],
            expect: 'nodelist'
        },
        {
            args: [ document.querySelector("body") ],
            expect: 'element'
        },
        {
            args: [ document.createDocumentFragment() ],
            expect: 'fragment'
        },
        {
            args: [ null ],
            expect: 'null'
        },
        {
            args: [ undefined ],
            expect: 'undefined'
        },
        {
            args: [ window ],
            expect: 'window'
        }
    ];

    testCases.forEach(function(testCase){
        it('ax5.util.getType(' + testCase.expect + ') expect ' + testCase.expect, function() {
            var actual = ax5.util.getType.apply(this, testCase.args);

            should.equal(actual, testCase.expect);
        });
    });

});

describe('ax5.util.is{Type}', function() {
    var getTypes = function(typeNames, isExclude) {
        var types = [
            { name: 'Window', value: window },
            { name: 'DOMElement', value: document.createElement('div') },
            { name: 'Object', value: {} },
            { name: 'Array', value: [] },
            { name: 'Function', value: new Function() },
            { name: 'String', value: 'ax5ui' },
            { name: 'Number', value: 5 },
            { name: 'DOMNodelist', value: document.querySelectorAll('.content') },
            { name: 'undefined', value: undefined },
            { name: 'Null', value: null },
            { name: 'EmptyString', value: '' },
            { name: 'Date', value: new Date() }
        ];

        var matchedTypes = _.map(typeNames, function(typeName){
            return _.find(types, { 'name': typeName });
        });

        if (isExclude === true) {
            return _.reject(types, function(type){
                return _.findIndex(matchedTypes, { 'name': type.name }) > -1;
            });
        } else {
            return matchedTypes;
        }
    }

    var getTestCases = function(typeNames){
        var testCases = [];
        var trueExpectedTypes = getTypes(typeNames);
        var falseExpectedTypes = getTypes(typeNames, true);

        _.each(trueExpectedTypes, function(type){
            testCases.push({
                args: [ type ],
                expect: true
            });
        });

        _.each(falseExpectedTypes, function(type){
            testCases.push({
                args: [ type ],
                expect: false
            });
        });

        return testCases;
    }

    var testTargets = [
        {
            testMethod: 'isWindow',
            testCases: getTestCases(['Window'])
        },
        {
            testMethod: 'isElement',
            testCases: getTestCases(['DOMElement'])
        },
        {
            testMethod: 'isObject',
            testCases: getTestCases(['Object'])
        },
        {
            testMethod: 'isArray',
            testCases: getTestCases(['Array'])
        },
        {
            testMethod: 'isFunction',
            testCases: getTestCases(['Function'])
        },
        {
            testMethod: 'isString',
            testCases: getTestCases(['String', 'EmptyString'])
        },
        {
            testMethod: 'isNumber',
            testCases: getTestCases(['Number'])
        },
        {
            testMethod: 'isNodelist',
            testCases: getTestCases(['DOMNodelist'])
        },
        {
            testMethod: 'isUndefined',
            testCases: getTestCases(['undefined'])
        },
        {
            testMethod: 'isNothing',
            testCases: getTestCases(['undefined', 'Null', 'EmptyString'])
        },
        {
            testMethod: 'isDate',
            testCases: getTestCases(['Date'])
        }
    ];

    _.each(testTargets, function(testTarget){
        describe('ax5.util.' + testTarget.testMethod + ' TEST', function() {
            _.each(testTarget.testCases, function(testCase){
                _.each(testCase.args, function(args){
                    it('ax5.util.' + testTarget.testMethod + '(' + args.name + ') expect ' + testCase.expect, function() {
                        var actual = ax5.util[testTarget.testMethod].call(this, args.value);

                        should.equal(actual, testCase.expect);
                    });
                });
            });
        });
    });
});

describe('ax5.util.isDateFormat TEST', function() {
    var testCases = [
        {
            args: [ '20160101' ],
            expect: true
        },
        {
            args: [ '2016-01-01' ],
            expect: true
        },
        {
            args: [ '2016/01/01' ],
            expect: true
        },
        {
            args: [ '2016*01*01' ],
            expect: true
        },
        {
            args: [ '01/01/2016' ],
            expect: true
        },
        {
            args: [ '2016010' ],
            expect: false
        },
        {
            args: [ '201601011200' ],
            expect: true
        },
        {
            args: [ '2016-01-01T12:00' ],
            expect: true
        },
        {
            args: [ '2016-01-01T12:00+09:00' ],
            expect: true
        },
        {
            args: [ '' ],
            expect: false
        },
        {
            args: [ false ],
            expect: false
        },
        {
            args: [ null ],
            expect: false
        },
        {
            args: [ undefined ],
            expect: false
        }
    ];

    testCases.forEach(function(testCase){
        it('ax5.util.isDateFormat(' + JSON.stringify(testCase.args[0]) + ') expect ' + testCase.expect, function() {
            var actual = ax5.util.isDateFormat.apply(this, testCase.args);

            actual.should.equal(testCase.expect);
        });
    });

});
<commit_msg>update test code<commit_after>describe('ax5.util.getType TEST', function() {
    var testCases = [
        {
            args: [ 1 ],
            expect: 'number'
        },
        {
            args: [ '1' ],
            expect: 'string'
        },
        {
            args: [ [0, 1, 2] ],
            expect: 'array'
        },
        {
            args: [ {a: 1} ],
            expect: 'object'
        },
        {
            args: [ function(){} ],
            expect: 'function'
        },
        {
            args: [ document.querySelectorAll("div") ],
            expect: 'nodelist'
        },
        {
            args: [ document.querySelector("body") ],
            expect: 'element'
        },
        {
            args: [ document.createDocumentFragment() ],
            expect: 'fragment'
        },
        {
            args: [ null ],
            expect: 'null'
        },
        {
            args: [ undefined ],
            expect: 'undefined'
        },
        {
            args: [ window ],
            expect: 'window'
        },
        {
            args: [ jQuery ],
            expect: 'function'
        }
    ];

    testCases.forEach(function(testCase){
        it('ax5.util.getType(' + testCase.expect + ') expect ' + testCase.expect, function() {
            var actual = ax5.util.getType.apply(this, testCase.args);

            should.equal(actual, testCase.expect);
        });
    });

});

describe('ax5.util.is{Type}', function() {
    var getTypes = function(typeNames, isExclude) {
        var types = [
            { name: 'Window', value: window },
            { name: 'DOMElement', value: document.createElement('div') },
            { name: 'Object', value: {} },
            { name: 'Array', value: [] },
            { name: 'Function', value: new Function() },
            { name: 'String', value: 'ax5ui' },
            { name: 'Number', value: 5 },
            { name: 'DOMNodelist', value: document.querySelectorAll('.content') },
            { name: 'undefined', value: undefined },
            { name: 'Null', value: null },
            { name: 'EmptyString', value: '' },
            { name: 'Date', value: new Date() }
        ];

        var matchedTypes = _.map(typeNames, function(typeName){
            return _.find(types, { 'name': typeName });
        });

        if (isExclude === true) {
            return _.reject(types, function(type){
                return _.findIndex(matchedTypes, { 'name': type.name }) > -1;
            });
        } else {
            return matchedTypes;
        }
    }

    var getTestCases = function(typeNames){
        var testCases = [];
        var trueExpectedTypes = getTypes(typeNames);
        var falseExpectedTypes = getTypes(typeNames, true);

        _.each(trueExpectedTypes, function(type){
            testCases.push({
                args: [ type ],
                expect: true
            });
        });

        _.each(falseExpectedTypes, function(type){
            testCases.push({
                args: [ type ],
                expect: false
            });
        });

        return testCases;
    }

    var testTargets = [
        {
            testMethod: 'isWindow',
            testCases: getTestCases(['Window'])
        },
        {
            testMethod: 'isElement',
            testCases: getTestCases(['DOMElement'])
        },
        {
            testMethod: 'isObject',
            testCases: getTestCases(['Object'])
        },
        {
            testMethod: 'isArray',
            testCases: getTestCases(['Array'])
        },
        {
            testMethod: 'isFunction',
            testCases: getTestCases(['Function'])
        },
        {
            testMethod: 'isString',
            testCases: getTestCases(['String', 'EmptyString'])
        },
        {
            testMethod: 'isNumber',
            testCases: getTestCases(['Number'])
        },
        {
            testMethod: 'isNodelist',
            testCases: getTestCases(['DOMNodelist'])
        },
        {
            testMethod: 'isUndefined',
            testCases: getTestCases(['undefined'])
        },
        {
            testMethod: 'isNothing',
            testCases: getTestCases(['undefined', 'Null', 'EmptyString'])
        },
        {
            testMethod: 'isDate',
            testCases: getTestCases(['Date'])
        }
    ];

    _.each(testTargets, function(testTarget){
        describe('ax5.util.' + testTarget.testMethod + ' TEST', function() {
            _.each(testTarget.testCases, function(testCase){
                _.each(testCase.args, function(args){
                    it('ax5.util.' + testTarget.testMethod + '(' + args.name + ') expect ' + testCase.expect, function() {
                        var actual = ax5.util[testTarget.testMethod].call(this, args.value);

                        should.equal(actual, testCase.expect);
                    });
                });
            });
        });
    });
});

describe('ax5.util.isDateFormat TEST', function() {
    var testCases = [
        {
            args: [ '20160101' ],
            expect: true
        },
        {
            args: [ '2016-01-01' ],
            expect: true
        },
        {
            args: [ '2016/01/01' ],
            expect: true
        },
        {
            args: [ '2016*01*01' ],
            expect: true
        },
        {
            args: [ '01/01/2016' ],
            expect: true
        },
        {
            args: [ '2016010' ],
            expect: false
        },
        {
            args: [ '201601011200' ],
            expect: true
        },
        {
            args: [ '2016-01-01T12:00' ],
            expect: true
        },
        {
            args: [ '2016-01-01T12:00+09:00' ],
            expect: true
        },
        {
            args: [ '' ],
            expect: false
        },
        {
            args: [ false ],
            expect: false
        },
        {
            args: [ null ],
            expect: false
        },
        {
            args: [ undefined ],
            expect: false
        }
    ];

    testCases.forEach(function(testCase){
        it('ax5.util.isDateFormat(' + JSON.stringify(testCase.args[0]) + ') expect ' + testCase.expect, function() {
            var actual = ax5.util.isDateFormat.apply(this, testCase.args);

            actual.should.equal(testCase.expect);
        });
    });

});
<|endoftext|>


<commit_before>define('app/controllers/rules', ['app/models/rule', 'ember'],
    //
    //  Rules Controller
    //
    //  @returns Class
    //
    function (Rule) {

        'use strict';

        return Ember.ArrayController.extend(Ember.Evented, {

            //
            //
            //  Properties
            //
            //

            content: [],
            creationPending: false,

            aggregateList: [{
                'title': 'any',
                'value': 'any'
            }, {
                'title': 'every',
                'value': 'all'
            }, {
                'title': 'average',
                'value': 'avg'
            }],

            operatorList: [{
                'title': 'gt',
                'symbol': '>'
            }, {
                'title': 'lt',
                'symbol': '<'
            }],

            actionList: [
                'alert',
                'reboot',
                'destroy',
                'command'
            ],

            //
            //
            //  Initialization
            //
            //

            load: function(rules) {
                this._updateContent(rules);
            },

            //
            //
            //  Methods
            //
            //

            newRule: function (machine, callback) {

                var that = this;
                this.set('creationPending', true);
                Mist.ajax.POST('/rules', {
                    'backendId': machine.backend.id,
                    'machineId': machine.id,
                    'metric': 'load.shortterm',
                    'operator': 'gt',
                    'value': 5,
                    'action': 'alert'
                }).success(function (rule) {
                    that._addRule(rule);
                }).error(function (message) {
                    Mist.notificationController.notify(
                        'Error while creating rule: ' + message);
                }).complete(function (success, data) {
                    that.set('creationPending', false);
                    if (callback) callback(success, data);
                });
            },

            deleteRule: function (rule) {
                var that = this;
                rule.set('pendingAction', true);
                Mist.ajax.DELETE('/rules/' + rule.id, {
                }).success(function(){
                    that._deleteRule(rule);
                }).error(function(message) {
                    Mist.notificationController.notify(
                        'Error while deleting rule: ' + message);
                    rule.set('pendingAction', false);
                });
            },

            editRule: function (args) {

                var payload = {
                    id: args.rule.id
                };

                // Construct payload
                forIn(args.properties, function (value, property) {
                    payload[property] = value;
                });

                args.rule.set('pendingAction', true);
                Mist.ajax.POST('/rules',
                    payload
                ).error(function(message) {
                    Mist.notificationController.notify(
                        'Error while updating rule: ' + message);
                }).complete(function (success, data) {
                    args.rule.set('pendingAction', false);
                    if (args.callback) args.callback(success, data);
                });
            },

            ruleExists: function (ruleId) {
                return !!this.getRuleById(ruleId);
            },

            getRuleById: function(ruleId) {
                return this.content.findBy('id', ruleId);
            },

            getOperatorByTitle: function(ruleTitle) {
                return this.operatorList.findBy('title', ruleTitle);
            },

            getAggregateByValue: function (aggregateValue) {
                return this.aggregateList.findBy('value', aggregateValue);
            },

            //
            //
            //  Pseudo-Private Methods
            //
            //

            _updateContent: function (rules) {
                Ember.run(this, function() {

                    // Remove deleted rules
                    this.content.forEach(function (rule) {
                        if (!rules[rule.id])
                            this._deleteRule(rule);
                    }, this);

                    forIn(this, rules, function (rule, ruleId) {

                        rule.id = ruleId;

                        var oldRule = this.getRuleById(ruleId);

                        if (oldRule)
                            this._updateRule(oldRule, rule);
                        else
                            // Add new rules
                            this._addRule(rule);
                    });

                    this.trigger('onRuleListChange');
                });
            },

            _addRule: function (rule) {
                Ember.run(this, function () {
                    var newRule = Rule.create(rule);
                    if (this.ruleExists(rule.id)) return;
                    this.content.addObject(newRule);
                    this.trigger('onRuleAdd', {
                        rule: newRule
                    });
                });
            },

            _updateRule: function (rule, data) {
                Ember.run(this, function () {
                    rule.updateFromRawData(data);
                    this.trigger('onRuleUpdate', {
                        rule: rule,
                    });
                });
            },

            _deleteRule: function (rule) {
                Ember.run(this, function () {
                    this.content.removeObject(rule);
                    this.trigger('onRuleDelete', {
                        rule: rule
                    });
                });
            },

            //
            //
            //  Observers
            //
            //

            creationPendingObserver: function() {
                if (this.creationPending)
                    $('#add-rule-button').addClass('ui-state-disabled');
                else
                    $('#add-rule-button').removeClass('ui-state-disabled');
            }.observes('creationPending'),

        });
    }
);
<commit_msg>Update rule on ajax success<commit_after>define('app/controllers/rules', ['app/models/rule', 'ember'],
    //
    //  Rules Controller
    //
    //  @returns Class
    //
    function (Rule) {

        'use strict';

        return Ember.ArrayController.extend(Ember.Evented, {

            //
            //
            //  Properties
            //
            //

            content: [],
            creationPending: false,

            aggregateList: [{
                'title': 'any',
                'value': 'any'
            }, {
                'title': 'every',
                'value': 'all'
            }, {
                'title': 'average',
                'value': 'avg'
            }],

            operatorList: [{
                'title': 'gt',
                'symbol': '>'
            }, {
                'title': 'lt',
                'symbol': '<'
            }],

            actionList: [
                'alert',
                'reboot',
                'destroy',
                'command'
            ],

            //
            //
            //  Initialization
            //
            //

            load: function(rules) {
                this._updateContent(rules);
            },

            //
            //
            //  Methods
            //
            //

            newRule: function (machine, callback) {

                var that = this;
                this.set('creationPending', true);
                Mist.ajax.POST('/rules', {
                    'backendId': machine.backend.id,
                    'machineId': machine.id,
                    'metric': 'load.shortterm',
                    'operator': 'gt',
                    'value': 5,
                    'action': 'alert'
                }).success(function (rule) {
                    that._addRule(rule);
                }).error(function (message) {
                    Mist.notificationController.notify(
                        'Error while creating rule: ' + message);
                }).complete(function (success, data) {
                    that.set('creationPending', false);
                    if (callback) callback(success, data);
                });
            },

            deleteRule: function (rule) {
                var that = this;
                rule.set('pendingAction', true);
                Mist.ajax.DELETE('/rules/' + rule.id, {
                }).success(function(){
                    that._deleteRule(rule);
                }).error(function(message) {
                    Mist.notificationController.notify(
                        'Error while deleting rule: ' + message);
                    rule.set('pendingAction', false);
                });
            },

            editRule: function (args) {

                var payload = {
                    id: args.rule.id
                };

                // Construct payload
                forIn(args.properties, function (value, property) {
                    payload[property] = value;
                });

                var that = this;
                args.rule.set('pendingAction', true);
                Mist.ajax.POST('/rules',
                    payload
                ).success(function (data) {
                    that._updateRule(args.rule, data);
                }).error(function(message) {
                    Mist.notificationController.notify(
                        'Error while updating rule: ' + message);
                }).complete(function (success, data) {
                    args.rule.set('pendingAction', false);
                    if (args.callback) args.callback(success, data);
                });
            },

            ruleExists: function (ruleId) {
                return !!this.getRuleById(ruleId);
            },

            getRuleById: function(ruleId) {
                return this.content.findBy('id', ruleId);
            },

            getOperatorByTitle: function(ruleTitle) {
                return this.operatorList.findBy('title', ruleTitle);
            },

            getAggregateByValue: function (aggregateValue) {
                return this.aggregateList.findBy('value', aggregateValue);
            },

            //
            //
            //  Pseudo-Private Methods
            //
            //

            _updateContent: function (rules) {
                Ember.run(this, function() {

                    // Remove deleted rules
                    this.content.forEach(function (rule) {
                        if (!rules[rule.id])
                            this._deleteRule(rule);
                    }, this);

                    forIn(this, rules, function (rule, ruleId) {

                        rule.id = ruleId;

                        var oldRule = this.getRuleById(ruleId);

                        if (oldRule)
                            this._updateRule(oldRule, rule);
                        else
                            // Add new rules
                            this._addRule(rule);
                    });

                    this.trigger('onRuleListChange');
                });
            },

            _addRule: function (rule) {
                Ember.run(this, function () {
                    var newRule = Rule.create(rule);
                    if (this.ruleExists(rule.id)) return;
                    this.content.addObject(newRule);
                    this.trigger('onRuleAdd', {
                        rule: newRule
                    });
                });
            },

            _updateRule: function (rule, data) {
                Ember.run(this, function () {
                    rule.updateFromRawData(data);
                    this.trigger('onRuleUpdate', {
                        rule: rule,
                    });
                });
            },

            _deleteRule: function (rule) {
                Ember.run(this, function () {
                    this.content.removeObject(rule);
                    this.trigger('onRuleDelete', {
                        rule: rule
                    });
                });
            },

            //
            //
            //  Observers
            //
            //

            creationPendingObserver: function() {
                if (this.creationPending)
                    $('#add-rule-button').addClass('ui-state-disabled');
                else
                    $('#add-rule-button').removeClass('ui-state-disabled');
            }.observes('creationPending'),

        });
    }
);
<|endoftext|>


<commit_before>export const silvercauldron = [
    {
        "item": "Lesser Potions Box",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 6,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 15,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 35,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    },
    {
        "item": "Better Potions Box",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 5,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 10,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 25,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    },
    {
        "item": "Defensive Pellets Box",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 4,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 9,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 20,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    },
    {
        "item": "Ofensive Pellets Box",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 4,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 9,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 20,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    },
    {
        "item": "Turtle Stone",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "1d",
                "cost": 300000,
                "output": 1,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 70},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 70},
                    {"name": "Elysian Orb", "quantity": 15}

                ]
            },
            {
                "duration": "3d",
                "cost": 300000,
                "output": 2,
                "ingredients": [
                    {"name": "Common Root", "quantity": 45},
                    {"name": "Advanced Root", "quantity": 55},
                    {"name": "Rare Material", "quantity": 30},
                    {"name": "Soulstone", "quantity": 120},
                    {"name": "Moonstone", "quantity": 25},
                    {"name": "Sacred Orb", "quantity": 120},
                    {"name": "Elysian Orb", "quantity": 25}
                ]
            },
            {
                "duration": "5d",
                "cost": 600000,
                "output": 5,
                "ingredients": [
                    {"name": "Common Root", "quantity": 70},
                    {"name": "Advanced Root", "quantity": 95},
                    {"name": "Rare Material", "quantity": 50},
                    {"name": "Soulstone", "quantity": 250},
                    {"name": "Moonstone", "quantity": 50},
                    {"name": "Sacred Orb", "quantity": 250},
                    {"name": "Elysian Orb", "quantity": 50}
                ]
            }
        ]
    }

];<commit_msg>Update silvercauldronCrafting.js<commit_after>export const silvercauldron = [
    {
        "item": "Cactus Potion Chest",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 6,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 15,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 35,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    },
    {
        "item": "Prickly Potion Chest",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 5,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 10,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 25,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    },
    {
        "item": "Persistence Potion Chest",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 4,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 9,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 20,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    },
    {
        "item": "Awakened Potion Chest",
        "createdBy": "Silver Cauldron",
        "orders": [
            {
                "duration": "6h",
                "cost": 150000,
                "output": 4,
                "ingredients": [
                    {"name": "Common Root", "quantity": 4},
                    {"name": "Advanced Root", "quantity": 5},
                    {"name": "Rare Material", "quantity": 3},
                    {"name": "Soulstone", "quantity": 30},
                    {"name": "Moonstone", "quantity": 7},
                    {"name": "Sacred Orb", "quantity": 30},
                    {"name": "Elysian Orb", "quantity": 7}

                ]
            },
            {
                "duration": "12h",
                "cost": 300000,
                "output": 9,
                "ingredients": [
                    {"name": "Common Root", "quantity": 8},
                    {"name": "Advanced Root", "quantity": 10},
                    {"name": "Rare Material", "quantity": 5},
                    {"name": "Soulstone", "quantity": 55},
                    {"name": "Moonstone", "quantity": 15},
                    {"name": "Sacred Orb", "quantity": 55},
                    {"name": "Elysian Orb", "quantity": 15}
                ]
            },
            {
                "duration": "1d",
                "cost": 600000,
                "output": 20,
                "ingredients": [
                    {"name": "Common Root", "quantity": 15},
                    {"name": "Advanced Root", "quantity": 20},
                    {"name": "Rare Material", "quantity": 10},
                    {"name": "Soulstone", "quantity": 110},
                    {"name": "Moonstone", "quantity": 30},
                    {"name": "Sacred Orb", "quantity": 110},
                    {"name": "Elysian Orb", "quantity": 30}
                ]
            }
        ]
    }
];
<|endoftext|>


<commit_before>/**
 * ISARI Import Scripts File Helpers
 * ==================================
 *
 * Miscellaneous helpers used by the import scripts such as foreign key
 * finders.
 */
const chalk = require('chalk'),
      _ = require('lodash');

// Simple partition by helper
exports.partitionBy = function(collection, predicate) {
  return _.values(_.groupBy(collection, predicate));
};

// Function normalizing a single name
function normalizeName(name) {

  // TODO: for a better version drop more punct
  return _.deburr(name)
    .toUpperCase()
    .replace(/[A-Z]\./g, '')
    .replace(/(?:^|\s)(\w)\s+(\w)/gi, '$1$2')
    .trim()
    .replace(/[-\s]+/g, ' ')
    .replace(/[’‘`‛']/g, '')
    .replace(/[^\w\d\s]/g, '');
}
exports.normalizeName = normalizeName;

// Function used to hash people
exports.hashPeople = function(p) {
  const name = normalizeName(p.name),
        firstName = normalizeName(p.firstName);

  return `${name}§${firstName}`;
};

exports.hashPeopleWithYear = function(p) {
  const name = normalizeName(p.name),
        firstName = normalizeName(p.firstName);

  if (p.birthDate)
    return `${name}§${firstName}§${p.birthDate.slice(0, 4)}`;
  else
    return `${name}§${firstName}`;
};

// Function checking overlap of two potentially non-ending periods.
exports.overlap = function(A, B) {
  if (!A.endDate && !B.endDate)
    return true;
  if (A.endDate > B.startDate)
    return true;
  if (A.startDate < B.startDate && A.endDate > B.endDate)
    return true;
  return false;
};

// Recursive function handling each schema level
function parseSchemaLevel(relations, level, path) {
  path = path || [];

  for (const k in level) {
    const prop = level[k];

    if (!Array.isArray(prop)) {
      if (prop.ref)
        relations.push(path.concat({plural: false, ref: prop.ref, key: k}));
      continue;
    }

    if (prop[0].ref) {
      relations.push(path.concat({plural: true, ref: prop[0].ref, key: k}));
    }
    else {
      parseSchemaLevel(relations, prop[0], path.concat({key: k}));
    }
  }
}

// Function used to describe the relations from the Mongoose schema
exports.findRelations = function(schema) {
  const tree = schema.tree,
        relations = [];

  parseSchemaLevel(relations, tree);

  return relations;
};

// Function used to access the desired path in an item
function recurseIntoItem(path, item, callback) {

  // Leaf
  if (path.length === 1) {
    const {plural, key, ref} = path[0];

    if (!item[key])
      return;

    // Singular
    if (!plural) {
      item[key] = callback(item[key], ref);
    }

    // Plural
    else {
      for (let i = 0, l = item[key].length; i < l; i++) {
        item[key][i] = callback(item[key][i], ref);
      }
    }
  }

  // Not leaf
  else {
    item = item[path[0].key];
    path = path.slice(1);

    if (!item)
      return;

    for (let i = 0, l = item.length; i < l; i++) {
      recurseIntoItem(path, item[i], callback);
    }
  }
}

// Function used to iterate over the described relations of an item
exports.processRelations = function(relations, item, callback) {

  // Iterating over relations
  for (let i = 0, l = relations.length; i < l; i++) {
    const path = relations[i];

    recurseIntoItem(path, item, callback);
  }
};

// Function used to recursively collect schema validation errors
const collectErrors = function(error, index, path = [], acc = []) {

  // Leaf?
  if (!error.reason && !error.errors && error.kind !== 'ObjectId') {
    const meta = {
      line: index + 1,
      type: error.name,
      message: error.message || '',
      path
    };

    const coloredMessage = meta.message
      .replace(/`(.*?)`/g, function($, m) {
        return chalk.cyan(m);
      })
      .replace(/".*?"/g, function(m) {
        return chalk.green(m);
      });

    meta.formattedMessage = `Line ${meta.line}: ${meta.type} => ${coloredMessage}`;

    acc.push(meta);
  }

  // Recurse
  else {
    if (error.reason) {
      collectErrors(error.reason, index, path.concat(error.path), acc);
    }
    else if (error.errors) {
      for (const k in error.errors) {
        collectErrors(error.errors[k], index, path.concat(error.errors[k].path), acc);
      }
    }
  }

  return acc;
};

exports.collectErrors = collectErrors;
<commit_msg>Fuzzier name hash<commit_after>/**
 * ISARI Import Scripts File Helpers
 * ==================================
 *
 * Miscellaneous helpers used by the import scripts such as foreign key
 * finders.
 */
const chalk = require('chalk'),
      _ = require('lodash');

// Simple partition by helper
exports.partitionBy = function(collection, predicate) {
  return _.values(_.groupBy(collection, predicate));
};

// Function normalizing a single name
function normalizeName(name) {

  // TODO: for a better version drop more punct
  return _.deburr(name)
    .toUpperCase()
    .replace(/[A-Z]\./g, '')
    .replace(/(?:^|\s)(\w)\s+(\w)/gi, '$1$2')
    .trim()
    .replace(/[-\s]+/g, ' ')
    .replace(/[’‘`‛']/g, '')
    .replace(/[()]/g, '')
    .split(/\s/g)
    .sort()
    .join(' ');
}
exports.normalizeName = normalizeName;

// Function used to hash people
exports.hashPeople = function(p) {
  const name = normalizeName(p.name),
        firstName = normalizeName(p.firstName);

  return `${name}§${firstName}`;
};

exports.hashPeopleWithYear = function(p) {
  const name = normalizeName(p.name),
        firstName = normalizeName(p.firstName);

  if (p.birthDate)
    return `${name}§${firstName}§${p.birthDate.slice(0, 4)}`;
  else
    return `${name}§${firstName}`;
};

// Function checking overlap of two potentially non-ending periods.
exports.overlap = function(A, B) {
  if (!A.endDate && !B.endDate)
    return true;
  if (A.endDate > B.startDate)
    return true;
  if (A.startDate < B.startDate && A.endDate > B.endDate)
    return true;
  return false;
};

// Recursive function handling each schema level
function parseSchemaLevel(relations, level, path) {
  path = path || [];

  for (const k in level) {
    const prop = level[k];

    if (!Array.isArray(prop)) {
      if (prop.ref)
        relations.push(path.concat({plural: false, ref: prop.ref, key: k}));
      continue;
    }

    if (prop[0].ref) {
      relations.push(path.concat({plural: true, ref: prop[0].ref, key: k}));
    }
    else {
      parseSchemaLevel(relations, prop[0], path.concat({key: k}));
    }
  }
}

// Function used to describe the relations from the Mongoose schema
exports.findRelations = function(schema) {
  const tree = schema.tree,
        relations = [];

  parseSchemaLevel(relations, tree);

  return relations;
};

// Function used to access the desired path in an item
function recurseIntoItem(path, item, callback) {

  // Leaf
  if (path.length === 1) {
    const {plural, key, ref} = path[0];

    if (!item[key])
      return;

    // Singular
    if (!plural) {
      item[key] = callback(item[key], ref);
    }

    // Plural
    else {
      for (let i = 0, l = item[key].length; i < l; i++) {
        item[key][i] = callback(item[key][i], ref);
      }
    }
  }

  // Not leaf
  else {
    item = item[path[0].key];
    path = path.slice(1);

    if (!item)
      return;

    for (let i = 0, l = item.length; i < l; i++) {
      recurseIntoItem(path, item[i], callback);
    }
  }
}

// Function used to iterate over the described relations of an item
exports.processRelations = function(relations, item, callback) {

  // Iterating over relations
  for (let i = 0, l = relations.length; i < l; i++) {
    const path = relations[i];

    recurseIntoItem(path, item, callback);
  }
};

// Function used to recursively collect schema validation errors
const collectErrors = function(error, index, path = [], acc = []) {

  // Leaf?
  if (!error.reason && !error.errors && error.kind !== 'ObjectId') {
    const meta = {
      line: index + 1,
      type: error.name,
      message: error.message || '',
      path
    };

    const coloredMessage = meta.message
      .replace(/`(.*?)`/g, function($, m) {
        return chalk.cyan(m);
      })
      .replace(/".*?"/g, function(m) {
        return chalk.green(m);
      });

    meta.formattedMessage = `Line ${meta.line}: ${meta.type} => ${coloredMessage}`;

    acc.push(meta);
  }

  // Recurse
  else {
    if (error.reason) {
      collectErrors(error.reason, index, path.concat(error.path), acc);
    }
    else if (error.errors) {
      for (const k in error.errors) {
        collectErrors(error.errors[k], index, path.concat(error.errors[k].path), acc);
      }
    }
  }

  return acc;
};

exports.collectErrors = collectErrors;
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Add action when user clicks button for image uploading.
$('#upload-photo-button').click(function() {
  fetchBlobstoreUrlAndShowForm();
});

/**
 * Stores the URL, generated by the Blobstore API, which allows the user to upload a file,
 * namely {@code imageUploadUrl}, and displays the form content.
 */
let imageUploadUrl;
async function fetchBlobstoreUrlAndShowForm() {
  const response = await fetch('/blobstore-upload-url');
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  imageUploadUrl = await response.text();
  $('#upload-image-form').removeClass('hidden');
}

/**
 * Makes a POST request to {@code imageUploadUrl}, gets the shopping results in JSON format,
 * into {@code products}, and calls the method for integrating those results into the main
 * web page.
 */
async function onSubmitUploadImageForm() {
  // Get the data introduced by the user in the form.
  const photoCategory = document.getElementById('photo-category').value;
  const selectedFile = document.getElementById('input-photo').files[0];

  // Construct the FormData object.
  const formData = new FormData();
  formData.append('photo-category', photoCategory);
  formData.append('photo', selectedFile);

  // Before making the POST request, empty or hide containers from previous photo requests.
  $('#shopping-query-display').empty();
  if (!$('#shopping-query-display-container').hasClass('hidden')) {
    $('#shopping-query-display-container').addClass('hidden');
  }
  $('#shopping-results-wrapper').empty();
  $('#shopping-query').empty();

  // Stop displaying the logo.
  $('#logo').addClass('hidden');

  // Close the form modal and display a prompt, alerting the user that the results are loading.
  $('#upload-image-modal').modal('hide');
  $('#search-loading-prompt').text('Shopping results loading, please wait!');
  $('#loading-gif-prompt').removeClass('hidden');

  // Make a POST request to {@code imageUploadUrl} to process the image uploading.
  const response = await fetch(imageUploadUrl, {
    method: 'POST',
    body: formData,
  }).catch((error) => {
    console.warn(error);
    return new Response(JSON.stringify({
      code: error.response.status,
      message: `Failed to fetch ${imageUploadUrl}`,
    }));
  });

  if (!response.ok) {
    return Promise.reject(response);
  }

  // The request returns a JSON object with the all the shopping queries used to search on Google Shopping and
  // the data about each product for each query from the Google Shopping results page.
  const shoppingResults = await response.json();

  // Empty the prompt container and add the query and its resultant content into the page.
  $('#search-loading-prompt').empty();
  $('#loading-gif-prompt').addClass('hidden');

  $('#shopping-query-display-container').removeClass('hidden');
  // Add a new line before displaying the queries.
  $('#shopping-query').append('
');

  appendShoppingResults(shoppingResults);
}

/**
 * Integrates product results from Google Shopping and their respective queries into the web page.
 */
async function appendShoppingResults(shoppingResults) {
  shoppingResults.forEach((result) => {
    // Show the user the shopping query built to search on Google Shopping.
    $('#shopping-query').append(result['query'] + '
');
    const $query = `<div class='text-center query'>${result['query']}</div>`;
    $('#shopping-results-wrapper').append($query);

    const $listProductsContainer = $('
', {class: 'row'});

    const $products = result['products'];
    // Integrate the products into the web page.
    $products.forEach((product) => {
      // Create an HTML node for the item container.
      const $productContainer = $('
', {class: 'col-md-4'});

      // Get the HTML content for the container.
      const productElementHTML = getProductElementHTML(product.title,
          product.imageLink,
          product.priceAndSeller,
          product.link,
          product.shippingPrice);

      // Load the content using jQuery's append.
      $productContainer.append(productElementHTML);

      // Add the container to the results page, into the corresponding product wrapper.
      $listProductsContainer.append($productContainer);
    });
    $('#shopping-results-wrapper').append($listProductsContainer);
  });
}

/**
 * Returns the HTML content for a product container, based on the arguments.
 */
function getProductElementHTML(productTitle,
    productImageLink,
    productPriceAndSeller,
    productLink,
    productShippingPrice) {
  return `

${productTitle}

${productPriceAndSeller}

                    View

                ${productShippingPrice}

`;
}
<commit_msg>Address reviews in script<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Add action when user clicks button for image uploading.
$('#upload-photo-button').click(function() {
  fetchBlobstoreUrlAndShowForm();
});

/**
 * Stores the URL, generated by the Blobstore API, which allows
 * the user to upload a file, namely {@code imageUploadUrl},
 * and displays the form content.
 */
let imageUploadUrl;
async function fetchBlobstoreUrlAndShowForm() {
  const response = await fetch('/blobstore-upload-url');
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  imageUploadUrl = await response.text();
  $('#upload-image-form').removeClass('hidden');
}

/**
 * Makes a POST request to {@code imageUploadUrl}, gets the
 * shopping results in JSON format, into {@code products},
 * and calls the method for integrating those results into the main
 * web page.
 */
async function onSubmitUploadImageForm() {
  // Get the data introduced by the user in the form.
  const photoCategory = document.getElementById('photo-category').value;
  const selectedFile = document.getElementById('input-photo').files[0];

  // Construct the FormData object.
  const formData = new FormData();
  formData.append('photo-category', photoCategory);
  formData.append('photo', selectedFile);

  // Before making the POST request, empty or hide containers from previous photo requests.
  $('#shopping-query-display').empty();
  if (!$('#shopping-query-display-container').hasClass('hidden')) {
    $('#shopping-query-display-container').addClass('hidden');
  }
  $('#shopping-results-wrapper').empty();
  $('#shopping-query').empty();

  // Stop displaying the logo.
  $('#logo').addClass('hidden');

  // Close the form modal and display a prompt, alerting the user that the results are loading.
  $('#upload-image-modal').modal('hide');
  $('#search-loading-prompt').text('Shopping results loading, please wait!');
  $('#loading-gif-prompt').removeClass('hidden');

  // Make a POST request to {@code imageUploadUrl} to process the image uploading.
  const response = await fetch(imageUploadUrl, {
    method: 'POST',
    body: formData,
  }).catch((error) => {
    console.warn(error);
    return new Response(JSON.stringify({
      code: error.response.status,
      message: `Failed to fetch ${imageUploadUrl}`,
    }));
  });

  if (!response.ok) {
    return Promise.reject(response);
  }

  // The request returns a JSON object with the all the shopping queries
  // used to search on Google Shopping and the data about each product
  // for each query from the Google Shopping results page.
  const shoppingResults = await response.json();

  // Empty the prompt container and add the query and its resultant content into the page.
  $('#search-loading-prompt').empty();
  $('#loading-gif-prompt').addClass('hidden');
  $('#shopping-query-display-container').removeClass('hidden');

  appendShoppingResults(shoppingResults);
}

/**
 * Integrates product results from Google Shopping and their respective queries into the web page.
 */
function appendShoppingResults(shoppingResults) {
  shoppingResults.forEach((result) => {
    // Show the user the shopping query built to search on Google Shopping.
    $('#shopping-query').append('
' + result['query']);
    const query = `<div class='text-center query'>${result['query']}</div>`;
    $('#shopping-results-wrapper').append(query);

    const listProductsContainer = $('
', {class: 'row'});

    const products = result['products'];
    // Integrate the products into the web page.
    products.forEach((product) => {
      // Create an HTML node for the item container.
      const productContainer = $('
', {class: 'col-md-4'});

      // Get the HTML content for the container.
      const productElementHTML = getProductElementHTML(product.title,
          product.imageLink,
          product.priceAndSeller,
          product.link,
          product.shippingPrice);

      // Load the content using jQuery's append.
      productContainer.append(productElementHTML);

      // Add the container to the results page, into the corresponding product wrapper.
      listProductsContainer.append(productContainer);
    });
    $('#shopping-results-wrapper').append(listProductsContainer);
  });
}

/**
 * Returns the HTML content for a product container, based on the arguments.
 */
function getProductElementHTML(productTitle,
    productImageLink,
    productPriceAndSeller,
    productLink,
    productShippingPrice) {
  return `

${productTitle}

${productPriceAndSeller}

                    View

                ${productShippingPrice}

`;
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Esa Search Extension
// @namespace    ese
// @version      0.2.0
// @description  Esa Search Extension makes advanced searching easy.
// @author       nalabjp
// @match        https://*.esa.io/*
// @require      https://cdn.rawgit.com/HubSpot/vex/v4.0.0/dist/js/vex.combined.min.js
// @resource     vexCSS https://cdn.rawgit.com/HubSpot/vex/v4.0.0/dist/css/vex.css
// @resource     vexTheme https://cdn.rawgit.com/HubSpot/vex/v4.0.0/dist/css/vex-theme-default.css
// @grant        GM_addStyle
// @grant        GM_getResourceText
// ==/UserScript==

(function() {
    'use strict';

    // Configuration for vex
    const vexCss = GM_getResourceText('vexCSS');
    const vexTheme = GM_getResourceText('vexTheme');
    GM_addStyle(vexCss);
    GM_addStyle(vexTheme);
    vex.defaultOptions.className = 'vex-theme-default';

    // functions
    function replace_to_half_space(str) {
        // em space to half space
        return str.replace(/　/g, ' ');
    }

    // Add ESE element after form element
    var form = $('form.navbar-form.navbar-sub__navbar-form');
    var ese = $('');
    form.after(ese);

    // vex dialog
    var dialog = [
        '',
        '
',
            '
',
                'キーワード ' +
                '<input name="keyword" type="text" placeholder="keyword" />',
            '
',
            '
',
                '記事名 ' +
                '<input name="title" type="text" placeholder="keyword" />',
            '
',
            '
',
                '記事本文 ' +
                '<input name="body" type="text" placeholder="keyword" />',
            '
',
            '
',
                '記事作成者 ' +
                '<input name="user" type="text" placeholder="screen_name" />',
            '
',
            '
',
                'Star
' +
                '<input value="true" name="stared" type="radio" id="stared_true" />true' +
                '<input value="false" name="stared" type="radio" id="stared_false" />false' +
                '<input value="none" name="stared" type="radio" id="stared_none" />none',
            '
',
            '
',
                'Watch
' +
                '<input value="true" name="watched" type="radio" id="watched_true" />true' +
                '<input value="false" name="watched" type="radio" id="watched_false" />false' +
                '<input value="none" name="watched" type="radio" id="watched_none" />none',
            '
',
            '
',
                'WIP
' +
                '<input value="true" name="wip" type="radio" id="wip_true" />true' +
                '<input value="false" name="wip" type="radio" id="wip_false" />false' +
                '<input value="none" name="wip" type="radio" id="wip_none" />none',
            '
',
            '
',
                '種類
' +
                '<input value="stock" name="kind" type="radio" id="kind_stock" />stock' +
                '<input value="flow" name="kind" type="radio" id="kind_flow" />flow' +
                '<input value="none" name="kind" type="radio" id="kind_both" />both',
            '
',
            '
',
                'カテゴリ(部分一致) ' +
                '<input name="category" type="text" placeholder="keyword" />',
            '
',
            '
',
                'カテゴリ(前方一致) ' +
                '<input name="in" type="text" placeholder="keyword" />',
            '
',
            '
',
                'カテゴリ(完全一致) ' +
                '<input name="on" type="text" placeholder="keyword" />',
            '
',
            '
',
                'タグ ' +
                '<input name="tag" type="text" placeholder="tag" />',
            '
',
            '
',
                'コメント ' +
                '<input name="comment" type="text" placeholder="keyword" />',
            '
',
            '
',
                'Star数 , <, >=, <=]が使えます）">' +
                '<input name="stars" type="text" placeholder="number" />',
            '
',
            '
',
                'Watch数 , <, >=, <=]が使えます）">' +
                '<input name="watcheds" type="text" placeholder="number" />',
            '
',
            '
',
                'コメント数 , <, >=, <=]が使えます）">' +
                '<input name="comments" type="text" placeholder="number" />',
            '
',
            '
',
                '作成日 , <, >=, <=]が使えます）">' +
                '<input name="created" type="text" placeholder="YYYY-MM-DD" />',
            '
',
            '
',
                '更新日 , <, >=, <=]が使えます）">' +
                '<input name="updated" type="text" placeholder="YYYY-MM-DD" />',
            '
',
            '
',
                '外部公開
' +
                '<input value="true" name="sharing" type="radio" id="sharing_true" />true' +
                '<input value="false" name="sharing" type="radio" id="sharing_false" />false' +
                '<input value="none" name="sharing" type="radio" id="sharing_none" />none',
            '
',
        '
'
    ].join(' ');

    // vex buttons
    var buttons = [
        $.extend({}, vex.dialog.buttons.YES, { className: 'btn btn-primary js-disable-on-uploading', text: 'Search' })
    ];

    // vex callback function
    var submitting = function (data) {
        if (!data) return console.log('Cancelled');

        var conditions = [];
        Object.keys(data).forEach(function(key) {
            var val = replace_to_half_space(this[key].trim());
            switch(key) {
                case 'wip':
                case 'kind':
                case 'stared':
                case 'watched':
                case 'sharing':
                    if (val === 'none') return;
                    conditions.push(key + ':' + val);
                    break;
                case 'keyword':
                    for(let w of val.split(' ').reverse()) {
                        conditions.unshift(w);
                    }
                    break;
                default:
                    for(let w of val.split(' ')) {
                        conditions.push(key + ':' + w);
                    }
            }
        }, data);
        console.log(conditions);

        $('#search_input').val(conditions.join(' '));
        form.submit();
    };

    // Register click event
    ese.click(function(){
        vex.dialog.open({
            message: 'Advanced Search',
            input: dialog,
            buttons: buttons,
            callback: submitting
        });

        // Enable after the element that configured Bootstrap tooltip defined
        $('[data-toggle="tooltip"]').tooltip();
    });
})();
<commit_msg>Assign input values to form<commit_after>// ==UserScript==
// @name         Esa Search Extension
// @namespace    ese
// @version      0.2.0
// @description  Esa Search Extension makes advanced searching easy.
// @author       nalabjp
// @match        https://*.esa.io/*
// @require      https://cdn.rawgit.com/HubSpot/vex/v4.0.0/dist/js/vex.combined.min.js
// @resource     vexCSS https://cdn.rawgit.com/HubSpot/vex/v4.0.0/dist/css/vex.css
// @resource     vexTheme https://cdn.rawgit.com/HubSpot/vex/v4.0.0/dist/css/vex-theme-default.css
// @grant        GM_addStyle
// @grant        GM_getResourceText
// ==/UserScript==

(function() {
    'use strict';

    // Configuration for vex
    const vexCss = GM_getResourceText('vexCSS');
    const vexTheme = GM_getResourceText('vexTheme');
    GM_addStyle(vexCss);
    GM_addStyle(vexTheme);
    vex.defaultOptions.className = 'vex-theme-default';

    // functions
    function replace_to_half_space(str) {
        // em space to half space
        return str.replace(/　/g, ' ');
    }

    function replace_to_only_colon(str) {
        return str.replace(/: +/g, ':');
    }

    // Add ESE element after form element
    var form = $('form.navbar-form.navbar-sub__navbar-form');
    var ese = $('');
    form.after(ese);

    // vex dialog
    var dialog = [
        '',
        '
',
            '
',
                'キーワード ' +
                '<input name="keyword" type="text" placeholder="keyword" />',
            '
',
            '
',
                '記事名 ' +
                '<input name="title" type="text" placeholder="keyword" />',
            '
',
            '
',
                '記事本文 ' +
                '<input name="body" type="text" placeholder="keyword" />',
            '
',
            '
',
                '記事作成者 ' +
                '<input name="user" type="text" placeholder="screen_name" />',
            '
',
            '
',
                'Star
' +
                '<input value="true" name="stared" type="radio" id="stared_true" />true' +
                '<input value="false" name="stared" type="radio" id="stared_false" />false' +
                '<input value="none" name="stared" type="radio" id="stared_none" />none',
            '
',
            '
',
                'Watch
' +
                '<input value="true" name="watched" type="radio" id="watched_true" />true' +
                '<input value="false" name="watched" type="radio" id="watched_false" />false' +
                '<input value="none" name="watched" type="radio" id="watched_none" />none',
            '
',
            '
',
                'WIP
' +
                '<input value="true" name="wip" type="radio" id="wip_true" />true' +
                '<input value="false" name="wip" type="radio" id="wip_false" />false' +
                '<input value="none" name="wip" type="radio" id="wip_none" />none',
            '
',
            '
',
                '種類
' +
                '<input value="stock" name="kind" type="radio" id="kind_stock" />stock' +
                '<input value="flow" name="kind" type="radio" id="kind_flow" />flow' +
                '<input value="none" name="kind" type="radio" id="kind_both" />both',
            '
',
            '
',
                'カテゴリ(部分一致) ' +
                '<input name="category" type="text" placeholder="keyword" />',
            '
',
            '
',
                'カテゴリ(前方一致) ' +
                '<input name="in" type="text" placeholder="keyword" />',
            '
',
            '
',
                'カテゴリ(完全一致) ' +
                '<input name="on" type="text" placeholder="keyword" />',
            '
',
            '
',
                'タグ ' +
                '<input name="tag" type="text" placeholder="tag" />',
            '
',
            '
',
                'コメント ' +
                '<input name="comment" type="text" placeholder="keyword" />',
            '
',
            '
',
                'Star数 , <, >=, <=]が使えます）">' +
                '<input name="stars" type="text" placeholder="number" />',
            '
',
            '
',
                'Watch数 , <, >=, <=]が使えます）">' +
                '<input name="watches" type="text" placeholder="number" />',
            '
',
            '
',
                'コメント数 , <, >=, <=]が使えます）">' +
                '<input name="comments" type="text" placeholder="number" />',
            '
',
            '
',
                '作成日 , <, >=, <=]が使えます）">' +
                '<input name="created" type="text" placeholder="YYYY-MM-DD" />',
            '
',
            '
',
                '更新日 , <, >=, <=]が使えます）">' +
                '<input name="updated" type="text" placeholder="YYYY-MM-DD" />',
            '
',
            '
',
                '外部公開
' +
                '<input value="true" name="sharing" type="radio" id="sharing_true" />true' +
                '<input value="false" name="sharing" type="radio" id="sharing_false" />false' +
                '<input value="none" name="sharing" type="radio" id="sharing_none" />none',
            '
',
        '
'
    ].join(' ');

    // vex buttons
    var buttons = [
        $.extend({}, vex.dialog.buttons.YES, { className: 'btn btn-primary js-disable-on-uploading', text: 'Search' })
    ];

    // vex callback function
    var submitting = function (data) {
        if (!data) return console.log('Cancelled');

        var conditions = [];
        Object.keys(data).forEach(function(key) {
            var val = replace_to_half_space(this[key].trim());
            switch(key) {
                case 'wip':
                case 'kind':
                case 'stared':
                case 'watched':
                case 'sharing':
                    if (val === 'none') return;
                    conditions.push(key + ':' + val);
                    break;
                case 'keyword':
                    for(let w of val.split(' ').reverse()) {
                        conditions.unshift(w);
                    }
                    break;
                default:
                    for(let w of val.split(' ')) {
                        conditions.push(key + ':' + w);
                    }
            }
        }, data);
        console.log(conditions);

        $('#search_input').val(conditions.join(' '));
        form.submit();
    };

    // Assign input values
    var assign_input_values = function() {
        let input_values = replace_to_only_colon(replace_to_half_space($('#search_input').val())).split(' ');
        let value_hash = {
            keyword: [],
            title: [],
            body: [],
            user: [],
            stared: '',
            watched: '',
            wip: '',
            kind: '',
            category: [],
            in: [],
            on: [],
            tag: [],
            comment: [],
            stars: [],
            watches: [],
            comments: [],
            created: [],
            updated: [],
            sharing: '',
        };

        for(let chunk of input_values) {
            if (chunk.includes(':')) {
                let kv = chunk.split(':', 2);
                switch(kv[0]) {
                    case 'wip':
                    case 'kind':
                    case 'stared':
                    case 'watched':
                    case 'sharing':
                        value_hash[kv[0]] = kv[1];
                        break;
                    default:
                        value_hash[kv[0]].push(kv[1]);
                }
            } else {
                value_hash.keyword.push(chunk);
            }
        }

        Object.keys(value_hash).forEach(function(key) {
            let val = this[key];
            switch(key) {
                case 'wip':
                case 'kind':
                case 'stared':
                case 'watched':
                case 'sharing':
                    if (val !== '') {
                        $('.vex-custom-container .vex-custom-block input[name="' + key + '"]#' + key + '_' + val).attr('checked', true);
                    }
                    break;
                default:
                    if (val.length > 0) {
                        $('.vex-custom-container .vex-custom-block input[name="' + key + '"]').focus();
                        $('.vex-custom-container .vex-custom-block input[name="' + key + '"]').val(val.join(' ')).change();
                    }
            }
        }, value_hash);

        // Initial focus
        $('.vex-custom-container .vex-custom-block input[name="keyword"]').focus();
    };

    // Register click event
    ese.click(function(){
        vex.dialog.open({
            message: 'Advanced Search',
            input: dialog,
            buttons: buttons,
            callback: submitting
        });

        // Enable after the element that configured Bootstrap tooltip defined
        $('[data-toggle="tooltip"]').tooltip();

        assign_input_values();
    });
})();
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Why No Bounty?
// @namespace   https://github.com/Glorfindel83/
// @description Adds information to a question indicating why you can't start a bounty.
// @author      Glorfindel
// @version     0.2
// @match       *://*.stackexchange.com/*
// @match       *://*.stackoverflow.com/*
// @match       *://*.superuser.com/*
// @match       *://*.serverfault.com/*
// @match       *://*.askubuntu.com/*
// @match       *://*.stackapps.com/*
// @match       *://*.mathoverflow.net/*
// @grant       none
// ==/UserScript==

(function () {
  "use strict";

  // Is this a Q&A page?
  var questionLink = $("#question-header a").attr("href");
  if (typeof(questionLink) === "undefined") {
    return;
  }

  // If there's a 'start a bounty' link, we have nothing to do.
  if ($("a.bounty-link.bounty").length !== 0) {
    return;
  }

  var addInformation = function (message) {
    $("#question > table > tbody").append("
"
      + "bounty not possible: " + message + "
");
  };

  // Meta site?
  if (window.location.host != "meta.stackexchange.com" && window.location.host.contains("meta")) {
    addInformation("meta sites don't have bounties");
    return;
  }

  // Is there an open bounty?
  if ($("div.question-status.bounty").length !== 0) {
  	addInformation("there is already an open bounty");
    return;
  }

  // Is the question closed / locked?
  var status = $("#question .question-status h2 b").text();
  if (status === "closed" || status === "put on hold" || status == "marked") {
    addInformation("the question is closed");
    return;
  } else if (status === "locked") {
    addInformation("the question is locked");
    return;
  } else if (status === "deleted") {
    addInformation("the question is deleted");
    return;
  }

  // Is the question at least two days old?
  var creationDate = Date.parse($("#question .owner .user-action-time span").attr("title"));
  var ageInSeconds = (Date.now() - creationDate)  / 1000;
  if (ageInSeconds < 2 * 86400) {
    addInformation("the question is not two days old yet");
    return;
  }

  // Get reputation of current user
  var reputation = parseInt($("div.-rep.js-header-rep").text().replace(",", ""), 10);
  if (reputation < 75) {
    addInformation("you need 75 reputation to create a bounty");
    return;
  }

  // Does the user have an answer to this question?
  var profileURL = $("a.my-profile.js-gps-track").attr("href");
  var hasAnswer = $(".answercell .user-details a").is(function(index, element) {
    return $(element).attr("href") == profileURL;
  });
  if (hasAnswer && reputation < 100) {
    addInformation("you need 100 reputation to create a bounty for a question which you've already answered");
    return;
  }

  // Find previous bounties on this question by current user
  var questionID = /^\/questions\/(\d+)\//.exec(questionLink)[1];
  $.get("https://" + window.location.host + "/users/current?tab=bounties&sort=offered", function (data) {
    var summaries = $(data).find("#question-summary-" + questionID);
    var minimumBounty = 50;
    if (summaries.length !== 0) {
      // assume the last (highest) bounty is the first in this list
      var highestBounty = parseInt(summaries[0].find("div.bounty-indicator").replace("+", ""), 10);
      minimumBounty = highestBounty * 2;
      if (minimumBounty > 500) {
        addInformation("last bounty was " + highestBounty + " which cannot be doubled");
        return;
      }
    }
    if (reputation < minimumBounty) {
      addInformation("minimum bounty (" + minimumBounty + ") higher than reputation (" + reputation + ")");
      return;
    }

    // Find currently active bounties by current user
    $.get("https://" + window.location.host + "/users/current?tab=bounties&sort=active", function (data) {
      var activeCount = parseInt($(data).find("#user-tab-bounties h1 span.count").text(), 10);
      if (activeCount >= 3) {
        addInformation("you have " + activeCount + " currently active bounties");
        return;
      }
    });
  });
})();
<commit_msg>Why No Bounty v0.2: - deleted questions - reputation comparison fix<commit_after>// ==UserScript==
// @name        Why No Bounty?
// @namespace   https://github.com/Glorfindel83/
// @description Adds information to a question indicating why you can't start a bounty.
// @author      Glorfindel
// @version     0.2
// @match       *://*.stackexchange.com/*
// @match       *://*.stackoverflow.com/*
// @match       *://*.superuser.com/*
// @match       *://*.serverfault.com/*
// @match       *://*.askubuntu.com/*
// @match       *://*.stackapps.com/*
// @match       *://*.mathoverflow.net/*
// @grant       none
// ==/UserScript==

(function () {
  "use strict";

  // Is this a Q&A page?
  var questionLink = $("#question-header a").attr("href");
  if (typeof(questionLink) === "undefined") {
    return;
  }

  // If there's a 'start a bounty' link, we have nothing to do.
  if ($("a.bounty-link.bounty").length !== 0) {
    return;
  }

  var addInformation = function (message) {
    $("#question > table > tbody").append("
"
      + "bounty not possible: " + message + "
");
  };

  // Meta site?
  if (window.location.host != "meta.stackexchange.com" && window.location.host.contains("meta")) {
    addInformation("meta sites don't have bounties");
    return;
  }

  // Is there an open bounty?
  if ($("div.question-status.bounty").length !== 0) {
  	addInformation("there is already an open bounty");
    return;
  }

  // Is the question closed / locked?
  var questionStatus = $("#question .question-status h2 b");
  if (questionStatus.length !== 0) {
    var status = questionStatus.get(0).text();
    if (status === "closed" || status === "put on hold" || status == "marked") {
      addInformation("the question is closed");
      return;
    } else if (status === "locked") {
      addInformation("the question is locked");
      return;
    } else if (status === "deleted") {
      addInformation("the question is deleted");
      return;
    }
  }

  // Is the question at least two days old?
  var creationDate = Date.parse($("#question .owner .user-action-time span").attr("title"));
  var ageInSeconds = (Date.now() - creationDate)  / 1000;
  if (ageInSeconds < 2 * 86400) {
    addInformation("the question is not two days old yet");
    return;
  }

  // Get reputation of current user
  var reputation = parseInt($("div.-rep.js-header-rep").text().replace(",", ""), 10);
  if (reputation < 75) {
    addInformation("you need 75 reputation to create a bounty");
    return;
  }

  // Does the user have an answer to this question?
  var profileURL = $("a.my-profile.js-gps-track").attr("href");
  var hasAnswer = $(".answercell .user-details a").is(function(index, element) {
    return $(element).attr("href") == profileURL;
  });
  if (hasAnswer && reputation < 100) {
    addInformation("you need 100 reputation to create a bounty for a question which you've already answered");
    return;
  }

  // Find previous bounties on this question by current user
  var questionID = /^\/questions\/(\d+)\//.exec(questionLink)[1];
  $.get("https://" + window.location.host + "/users/current?tab=bounties&sort=offered", function (data) {
    var summaries = $(data).find("#question-summary-" + questionID);
    var minimumBounty = 50;
    if (summaries.length !== 0) {
      // assume the last (highest) bounty is the first in this list
      var highestBounty = parseInt(summaries[0].find("div.bounty-indicator").replace("+", ""), 10);
      minimumBounty = highestBounty * 2;
      if (minimumBounty > 500) {
        addInformation("last bounty was " + highestBounty + " which cannot be doubled");
        return;
      }
    }
    if (reputation < minimumBounty) {
      addInformation("minimum bounty (" + minimumBounty + ") higher than reputation (" + reputation + ")");
      return;
    }

    // Find currently active bounties by current user
    $.get("https://" + window.location.host + "/users/current?tab=bounties&sort=active", function (data) {
      var activeCount = parseInt($(data).find("#user-tab-bounties h1 span.count").text(), 10);
      if (activeCount >= 3) {
        addInformation("you have " + activeCount + " currently active bounties");
        return;
      }
    });
  });
})();
<|endoftext|>


<commit_before>function runIsInViewport(tol) {
  $('#container > div.box').css('background-color', '#21221E').text('out of viewport');
  $('#container > div.box:in-viewport(' + tol + ')').css('background-color', '#C5C7BC').text('in viewport');
}

describe('isInViewport', function() {
  describe('viewport is window', function() {
    var div;

    before(function() {
      var html = '

1

';
      $('body').prepend(html);
      runIsInViewport(100);
      div = $('.box');
    });

    after(function() {
      $('#container').remove();
    });

    function top(x, tol) {
      div.css('top', '0');
      div.css('top', x + 'px');
      runIsInViewport(tol);
    }

    function left(x, tol) {
      div.css('left', '0');
      div.css('left', x + 'px');
      runIsInViewport(tol);
    }

    describe('when tolerance is 100', function() {
      describe('div location vertically in viewport', function() {
        describe('when bottom of div is outside tolerance region while top is inside', function() {
          it('should return the text from div as "in viewport"', function() {
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when bottom of div is equal to tolerance ie it\'s on the edge of tolerance region', function() {
          it('should return the text from div as "in viewport"', function() {
            top(-100, 100);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when bottom of div is inside tolerance region', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(-150, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when top of div is equal to tolerance ie it\'s on the edge of tolerance region', function() {
          it('should return the text from div as "in viewport"', function() {
            top(100, 100);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when top of div is outside tolerance region', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(101, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
      });

      describe('div location horizontally in viewport', function() {
        describe('when left is greater than viewport width', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(0, 100);
            left(99999, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is greater than viewport left edge', function() {
          it('should return the text from div as "out of viewport"', function() {
            left(-99999, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is lesser than viewport width', function() {
          it('should return the text from div as "in viewport"', function() {
            left(90, 100);
            div.text().should.be.exactly('in viewport');
            left(0, 100);
          });
        });
      });
    });

    describe('when tolerance is 0', function() {
      describe('div location vertically in viewport', function() {
        describe('when div top is 0', function() {
          it('should return the text from div as "in viewport"', function() {
            top(0, 0);
            left(0, 0);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when div top < 0 but bottom > 0', function() {
          it('should return the text from div as "in viewport"', function() {
            top(-1, 0);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when div bottom < 0', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(-201, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when div top > viewport height', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(99999, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
      });

      describe('div location horizontally in viewport', function() {
        describe('when left is greater than viewport width', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(0, 0);
            left(99999, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is greater than viewport left edge', function() {
          it('should return the text from div as "out of viewport"', function() {
            left(-99999, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is lesser than viewport width', function() {
          it('should return the text from div as "in viewport"', function() {
            left(90, 0);
            div.text().should.be.exactly('in viewport');
            left(0, 0);
          });
        });
      });
    });

    describe('when tolerance is -100', function(){
      it('should be window.height - abs(tolerance)',function(){
        var winHt = $(window).height();
        top(winHt-100, -100);
        div.text().should.be.exactly('in viewport');
      });
    });
  });
});
<commit_msg>formatting<commit_after>function runIsInViewport(tol) {
  $('#container > div.box').css('background-color', '#21221E').text('out of viewport');
  $('#container > div.box:in-viewport(' + tol + ')').css('background-color', '#C5C7BC').text('in viewport');
}

describe('isInViewport', function() {
  describe('viewport is window', function() {
    var div;

    before(function() {
      var html = '

1

';
      $('body').prepend(html);
      runIsInViewport(100);
      div = $('.box');
    });

    after(function() {
      $('#container').remove();
    });

    function top(x, tol) {
      div.css('top', '0');
      div.css('top', x + 'px');
      runIsInViewport(tol);
    }

    function left(x, tol) {
      div.css('left', '0');
      div.css('left', x + 'px');
      runIsInViewport(tol);
    }

    describe('when tolerance is 100', function() {
      describe('div location vertically in viewport', function() {
        describe('when bottom of div is outside tolerance region while top is inside', function() {
          it('should return the text from div as "in viewport"', function() {
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when bottom of div is equal to tolerance ie it\'s on the edge of tolerance region', function() {
          it('should return the text from div as "in viewport"', function() {
            top(-100, 100);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when bottom of div is inside tolerance region', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(-150, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when top of div is equal to tolerance ie it\'s on the edge of tolerance region', function() {
          it('should return the text from div as "in viewport"', function() {
            top(100, 100);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when top of div is outside tolerance region', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(101, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
      });

      describe('div location horizontally in viewport', function() {
        describe('when left is greater than viewport width', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(0, 100);
            left(99999, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is greater than viewport left edge', function() {
          it('should return the text from div as "out of viewport"', function() {
            left(-99999, 100);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is lesser than viewport width', function() {
          it('should return the text from div as "in viewport"', function() {
            left(90, 100);
            div.text().should.be.exactly('in viewport');
            left(0, 100);
          });
        });
      });
    });

    describe('when tolerance is 0', function() {
      describe('div location vertically in viewport', function() {
        describe('when div top is 0', function() {
          it('should return the text from div as "in viewport"', function() {
            top(0, 0);
            left(0, 0);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when div top < 0 but bottom > 0', function() {
          it('should return the text from div as "in viewport"', function() {
            top(-1, 0);
            div.text().should.be.exactly('in viewport');
          });
        });
        describe('when div bottom < 0', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(-201, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when div top > viewport height', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(99999, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
      });

      describe('div location horizontally in viewport', function() {
        describe('when left is greater than viewport width', function() {
          it('should return the text from div as "out of viewport"', function() {
            top(0, 0);
            left(99999, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is greater than viewport left edge', function() {
          it('should return the text from div as "out of viewport"', function() {
            left(-99999, 0);
            div.text().should.be.exactly('out of viewport');
          });
        });
        describe('when left is lesser than viewport width', function() {
          it('should return the text from div as "in viewport"', function() {
            left(90, 0);
            div.text().should.be.exactly('in viewport');
            left(0, 0);
          });
        });
      });
    });

    describe('when tolerance is -100', function() {
      it('should be window.height - abs(tolerance)', function() {
        var winHt = $(window).height();
        top(winHt - 100, -100);
        div.text().should.be.exactly('in viewport');
      });
    });
  });
});
<|endoftext|>