<commit_before>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-ul
 *
 * iotagent-ul is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-ul is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with iotagent-ul.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[iot_support@tid.es]
 *
 * Modified by: Fernando Méndez, Daniel Calvo - ATOS Research & Innovation
 */

/* eslint-disable no-prototype-builtins */

const iotAgentLib = require('iotagent-node-lib');
const regenerateTransid = iotAgentLib.regenerateTransid;
const config = require('./configService');
const transportSelector = require('./transportSelector');
const intoTrans = iotAgentLib.intoTrans;
const finishSouthBoundTransaction = iotAgentLib.finishSouthBoundTransaction;
const fillService = iotAgentLib.fillService;
const _ = require('underscore');
const utils = require('./iotaUtils');
const async = require('async');
const ulParser = require('./ulParser');
const constants = require('./constants');
let context = {
    op: 'IOTAUL.Common.Binding'
};

/**
 * Find the attribute given by its name between all the active attributes of the given device, returning its type, or
 * null otherwise.
 *
 * @param {String} attribute        Name of the attribute to find.
 * @param {Object} device           Device object containing all the information about a device.
 * @return {String}                 String identifier of the attribute type.
 */
function guessType(attribute, device) {
    for (let i = 0; i < device.active.length; i++) {
        if (device.active[i].name === attribute) {
            return device.active[i].type;
        }
    }

    if (attribute === constants.TIMESTAMP_ATTRIBUTE) {
        return constants.TIMESTAMP_TYPE_NGSI2;
    }
    return constants.DEFAULT_ATTRIBUTE_TYPE;
}

function sendConfigurationToDevice(device, apiKey, deviceId, results, callback) {
    transportSelector.applyFunctionFromBinding(
        [apiKey, deviceId, results],
        'sendConfigurationToDevice',
        device.transport,
        callback
    );
}

/**
 * Deals with configuration requests coming from the device. Whenever a new configuration requests arrives with a list
 * of attributes to retrieve, this handler asks the Context Broker for the values of those attributes, and publish a
 * new message in the "/1234/MQTT_2/configuration/values" topic
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} deviceId         Id of the device to be updated.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Object} objMessage          JSON object received.
 */
function manageConfigurationRequest(apiKey, deviceId, device, objMessage) {
    utils.manageConfiguration(
        apiKey,
        deviceId,
        device,
        objMessage,
        async.apply(sendConfigurationToDevice, device),
        function (error) {
            if (error) {
                iotAgentLib.alarms.raise(constants.MQTTB_ALARM, error);
            } else {
                iotAgentLib.alarms.release(constants.MQTTB_ALARM);
                config
                    .getLogger()
                    .debug(context, 'Configuration request finished for APIKey [%s] and Device [%s]', apiKey, deviceId);
            }
            finishSouthBoundTransaction(null);
        }
    );
}

/**
 * Utility function used to reduce UL Payload group arrays and process them to update their value in the Context Broker.
 * This function process a new element of the payload group, generating a function ready to be called with a callback
 * in a async.series() call.
 *
 * @param {Object} device       Object containing all the information about the device sending the measures.
 * @param {String} apikey       APIKey of the service the device belongs to.
 * @param {Array} previous      Array of prepared functions that send information to the Context Broker.
 * @param {Object} current      Information sent by the device.
 * @param {Number} index        Index of the group in the array.
 * @return {Array}             Updated array of functions.
 */

/* eslint-disable-next-line no-unused-vars */
function processMeasureGroup(device, apikey, previous, current, index) {
    const values = [];
    if (current.command) {
        previous.push(
            iotAgentLib.setCommandResult.bind(
                null,
                device.name,
                config.getConfig().iota.defaultResource,
                apikey,
                current.command,
                current.value,
                constants.COMMAND_STATUS_COMPLETED,
                device
            )
        );
    } else {
        for (const k in current) {
            if (current.hasOwnProperty(k)) {
                values.push({
                    name: k,
                    type: guessType(k, device),
                    value: current[k]
                });
            }
        }

        previous.push(iotAgentLib.update.bind(null, device.name, device.type, '', values, device));
    }

    return previous;
}

/**
 * Adds multiple measures to the Context Broker. Multiple measures come in the form of single-level JSON objects,
 * whose keys are the attribute names and whose values are the attribute values.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {String} messageStr       UL payload parsed to string.
 */
function multipleMeasures(apiKey, device, messageStr) {
    let updates = [];
    let parsedMessage;

    config.getLogger().debug('Processing multiple measures for device [%s] with apiKey [%s]', device.id, apiKey);

    // Parse Message
    try {
        parsedMessage = ulParser.parse(messageStr);
    } catch (e) {
        config.getLogger().error(context, 'MEASURES-003: Parse error parsing incoming message [%]', messageStr);
        return;
    }
    config.getLogger().debug(context, 'stringMessage: [%s] parsedMessage: [%s]', messageStr, parsedMessage);
    updates = parsedMessage.reduce(processMeasureGroup.bind(null, device, apiKey), []);

    async.series(updates, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().info(
                context,
                'Multiple measures for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Adds a single measure to the context broker. The message for single measures contains the direct value to
 * be inserted in the attribute, given by its name.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} attribute        Name of the attribute to update.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Buffer} messageStr       Raw message coming from the client parsed to string.
 * @param {Buffer} message          Raw message coming from the client.
 */
function singleMeasure(apiKey, attribute, device, messageStr, message) {
    config
        .getLogger()
        .debug('Processing single measure [%s] for device [%s] with apiKey [%s]', messageStr, device.id, apiKey);
    if (typeof message === 'object') {
        messageStr = message.toString('hex');
    }
    const values = [
        {
            name: attribute,
            type: guessType(attribute, device),
            value: messageStr
        }
    ];

    iotAgentLib.update(device.name, device.type, '', values, device, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().debug(
                context,
                'Single measure for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Handles an incoming message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      message body (Object or Buffer, depending on the value).
 */
function messageHandler(topic, message, protocol) {
    if (topic[0] !== '/') {
        topic = '/' + topic;
    }
    const topicInformation = topic.split('/');
    let parsedMessage;

    if (topicInformation[1].toLowerCase() === 'ul') {
        topicInformation.splice(1, 1);
    }
    const apiKey = topicInformation[1];
    const deviceId = topicInformation[2];
    const messageStr = message.toString();

    function processMessageForDevice(device, apiKey, topicInformation) {
        iotAgentLib.alarms.release(constants.MQTTB_ALARM);

        if (
            topicInformation[3] === constants.CONFIGURATION_SUFIX &&
            topicInformation[4] === constants.CONFIGURATION_COMMAND_SUFIX &&
            message
        ) {
            parsedMessage = ulParser.parseConfigurationRequest(messageStr);
            manageConfigurationRequest(apiKey, deviceId, device, parsedMessage);
        } else if (topicInformation[3] === constants.CONFIGURATION_COMMAND_UPDATE) {
            const commandObj = ulParser.result(messageStr);
            utils.updateCommand(
                apiKey,
                device,
                commandObj.result,
                commandObj.command,
                constants.COMMAND_STATUS_COMPLETED,
                function (error) {
                    config.getLogger().debug('Command updated with result: %s', error);
                }
            );
        } else if (topicInformation[4]) {
            singleMeasure(apiKey, topicInformation[4], device, messageStr, message);
        } else if (topicInformation[3] === constants.MEASURES_SUFIX) {
            multipleMeasures(apiKey, device, messageStr);
        } else {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                "MEASURES-004: Couldn't process message [%s] due to format issues.",
                /*jshint quotmark: single */
                message
            );
        }
    }

    function processDeviceMeasure(error, device) {
        if (error) {
            context = fillService(context, { service: 'n/a', subservice: 'n/a' });
            config.getLogger().warn(context, 'MEASURES-004: Device not found for topic [%s]', topic);
        } else {
            const localContext = _.clone(context);

            localContext.service = device.service;
            localContext.subservice = device.subservice;

            intoTrans(localContext, processMessageForDevice)(device, apiKey, topicInformation);
        }
    }

    utils.retrieveDevice(deviceId, apiKey, protocol, processDeviceMeasure);
}

/**
 * Handles an incoming AMQP message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the AMQP topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      AMQP message body (Object or Buffer, depending on the value).
 */
function amqpMessageHandler(topic, message) {
    regenerateTransid(topic);
    messageHandler(topic, message, 'AMQP');
}

/**
 * Handles an incoming MQTT message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the MQTT topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      MQTT message body (Object or Buffer, depending on the value).
 */
function mqttMessageHandler(topic, message) {
    regenerateTransid(topic);
    config.getLogger().debug(context, 'message topic: %s', topic);
    messageHandler(topic, message, 'MQTT');
}

exports.amqpMessageHandler = amqpMessageHandler;
exports.mqttMessageHandler = mqttMessageHandler;
exports.messageHandler = messageHandler;
exports.processMeasureGroup = processMeasureGroup;
exports.guessType = guessType;
<commit_msg>Update commonBindings.js<commit_after>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-ul
 *
 * iotagent-ul is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-ul is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with iotagent-ul.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[iot_support@tid.es]
 *
 * Modified by: Fernando Méndez, Daniel Calvo - ATOS Research & Innovation
 */

/* eslint-disable no-prototype-builtins */

const iotAgentLib = require('iotagent-node-lib');
const regenerateTransid = iotAgentLib.regenerateTransid;
const config = require('./configService');
const transportSelector = require('./transportSelector');
const intoTrans = iotAgentLib.intoTrans;
const finishSouthBoundTransaction = iotAgentLib.finishSouthBoundTransaction;
const fillService = iotAgentLib.fillService;
const _ = require('underscore');
const utils = require('./iotaUtils');
const async = require('async');
const ulParser = require('./ulParser');
const constants = require('./constants');
let context = {
    op: 'IOTAUL.Common.Binding'
};

/**
 * Find the attribute given by its name between all the active attributes of the given device, returning its type, or
 * null otherwise.
 *
 * @param {String} attribute        Name of the attribute to find.
 * @param {Object} device           Device object containing all the information about a device.
 * @return {String}                 String identifier of the attribute type.
 */
function guessType(attribute, device) {
    for (let i = 0; i < device.active.length; i++) {
        if (device.active[i].name === attribute) {
            return device.active[i].type;
        }
    }

    if (attribute === constants.TIMESTAMP_ATTRIBUTE) {
        return constants.TIMESTAMP_TYPE_NGSI2;
    }
    return constants.DEFAULT_ATTRIBUTE_TYPE;
}

function sendConfigurationToDevice(device, apiKey, deviceId, results, callback) {
    transportSelector.applyFunctionFromBinding(
        [apiKey, deviceId, results],
        'sendConfigurationToDevice',
        device.transport,
        callback
    );
}

/**
 * Deals with configuration requests coming from the device. Whenever a new configuration requests arrives with a list
 * of attributes to retrieve, this handler asks the Context Broker for the values of those attributes, and publish a
 * new message in the "/1234/MQTT_2/configuration/values" topic
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} deviceId         Id of the device to be updated.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Object} objMessage          JSON object received.
 */
function manageConfigurationRequest(apiKey, deviceId, device, objMessage) {
    utils.manageConfiguration(
        apiKey,
        deviceId,
        device,
        objMessage,
        async.apply(sendConfigurationToDevice, device),
        function (error) {
            if (error) {
                iotAgentLib.alarms.raise(constants.MQTTB_ALARM, error);
            } else {
                iotAgentLib.alarms.release(constants.MQTTB_ALARM);
                config
                    .getLogger()
                    .debug(context, 'Configuration request finished for APIKey [%s] and Device [%s]', apiKey, deviceId);
            }
            finishSouthBoundTransaction(null);
        }
    );
}

/**
 * Utility function used to reduce UL Payload group arrays and process them to update their value in the Context Broker.
 * This function process a new element of the payload group, generating a function ready to be called with a callback
 * in a async.series() call.
 *
 * @param {Object} device       Object containing all the information about the device sending the measures.
 * @param {String} apikey       APIKey of the service the device belongs to.
 * @param {Array} previous      Array of prepared functions that send information to the Context Broker.
 * @param {Object} current      Information sent by the device.
 * @param {Number} index        Index of the group in the array.
 * @return {Array}             Updated array of functions.
 */

/* eslint-disable-next-line no-unused-vars */
function processMeasureGroup(device, apikey, previous, current, index) {
    const values = [];
    if (current.command) {
        previous.push(
            iotAgentLib.setCommandResult.bind(
                null,
                device.name,
                config.getConfig().iota.defaultResource,
                apikey,
                current.command,
                current.value,
                constants.COMMAND_STATUS_COMPLETED,
                device
            )
        );
    } else {
        for (const k in current) {
            if (current.hasOwnProperty(k)) {
                values.push({
                    name: k,
                    type: guessType(k, device),
                    value: current[k]
                });
            }
        }

        previous.push(iotAgentLib.update.bind(null, device.name, device.type, '', values, device));
    }

    return previous;
}

/**
 * Adds multiple measures to the Context Broker. Multiple measures come in the form of single-level JSON objects,
 * whose keys are the attribute names and whose values are the attribute values.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {String} messageStr       UL payload parsed to string.
 */
function multipleMeasures(apiKey, device, messageStr) {
    let updates = [];
    let parsedMessage;

    config.getLogger().debug('Processing multiple measures for device [%s] with apiKey [%s]', device.id, apiKey);

    // Parse Message
    try {
        parsedMessage = ulParser.parse(messageStr);
    } catch (e) {
        config.getLogger().error(context, 'MEASURES-003: Parse error parsing incoming message [%]', messageStr);
        return;
    }
    config.getLogger().debug(context, 'stringMessage: [%s] parsedMessage: [%s]', messageStr, parsedMessage);
    updates = parsedMessage.reduce(processMeasureGroup.bind(null, device, apiKey), []);

    async.series(updates, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().info(
                context,
                'Multiple measures for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Adds a single measure to the context broker. The message for single measures contains the direct value to
 * be inserted in the attribute, given by its name.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} attribute        Name of the attribute to update.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Buffer} messageStr       Raw message coming from the client parsed to string.
 * @param {Buffer} message          Raw message coming from the client.
 */
function singleMeasure(apiKey, attribute, device, messageStr, message) {
    config
        .getLogger()
        .debug('Processing single measure [%s] for device [%s] with apiKey [%s]', messageStr, device.id, apiKey);
    const values = [
        {
            name: attribute,
            type: guessType(attribute, device),
            value: messageStr || message.toString('hex')
        }
    ];

    iotAgentLib.update(device.name, device.type, '', values, device, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().debug(
                context,
                'Single measure for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Handles an incoming message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      message body (Object or Buffer, depending on the value).
 */
function messageHandler(topic, message, protocol) {
    if (topic[0] !== '/') {
        topic = '/' + topic;
    }
    const topicInformation = topic.split('/');
    let parsedMessage;

    if (topicInformation[1].toLowerCase() === 'ul') {
        topicInformation.splice(1, 1);
    }
    const apiKey = topicInformation[1];
    const deviceId = topicInformation[2];
    const messageStr = message.toString();

    function processMessageForDevice(device, apiKey, topicInformation) {
        iotAgentLib.alarms.release(constants.MQTTB_ALARM);

        if (
            topicInformation[3] === constants.CONFIGURATION_SUFIX &&
            topicInformation[4] === constants.CONFIGURATION_COMMAND_SUFIX &&
            message
        ) {
            parsedMessage = ulParser.parseConfigurationRequest(messageStr);
            manageConfigurationRequest(apiKey, deviceId, device, parsedMessage);
        } else if (topicInformation[3] === constants.CONFIGURATION_COMMAND_UPDATE) {
            const commandObj = ulParser.result(messageStr);
            utils.updateCommand(
                apiKey,
                device,
                commandObj.result,
                commandObj.command,
                constants.COMMAND_STATUS_COMPLETED,
                function (error) {
                    config.getLogger().debug('Command updated with result: %s', error);
                }
            );
        } else if (topicInformation[4]) {
            singleMeasure(apiKey, topicInformation[4], device, messageStr, message);
        } else if (topicInformation[3] === constants.MEASURES_SUFIX) {
            multipleMeasures(apiKey, device, messageStr);
        } else {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                "MEASURES-004: Couldn't process message [%s] due to format issues.",
                /*jshint quotmark: single */
                message
            );
        }
    }

    function processDeviceMeasure(error, device) {
        if (error) {
            context = fillService(context, { service: 'n/a', subservice: 'n/a' });
            config.getLogger().warn(context, 'MEASURES-004: Device not found for topic [%s]', topic);
        } else {
            const localContext = _.clone(context);

            localContext.service = device.service;
            localContext.subservice = device.subservice;

            intoTrans(localContext, processMessageForDevice)(device, apiKey, topicInformation);
        }
    }

    utils.retrieveDevice(deviceId, apiKey, protocol, processDeviceMeasure);
}

/**
 * Handles an incoming AMQP message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the AMQP topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      AMQP message body (Object or Buffer, depending on the value).
 */
function amqpMessageHandler(topic, message) {
    regenerateTransid(topic);
    messageHandler(topic, message, 'AMQP');
}

/**
 * Handles an incoming MQTT message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the MQTT topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      MQTT message body (Object or Buffer, depending on the value).
 */
function mqttMessageHandler(topic, message) {
    regenerateTransid(topic);
    config.getLogger().debug(context, 'message topic: %s', topic);
    messageHandler(topic, message, 'MQTT');
}

exports.amqpMessageHandler = amqpMessageHandler;
exports.mqttMessageHandler = mqttMessageHandler;
exports.messageHandler = messageHandler;
exports.processMeasureGroup = processMeasureGroup;
exports.guessType = guessType;
<|endoftext|>


<commit_before>"use strict";
var fs = require('fs');
var should = require('should');

var passme = require('../bin/passme');

describe('BooleanLiteral', function(){
    var code = 'var a = !true,false;';
    it('should return array when the value is BooleanLiteral', function(){
        passme.tokenize(code).length.should.equal(8);
    })
})
describe('RegularExpression', function(){
    var reg = '/^(?:(?:or|and|like)\s*)+|\s*(?:(?:or|and|like)\s*)+$/mi;';
    var code = 'a = '+reg;
    it('should return array when the value is RegularExpression', function(){
        passme.tokenize(code)[2].value.should.equal('/^(?:(?:or|and|like)s*)+|s*(?:(?:or|and|like)s*)+$/mi');
    })
})
describe('Punctuator', function(){
    var code ='a /=(b/(c/(d/(e*f))))';
    it('should return array when the value is Punctuator', function(){
        passme.tokenize(code)[1].value.should.equal('/=');
    })
    it('should return array when the value is Punctuator', function(){
        passme.tokenize(code).length.should.equal(19);
    })
    it('should return array when the value is Punctuator', function(){
        passme.tokenize(code)[13].type.should.equal('Punctuator');
    })
})
describe('jQuery', function(){
    var jQuery = fs.readFileSync('test/3rdparty/jquery-1.10.2.js','utf8');
    it('should return array when the value is jQuery', function(){
        passme.tokenize(jQuery).length.should.equal(48007);
    })
})
describe('kissy', function(){
    var kissy = fs.readFileSync('test/3rdparty/kissy-1.4.0.js','utf8');
    it('should return array when the value is jQuery', function(){
        passme.tokenize(kissy)[22811].value.should.equal('nodejs');
    })
})
<commit_msg>add test case<commit_after>"use strict";
var fs = require('fs');

var passme = require('../bin/passme');

describe('BooleanLiteral', function(){
    var code = 'var a = !true,false;';
    it('should return array when the value is BooleanLiteral', function(){
        passme.tokenize(code).length.should.equal(8);
    })
})
describe('RegularExpression', function(){
    var reg = '/^(?:(?:or|and|like)\s*)+|\s*(?:(?:or|and|like)\s*)+$/mi;';
    var code = 'a = '+reg;
    it('should return array when the value is RegularExpression', function(){
        passme.tokenize(code)[2].value.should.equal('/^(?:(?:or|and|like)s*)+|s*(?:(?:or|and|like)s*)+$/mi');
    })
})
describe('Punctuator', function(){
    var code ='a /=(b/(c/(d/(e*f))))';
    it('should return array when the value is Punctuator', function(){
        passme.tokenize(code)[1].value.should.equal('/=');
    })
    it('should return array when the value is Punctuator', function(){
        passme.tokenize(code).length.should.equal(19);
    })
    it('should return array when the value is Punctuator', function(){
        passme.tokenize(code)[13].type.should.equal('Punctuator');
    })
})
describe('jQuery', function(){
    var jQuery = fs.readFileSync('test/3rdparty/jquery-1.10.2.js','utf8');
    it('should return array when the value is jQuery', function(){
        passme.tokenize(jQuery).length.should.equal(48007);
    })
})
describe('kissy', function(){
    var kissy = fs.readFileSync('test/3rdparty/kissy-1.4.0.js','utf8');
    it('should return array when the value is jQuery', function(){
        passme.tokenize(kissy)[22811].value.should.equal('nodejs');
    })
})
<|endoftext|>


<commit_before>module("css", { teardown: moduleTeardown });

test("css(String|Hash)", function() {
	expect( 42 );

	equals( jQuery('#main').css("display"), 'block', 'Check for css property "display"');

	ok( jQuery('#nothiddendiv').is(':visible'), 'Modifying CSS display: Assert element is visible');
	jQuery('#nothiddendiv').css({display: 'none'});
	ok( !jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is hidden');
	jQuery('#nothiddendiv').css({display: 'block'});
	ok( jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is visible');

	var div = jQuery( "
" );

	// These should be "auto" (or some better value)
	// temporarily provide "0px" for backwards compat
	equals( div.css("width"), "0px", "Width on disconnected node." );
	equals( div.css("height"), "0px", "Height on disconnected node." );

	div.css({ width: 4, height: 4 });

	equals( div.css("width"), "4px", "Width on disconnected node." );
	equals( div.css("height"), "4px", "Height on disconnected node." );

	var div2 = jQuery( "
<input type='text' style='height:20px;'/><textarea style='height:20px;'/>

").appendTo("body");

	equals( div2.find("input").css("height"), "20px", "Height on hidden input." );
	equals( div2.find("textarea").css("height"), "20px", "Height on hidden textarea." );
	equals( div2.find("div").css("height"), "20px", "Height on hidden textarea." );

	div2.remove();

	// handle negative numbers by ignoring #1599, #4216
	jQuery('#nothiddendiv').css({ 'width': 1, 'height': 1 });

	var width = parseFloat(jQuery('#nothiddendiv').css('width')), height = parseFloat(jQuery('#nothiddendiv').css('height'));
	jQuery('#nothiddendiv').css({ width: -1, height: -1 });
	equals( parseFloat(jQuery('#nothiddendiv').css('width')), width, 'Test negative width ignored')
	equals( parseFloat(jQuery('#nothiddendiv').css('height')), height, 'Test negative height ignored')

	equals( jQuery('
').css('display'), 'none', 'Styles on disconnected nodes');

	jQuery('#floatTest').css({'float': 'right'});
	equals( jQuery('#floatTest').css('float'), 'right', 'Modified CSS float using "float": Assert float is right');
	jQuery('#floatTest').css({'font-size': '30px'});
	equals( jQuery('#floatTest').css('font-size'), '30px', 'Modified CSS font-size: Assert font-size is 30px');

	jQuery.each("0,0.25,0.5,0.75,1".split(','), function(i, n) {
		jQuery('#foo').css({opacity: n});
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a String" );
		jQuery('#foo').css({opacity: parseFloat(n)});
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a Number" );
	});
	jQuery('#foo').css({opacity: ''});
	equals( jQuery('#foo').css('opacity'), '1', "Assert opacity is 1 when set to an empty String" );

	equals( jQuery('#empty').css('opacity'), '0', "Assert opacity is accessible via filter property set in stylesheet in IE" );
	jQuery('#empty').css({ opacity: '1' });
	equals( jQuery('#empty').css('opacity'), '1', "Assert opacity is taken from style attribute when set vs stylesheet in IE with filters" );
	jQuery.support.opacity ?
		ok(true, "Requires the same number of tests"):
		ok( ~jQuery('#empty')[0].currentStyle.filter.indexOf('gradient'), "Assert setting opacity doesn't overwrite other filters of the stylesheet in IE" );

	var div = jQuery('#nothiddendiv'), child = jQuery('#nothiddendivchild');

	equals( parseInt(div.css("fontSize")), 16, "Verify fontSize px set." );
	equals( parseInt(div.css("font-size")), 16, "Verify fontSize px set." );
	equals( parseInt(child.css("fontSize")), 16, "Verify fontSize px set." );
	equals( parseInt(child.css("font-size")), 16, "Verify fontSize px set." );

	child.css("height", "100%");
	equals( child[0].style.height, "100%", "Make sure the height is being set correctly." );

	child.attr("class", "em");
	equals( parseInt(child.css("fontSize")), 32, "Verify fontSize em set." );

	// Have to verify this as the result depends upon the browser's CSS
	// support for font-size percentages
	child.attr("class", "prct");
	var prctval = parseInt(child.css("fontSize")), checkval = 0;
	if ( prctval === 16 || prctval === 24 ) {
		checkval = prctval;
	}

	equals( prctval, checkval, "Verify fontSize % set." );

	equals( typeof child.css("width"), "string", "Make sure that a string width is returned from css('width')." );

	var old = child[0].style.height;

	// Test NaN
	child.css("height", parseFloat("zoo"));
	equals( child[0].style.height, old, "Make sure height isn't changed on NaN." );

	// Test null
	child.css("height", null);
	equals( child[0].style.height, old, "Make sure height isn't changed on null." );

	old = child[0].style.fontSize;

	// Test NaN
	child.css("font-size", parseFloat("zoo"));
	equals( child[0].style.fontSize, old, "Make sure font-size isn't changed on NaN." );

	// Test null
	child.css("font-size", null);
	equals( child[0].style.fontSize, old, "Make sure font-size isn't changed on null." );
});

test("css(String, Object)", function() {
	expect(22);

	ok( jQuery('#nothiddendiv').is(':visible'), 'Modifying CSS display: Assert element is visible');
	jQuery('#nothiddendiv').css("display", 'none');
	ok( !jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is hidden');
	jQuery('#nothiddendiv').css("display", 'block');
	ok( jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is visible');

	jQuery("#nothiddendiv").css("top", "-1em");
	ok( jQuery("#nothiddendiv").css("top"), -16, "Check negative number in EMs." );

	jQuery('#floatTest').css('float', 'left');
	equals( jQuery('#floatTest').css('float'), 'left', 'Modified CSS float using "float": Assert float is left');
	jQuery('#floatTest').css('font-size', '20px');
	equals( jQuery('#floatTest').css('font-size'), '20px', 'Modified CSS font-size: Assert font-size is 20px');

	jQuery.each("0,0.25,0.5,0.75,1".split(','), function(i, n) {
		jQuery('#foo').css('opacity', n);
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a String" );
		jQuery('#foo').css('opacity', parseFloat(n));
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a Number" );
	});
	jQuery('#foo').css('opacity', '');
	equals( jQuery('#foo').css('opacity'), '1', "Assert opacity is 1 when set to an empty String" );

	// using contents will get comments regular, text, and comment nodes
	var j = jQuery("#nonnodes").contents();
	j.css("overflow", "visible");
	equals( j.css("overflow"), "visible", "Check node,textnode,comment css works" );
	// opera sometimes doesn't update 'display' correctly, see #2037
	jQuery("#t2037")[0].innerHTML = jQuery("#t2037")[0].innerHTML
	equals( jQuery("#t2037 .hidden").css("display"), "none", "Make sure browser thinks it is hidden" );

	var div = jQuery("#nothiddendiv"),
		display = div.css("display"),
		ret = div.css("display", undefined);

	equals( ret, div, "Make sure setting undefined returns the original set." );
	equals( div.css("display"), display, "Make sure that the display wasn't changed." );

	// Test for Bug #5509
	var success = true;
	try {
		jQuery('#foo').css("backgroundColor", "rgba(0, 0, 0, 0.1)");
	}
	catch (e) {
		success = false;
	}
	ok( success, "Setting RGBA values does not throw Error" );
});

if ( !jQuery.support.opacity ) {
	test("css(String, Object) for MSIE", function() {
		// for #1438, IE throws JS error when filter exists but doesn't have opacity in it
		jQuery('#foo').css("filter", "progid:DXImageTransform.Microsoft.Chroma(color='red');");
		equals( jQuery('#foo').css('opacity'), '1', "Assert opacity is 1 when a different filter is set in IE, #1438" );

		var filterVal = "progid:DXImageTransform.Microsoft.Alpha(opacity=30) progid:DXImageTransform.Microsoft.Blur(pixelradius=5)";
		var filterVal2 = "progid:DXImageTransform.Microsoft.alpha(opacity=100) progid:DXImageTransform.Microsoft.Blur(pixelradius=5)";
		var filterVal3 = "progid:DXImageTransform.Microsoft.Blur(pixelradius=5)";
		jQuery('#foo').css("filter", filterVal);
		equals( jQuery('#foo').css("filter"), filterVal, "css('filter', val) works" );
		jQuery('#foo').css("opacity", 1);
		equals( jQuery('#foo').css("filter"), filterVal2, "Setting opacity in IE doesn't duplicate opacity filter" );
		equals( jQuery('#foo').css("opacity"), 1, "Setting opacity in IE with other filters works" );
		jQuery('#foo').css("filter", filterVal3).css("opacity", 1);
		ok( jQuery('#foo').css("filter").indexOf(filterVal3) !== -1, "Setting opacity in IE doesn't clobber other filters" );
	});
}

test("css(String, Function)", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css("font-size", function() {
		var size = sizes[index];
		index++;
		return size;
	});

	index = 0;

	jQuery("#cssFunctionTest div").each(function() {
		var computedSize = jQuery(this).css("font-size")
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
	});

	jQuery("#cssFunctionTest").remove();
});

test("css(String, Function) with incoming value", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css("font-size", function() {
		var size = sizes[index];
		index++;
		return size;
	});

	index = 0;

	jQuery("#cssFunctionTest div").css("font-size", function(i, computedSize) {
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
		return computedSize;
	});

	jQuery("#cssFunctionTest").remove();
});

test("css(Object) where values are Functions", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css({fontSize: function() {
		var size = sizes[index];
		index++;
		return size;
	}});

	index = 0;

	jQuery("#cssFunctionTest div").each(function() {
		var computedSize = jQuery(this).css("font-size")
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
	});

	jQuery("#cssFunctionTest").remove();
});

test("css(Object) where values are Functions with incoming values", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css({fontSize: function() {
		var size = sizes[index];
		index++;
		return size;
	}});

	index = 0;

	jQuery("#cssFunctionTest div").css({"font-size": function(i, computedSize) {
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
		return computedSize;
	}});

	jQuery("#cssFunctionTest").remove();
});

test("jQuery.css(elem, 'height') doesn't clear radio buttons (bug #1095)", function () {
	expect(4);

	var $checkedtest = jQuery("#checkedtest");
	// IE6 was clearing "checked" in jQuery.css(elem, "height");
	jQuery.css($checkedtest[0], "height");
	ok( !! jQuery(":radio:first", $checkedtest).attr("checked"), "Check first radio still checked." );
	ok( ! jQuery(":radio:last", $checkedtest).attr("checked"), "Check last radio still NOT checked." );
	ok( !! jQuery(":checkbox:first", $checkedtest).attr("checked"), "Check first checkbox still checked." );
	ok( ! jQuery(":checkbox:last", $checkedtest).attr("checked"), "Check last checkbox still NOT checked." );
});

test(":visible selector works properly on table elements (bug #4512)", function () {
	expect(1);

	jQuery('#table').html('
cell	cell
');
	equals(jQuery('#table td:visible').length, 1, "hidden cell is not perceived as visible");
});

test(":visible selector works properly on children with a hidden parent (bug #4512)", function () {
	expect(1);
	jQuery('#table').css('display', 'none').html('
cell	cell
');
	equals(jQuery('#table td:visible').length, 0, "hidden cell children not perceived as visible");
});

test("internal ref to elem.runtimeStyle (bug #7608)", function () {
	expect(1);
	var result = true;

	try {
		jQuery("#foo").css( { width: "0%" } ).css("width");
	} catch (e) {
		result = false;
	}

	ok( result, "elem.runtimeStyle does not throw exception" );
});
<commit_msg>Double quote is the new simple quote<commit_after>module("css", { teardown: moduleTeardown });

test("css(String|Hash)", function() {
	expect( 42 );

	equals( jQuery('#main').css("display"), 'block', 'Check for css property "display"');

	ok( jQuery('#nothiddendiv').is(':visible'), 'Modifying CSS display: Assert element is visible');
	jQuery('#nothiddendiv').css({display: 'none'});
	ok( !jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is hidden');
	jQuery('#nothiddendiv').css({display: 'block'});
	ok( jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is visible');

	var div = jQuery( "
" );

	// These should be "auto" (or some better value)
	// temporarily provide "0px" for backwards compat
	equals( div.css("width"), "0px", "Width on disconnected node." );
	equals( div.css("height"), "0px", "Height on disconnected node." );

	div.css({ width: 4, height: 4 });

	equals( div.css("width"), "4px", "Width on disconnected node." );
	equals( div.css("height"), "4px", "Height on disconnected node." );

	var div2 = jQuery( "
<input type='text' style='height:20px;'/><textarea style='height:20px;'/>

").appendTo("body");

	equals( div2.find("input").css("height"), "20px", "Height on hidden input." );
	equals( div2.find("textarea").css("height"), "20px", "Height on hidden textarea." );
	equals( div2.find("div").css("height"), "20px", "Height on hidden textarea." );

	div2.remove();

	// handle negative numbers by ignoring #1599, #4216
	jQuery('#nothiddendiv').css({ 'width': 1, 'height': 1 });

	var width = parseFloat(jQuery('#nothiddendiv').css('width')), height = parseFloat(jQuery('#nothiddendiv').css('height'));
	jQuery('#nothiddendiv').css({ width: -1, height: -1 });
	equals( parseFloat(jQuery('#nothiddendiv').css('width')), width, 'Test negative width ignored')
	equals( parseFloat(jQuery('#nothiddendiv').css('height')), height, 'Test negative height ignored')

	equals( jQuery('
').css('display'), 'none', 'Styles on disconnected nodes');

	jQuery('#floatTest').css({'float': 'right'});
	equals( jQuery('#floatTest').css('float'), 'right', 'Modified CSS float using "float": Assert float is right');
	jQuery('#floatTest').css({'font-size': '30px'});
	equals( jQuery('#floatTest').css('font-size'), '30px', 'Modified CSS font-size: Assert font-size is 30px');

	jQuery.each("0,0.25,0.5,0.75,1".split(','), function(i, n) {
		jQuery('#foo').css({opacity: n});
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a String" );
		jQuery('#foo').css({opacity: parseFloat(n)});
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a Number" );
	});
	jQuery('#foo').css({opacity: ''});
	equals( jQuery('#foo').css('opacity'), '1', "Assert opacity is 1 when set to an empty String" );

	equals( jQuery('#empty').css('opacity'), '0', "Assert opacity is accessible via filter property set in stylesheet in IE" );
	jQuery('#empty').css({ opacity: '1' });
	equals( jQuery('#empty').css('opacity'), '1', "Assert opacity is taken from style attribute when set vs stylesheet in IE with filters" );
	jQuery.support.opacity ?
		ok(true, "Requires the same number of tests"):
		ok( ~jQuery("#empty")[0].currentStyle.filter.indexOf("gradient"), "Assert setting opacity doesn't overwrite other filters of the stylesheet in IE" );

	var div = jQuery('#nothiddendiv'), child = jQuery('#nothiddendivchild');

	equals( parseInt(div.css("fontSize")), 16, "Verify fontSize px set." );
	equals( parseInt(div.css("font-size")), 16, "Verify fontSize px set." );
	equals( parseInt(child.css("fontSize")), 16, "Verify fontSize px set." );
	equals( parseInt(child.css("font-size")), 16, "Verify fontSize px set." );

	child.css("height", "100%");
	equals( child[0].style.height, "100%", "Make sure the height is being set correctly." );

	child.attr("class", "em");
	equals( parseInt(child.css("fontSize")), 32, "Verify fontSize em set." );

	// Have to verify this as the result depends upon the browser's CSS
	// support for font-size percentages
	child.attr("class", "prct");
	var prctval = parseInt(child.css("fontSize")), checkval = 0;
	if ( prctval === 16 || prctval === 24 ) {
		checkval = prctval;
	}

	equals( prctval, checkval, "Verify fontSize % set." );

	equals( typeof child.css("width"), "string", "Make sure that a string width is returned from css('width')." );

	var old = child[0].style.height;

	// Test NaN
	child.css("height", parseFloat("zoo"));
	equals( child[0].style.height, old, "Make sure height isn't changed on NaN." );

	// Test null
	child.css("height", null);
	equals( child[0].style.height, old, "Make sure height isn't changed on null." );

	old = child[0].style.fontSize;

	// Test NaN
	child.css("font-size", parseFloat("zoo"));
	equals( child[0].style.fontSize, old, "Make sure font-size isn't changed on NaN." );

	// Test null
	child.css("font-size", null);
	equals( child[0].style.fontSize, old, "Make sure font-size isn't changed on null." );
});

test("css(String, Object)", function() {
	expect(22);

	ok( jQuery('#nothiddendiv').is(':visible'), 'Modifying CSS display: Assert element is visible');
	jQuery('#nothiddendiv').css("display", 'none');
	ok( !jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is hidden');
	jQuery('#nothiddendiv').css("display", 'block');
	ok( jQuery('#nothiddendiv').is(':visible'), 'Modified CSS display: Assert element is visible');

	jQuery("#nothiddendiv").css("top", "-1em");
	ok( jQuery("#nothiddendiv").css("top"), -16, "Check negative number in EMs." );

	jQuery('#floatTest').css('float', 'left');
	equals( jQuery('#floatTest').css('float'), 'left', 'Modified CSS float using "float": Assert float is left');
	jQuery('#floatTest').css('font-size', '20px');
	equals( jQuery('#floatTest').css('font-size'), '20px', 'Modified CSS font-size: Assert font-size is 20px');

	jQuery.each("0,0.25,0.5,0.75,1".split(','), function(i, n) {
		jQuery('#foo').css('opacity', n);
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a String" );
		jQuery('#foo').css('opacity', parseFloat(n));
		equals( jQuery('#foo').css('opacity'), parseFloat(n), "Assert opacity is " + parseFloat(n) + " as a Number" );
	});
	jQuery('#foo').css('opacity', '');
	equals( jQuery('#foo').css('opacity'), '1', "Assert opacity is 1 when set to an empty String" );

	// using contents will get comments regular, text, and comment nodes
	var j = jQuery("#nonnodes").contents();
	j.css("overflow", "visible");
	equals( j.css("overflow"), "visible", "Check node,textnode,comment css works" );
	// opera sometimes doesn't update 'display' correctly, see #2037
	jQuery("#t2037")[0].innerHTML = jQuery("#t2037")[0].innerHTML
	equals( jQuery("#t2037 .hidden").css("display"), "none", "Make sure browser thinks it is hidden" );

	var div = jQuery("#nothiddendiv"),
		display = div.css("display"),
		ret = div.css("display", undefined);

	equals( ret, div, "Make sure setting undefined returns the original set." );
	equals( div.css("display"), display, "Make sure that the display wasn't changed." );

	// Test for Bug #5509
	var success = true;
	try {
		jQuery('#foo').css("backgroundColor", "rgba(0, 0, 0, 0.1)");
	}
	catch (e) {
		success = false;
	}
	ok( success, "Setting RGBA values does not throw Error" );
});

if ( !jQuery.support.opacity ) {
	test("css(String, Object) for MSIE", function() {
		// for #1438, IE throws JS error when filter exists but doesn't have opacity in it
		jQuery('#foo').css("filter", "progid:DXImageTransform.Microsoft.Chroma(color='red');");
		equals( jQuery('#foo').css('opacity'), '1', "Assert opacity is 1 when a different filter is set in IE, #1438" );

		var filterVal = "progid:DXImageTransform.Microsoft.Alpha(opacity=30) progid:DXImageTransform.Microsoft.Blur(pixelradius=5)";
		var filterVal2 = "progid:DXImageTransform.Microsoft.alpha(opacity=100) progid:DXImageTransform.Microsoft.Blur(pixelradius=5)";
		var filterVal3 = "progid:DXImageTransform.Microsoft.Blur(pixelradius=5)";
		jQuery('#foo').css("filter", filterVal);
		equals( jQuery('#foo').css("filter"), filterVal, "css('filter', val) works" );
		jQuery('#foo').css("opacity", 1);
		equals( jQuery('#foo').css("filter"), filterVal2, "Setting opacity in IE doesn't duplicate opacity filter" );
		equals( jQuery('#foo').css("opacity"), 1, "Setting opacity in IE with other filters works" );
		jQuery('#foo').css("filter", filterVal3).css("opacity", 1);
		ok( jQuery('#foo').css("filter").indexOf(filterVal3) !== -1, "Setting opacity in IE doesn't clobber other filters" );
	});
}

test("css(String, Function)", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css("font-size", function() {
		var size = sizes[index];
		index++;
		return size;
	});

	index = 0;

	jQuery("#cssFunctionTest div").each(function() {
		var computedSize = jQuery(this).css("font-size")
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
	});

	jQuery("#cssFunctionTest").remove();
});

test("css(String, Function) with incoming value", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css("font-size", function() {
		var size = sizes[index];
		index++;
		return size;
	});

	index = 0;

	jQuery("#cssFunctionTest div").css("font-size", function(i, computedSize) {
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
		return computedSize;
	});

	jQuery("#cssFunctionTest").remove();
});

test("css(Object) where values are Functions", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css({fontSize: function() {
		var size = sizes[index];
		index++;
		return size;
	}});

	index = 0;

	jQuery("#cssFunctionTest div").each(function() {
		var computedSize = jQuery(this).css("font-size")
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
	});

	jQuery("#cssFunctionTest").remove();
});

test("css(Object) where values are Functions with incoming values", function() {
	expect(3);

	var sizes = ["10px", "20px", "30px"];

	jQuery("

" +
				 "

" +
				 "

")
		.appendTo("body");

	var index = 0;

	jQuery("#cssFunctionTest div").css({fontSize: function() {
		var size = sizes[index];
		index++;
		return size;
	}});

	index = 0;

	jQuery("#cssFunctionTest div").css({"font-size": function(i, computedSize) {
		var expectedSize = sizes[index]
		equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize );
		index++;
		return computedSize;
	}});

	jQuery("#cssFunctionTest").remove();
});

test("jQuery.css(elem, 'height') doesn't clear radio buttons (bug #1095)", function () {
	expect(4);

	var $checkedtest = jQuery("#checkedtest");
	// IE6 was clearing "checked" in jQuery.css(elem, "height");
	jQuery.css($checkedtest[0], "height");
	ok( !! jQuery(":radio:first", $checkedtest).attr("checked"), "Check first radio still checked." );
	ok( ! jQuery(":radio:last", $checkedtest).attr("checked"), "Check last radio still NOT checked." );
	ok( !! jQuery(":checkbox:first", $checkedtest).attr("checked"), "Check first checkbox still checked." );
	ok( ! jQuery(":checkbox:last", $checkedtest).attr("checked"), "Check last checkbox still NOT checked." );
});

test(":visible selector works properly on table elements (bug #4512)", function () {
	expect(1);

	jQuery('#table').html('
cell	cell
');
	equals(jQuery('#table td:visible').length, 1, "hidden cell is not perceived as visible");
});

test(":visible selector works properly on children with a hidden parent (bug #4512)", function () {
	expect(1);
	jQuery('#table').css('display', 'none').html('
cell	cell
');
	equals(jQuery('#table td:visible').length, 0, "hidden cell children not perceived as visible");
});

test("internal ref to elem.runtimeStyle (bug #7608)", function () {
	expect(1);
	var result = true;

	try {
		jQuery("#foo").css( { width: "0%" } ).css("width");
	} catch (e) {
		result = false;
	}

	ok( result, "elem.runtimeStyle does not throw exception" );
});
<|endoftext|>


<commit_before>
var original = require('browserify/lib/builtins');

exports.vm = require.resolve('vm-titanium');
exports.os = require.resolve('titanium-os/src/titanium-os');
exports.fs = require.resolve('ti-fs/src/ti-fs');

Object.keys(original).forEach(function (key) {
  if (!(key in exports)) exports[ key ] = original[ key ];
});

exports.assert = false; // ???
<commit_msg>Do not exclude (ignore) `assert`, this was an horrible workaround<commit_after>
var original = require('browserify/lib/builtins');

exports.vm = require.resolve('vm-titanium');
exports.os = require.resolve('titanium-os/src/titanium-os');
exports.fs = require.resolve('ti-fs/src/ti-fs');

Object.keys(original).forEach(function (key) {
  if (!(key in exports)) exports[ key ] = original[ key ];
});
<|endoftext|>


<commit_before>/*
 *   Copyright 2014-2015 Guy Bedford (http://guybedford.com)
 *
 *   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
 *
 *       http://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.
 */

var config = require('../config');
var path = require('path');
var extend = require('../common').extend;
var hasProperties = require('../common').hasProperties;
var Promise = require('rsvp').Promise;
var ui = require('../ui');
var fs = require('graceful-fs');
var asp = require('rsvp').denodeify;
var readJSON = require('../common').readJSON;
var alphabetize = require('../common').alphabetize;
var globalConfig = require('../global-config');
var processDeps = require('../package').processDeps;
var stringify = require('../common').stringify;

var isWindows = process.platform.match(/^win/);
var winRegEx = /\\/g;
function winPath(path) {
  if (!isWindows)
    return path;
  return path.replace(winRegEx, '/');
}

/*
 * Package.json Configuration Class
 *
 * main
 * registry
 * dependencies
 * configFile
 * baseURL
 * lib
 * dist
 * packages
 * format
 * map
 * buildConfig
 *
 */
function PackageJSON(fileName) {
  this.fileName = fileName;
}

PackageJSON.prototype.read = function(prompts) {
  if (this.__read)
    throw 'Package.json file already read';

  this.__read = true;
  var self = this;
  var pjson;

  return readJSON(this.fileName)
  .then(function(_pjson) {
    self.originalPjson = _pjson;

    self.jspmPrefix = true;
    if (_pjson.registry && !_pjson.jspm)
      self.jspmPrefix = false;

    // derive the jspm config from the 'jspm' property in the package.json
    // also sets the registry property if dependencies are jspm-prefixed
    pjson = config.derivePackageConfig(_pjson);

    self.pjson = pjson;

    prompts = prompts || (!_pjson.jspm && !_pjson.registry);

    if (prompts)
      return doInitPrompts(pjson, self.fileName, self.jspmPrefix)
      .then(function(prefixed) {
        self.jspmPrefix = prefixed;
      });
  })
  .then(function() {
    self.dir = path.dirname(self.fileName);

    // populate defaults as we go
    var defaults = self.defaults = {};

    self.registry = pjson.registry || globalConfig.config.registry;
    self.dependencies = {};
    // only read dependencies when combined with a registry property
    self.dependencies = processDeps(pjson.dependencies, self.registry);

    defaults.baseURL = self.dir;
    self.baseURL = pjson.directories && pjson.directories.baseURL && path.resolve(self.dir, pjson.directories.baseURL) || defaults.baseURL;

    defaults.packages = path.resolve(self.baseURL, 'jspm_packages');

    defaults.main = path.join(self.baseURL, 'index');
    self.main = pjson.main || defaults.main;

    // NB can remove jspmPackages suport in time
    self.packages = pjson.directories && pjson.directories.packages && path.resolve(self.dir, pjson.directories.packages) ||
      pjson.directories && pjson.directories.jspmPackages && path.resolve(self.dir, pjson.directories.jspmPackages) || defaults.packages;

    defaults.configFile = path.resolve(self.baseURL, 'config.js');
    self.configFile = pjson.configFile && path.resolve(self.dir, pjson.configFile) || defaults.configFile;

    self.format = pjson.format;
    self.map = extend({}, pjson.map || {});

    // NB remove when nodelibs no longer built this way
    self.useJSExtensions = pjson.useJSExtensions;

    self.buildConfig = extend({}, pjson.buildConfig || {});

    return prompts;
  });
};

PackageJSON.prototype.write = function() {
  var pjson = this.jspmPrefix ? {} : this.originalPjson;

  if (!this.jspmPrefix) {
    delete pjson.jspm;
    pjson.registry = this.registry || globalConfig.config.registry;
  }
  else if (this.registry !== 'jspm') {
    pjson.registry = this.registry;
  }

  var defaults = this.defaults;

  // only set properties that differ from the defaults
  if (this.main !== defaults.main)
    pjson.main = this.main;

  if (this.format)
    pjson.format = this.format;

  var directories = {};
  if (this.baseURL !== defaults.baseURL)
    directories.baseURL = this.baseURL;
  if (this.packages !== defaults.packages)
    directories.packages = this.packages;

  if (hasProperties(directories)) {
    for (var d in directories) {
      if (!directories.hasOwnProperty(d))
        continue;
      directories[d] = winPath(path.relative(this.dir, directories[d]));
      if (!directories[d])
        directories[d] = '.';
    }
  }
  pjson.directories = extend(pjson.directories || {}, this.pjson.directories || {});
  pjson.directories = alphabetize(pjson.directories);

  if (!hasProperties(pjson.directories))
    delete pjson.directories;

  if (this.configFile !== defaults.configFile)
    pjson.configFile = winPath(path.relative(this.dir, this.configFile));

  pjson.map = alphabetize(this.map);
  if (!hasProperties(pjson.map))
    delete pjson.map;

  pjson.dependencies = {};
  var depValue, dependencies = this.dependencies;
  for (var dkey in dependencies) {
    if (!dependencies.hasOwnProperty(dkey))
      continue;
    var dep = dependencies[dkey];
    if (!dep)
      continue;
    if (dep.endpoint === this.registry) {
      if (dkey === dep.package)
        depValue = dep.version;
      else
        depValue = dep.exactPackage;
    }
    else
      depValue = dep.exactName;

    pjson.dependencies[dkey] = depValue;
  }
  pjson.dependencies = alphabetize(pjson.dependencies);
  if (!hasProperties(pjson.dependencies))
    delete pjson.dependencies;

  pjson.buildConfig = this.buildConfig;
  if (!hasProperties(pjson.buildConfig))
    delete pjson.buildConfig;

  // dedupe jspm properties against base properties
  if (this.jspmPrefix) {
    for (var p in pjson) {
      if (this.originalPjson[p] === pjson[p])
        delete pjson[p];
    }
    this.originalPjson.jspm = pjson;
  }

  // NB check that the file hasn't changed since we opened it and if so, prompt
  return asp(fs.writeFile)(this.fileName, stringify(this.originalPjson) + config.newLine);
};

// can take an existing non-jspm package.json
function doInitPrompts(pjson, pjsonPath, prefixed) {
  var baseDir = path.dirname(pjsonPath);
  var base;

  pjson.directories = pjson.directories || {};

  return Promise.resolve()
  .then(function() {
    return ui.confirm('Would you like jspm to prefix the jspm package.json properties under %jspm%?', prefixed);
  })
  .then(function(prefix) {
    prefixed = prefix;
    return ui.input('Enter server baseURL (public folder path)', pjson.directories.baseURL || './');
  })
  .then(function(baseURL) {
    base = path.relative(process.cwd(), path.resolve(baseURL));
    baseURL = path.relative(baseDir, path.resolve(baseURL));
    if (!base)
      base = '.';
    base += path.sep;
    if (baseURL)
      pjson.directories.baseURL = baseURL;
    return ui.input('Enter project code folder', pjson.directories.lib || base);
  })
  .then(function(lib) {
    pjson.directories.lib = path.relative(baseDir, path.resolve(lib));
    if (!pjson.directories.lib)
      pjson.directories.lib = '.';
    return ui.input('Enter jspm packages folder', pjson.directories.packages || base + 'jspm_packages');
  })
  .then(function(packages) {
    pjson.directories.packages = path.relative(baseDir, path.resolve(packages));
    return ui.input('Enter config file path', pjson.configFile || base + 'config.js');
  })
  .then(function(configFile) {
    pjson.configFile = path.relative(baseDir, path.resolve(configFile));
    return prefixed;
  });
}

module.exports = PackageJSON;
<commit_msg>finish lib / dist folder handling deprecation<commit_after>/*
 *   Copyright 2014-2015 Guy Bedford (http://guybedford.com)
 *
 *   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
 *
 *       http://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.
 */

var config = require('../config');
var path = require('path');
var extend = require('../common').extend;
var hasProperties = require('../common').hasProperties;
var Promise = require('rsvp').Promise;
var ui = require('../ui');
var fs = require('graceful-fs');
var asp = require('rsvp').denodeify;
var readJSON = require('../common').readJSON;
var alphabetize = require('../common').alphabetize;
var globalConfig = require('../global-config');
var processDeps = require('../package').processDeps;
var stringify = require('../common').stringify;

var isWindows = process.platform.match(/^win/);
var winRegEx = /\\/g;
function winPath(path) {
  if (!isWindows)
    return path;
  return path.replace(winRegEx, '/');
}

/*
 * Package.json Configuration Class
 *
 * main
 * registry
 * dependencies
 * configFile
 * baseURL
 * packages
 * format
 * map
 * buildConfig
 *
 */
function PackageJSON(fileName) {
  this.fileName = fileName;
}

PackageJSON.prototype.read = function(prompts) {
  if (this.__read)
    throw 'Package.json file already read';

  this.__read = true;
  var self = this;
  var pjson;

  return readJSON(this.fileName)
  .then(function(_pjson) {
    self.originalPjson = _pjson;

    self.jspmPrefix = true;
    if (_pjson.registry && !_pjson.jspm)
      self.jspmPrefix = false;

    // derive the jspm config from the 'jspm' property in the package.json
    // also sets the registry property if dependencies are jspm-prefixed
    pjson = config.derivePackageConfig(_pjson);

    self.pjson = pjson;

    prompts = prompts || (!_pjson.jspm && !_pjson.registry);

    if (prompts)
      return doInitPrompts(pjson, self.fileName, self.jspmPrefix)
      .then(function(prefixed) {
        self.jspmPrefix = prefixed;
      });
  })
  .then(function() {
    self.dir = path.dirname(self.fileName);

    // populate defaults as we go
    var defaults = self.defaults = {};

    self.registry = pjson.registry || globalConfig.config.registry;
    self.dependencies = {};
    // only read dependencies when combined with a registry property
    self.dependencies = processDeps(pjson.dependencies, self.registry);

    defaults.baseURL = self.dir;
    self.baseURL = pjson.directories && pjson.directories.baseURL && path.resolve(self.dir, pjson.directories.baseURL) || defaults.baseURL;

    defaults.packages = path.resolve(self.baseURL, 'jspm_packages');

    defaults.main = path.join(self.baseURL, 'index');
    self.main = pjson.main || defaults.main;

    // NB can remove jspmPackages suport in time
    self.packages = pjson.directories && pjson.directories.packages && path.resolve(self.dir, pjson.directories.packages) ||
      pjson.directories && pjson.directories.jspmPackages && path.resolve(self.dir, pjson.directories.jspmPackages) || defaults.packages;

    defaults.configFile = path.resolve(self.baseURL, 'config.js');
    self.configFile = pjson.configFile && path.resolve(self.dir, pjson.configFile) || defaults.configFile;

    self.format = pjson.format;
    self.map = extend({}, pjson.map || {});

    // NB remove when nodelibs no longer built this way
    self.useJSExtensions = pjson.useJSExtensions;

    self.buildConfig = extend({}, pjson.buildConfig || {});

    return prompts;
  });
};

PackageJSON.prototype.write = function() {
  var pjson = this.jspmPrefix ? {} : this.originalPjson;

  if (!this.jspmPrefix) {
    delete pjson.jspm;
    pjson.registry = this.registry || globalConfig.config.registry;
  }
  else if (this.registry !== 'jspm') {
    pjson.registry = this.registry;
  }

  var defaults = this.defaults;

  // only set properties that differ from the defaults
  if (this.main !== defaults.main)
    pjson.main = this.main;

  if (this.format)
    pjson.format = this.format;

  var directories = {};
  if (this.baseURL !== defaults.baseURL)
    directories.baseURL = this.baseURL;
  if (this.packages !== defaults.packages)
    directories.packages = this.packages;

  if (hasProperties(directories)) {
    for (var d in directories) {
      if (!directories.hasOwnProperty(d))
        continue;
      directories[d] = winPath(path.relative(this.dir, directories[d]));
      if (!directories[d])
        directories[d] = '.';
    }
  }
  pjson.directories = extend(pjson.directories || {}, this.pjson.directories || {});
  pjson.directories = alphabetize(pjson.directories);

  if (!hasProperties(pjson.directories))
    delete pjson.directories;

  if (this.configFile !== defaults.configFile)
    pjson.configFile = winPath(path.relative(this.dir, this.configFile));

  pjson.map = alphabetize(this.map);
  if (!hasProperties(pjson.map))
    delete pjson.map;

  pjson.dependencies = {};
  var depValue, dependencies = this.dependencies;
  for (var dkey in dependencies) {
    if (!dependencies.hasOwnProperty(dkey))
      continue;
    var dep = dependencies[dkey];
    if (!dep)
      continue;
    if (dep.endpoint === this.registry) {
      if (dkey === dep.package)
        depValue = dep.version;
      else
        depValue = dep.exactPackage;
    }
    else
      depValue = dep.exactName;

    pjson.dependencies[dkey] = depValue;
  }
  pjson.dependencies = alphabetize(pjson.dependencies);
  if (!hasProperties(pjson.dependencies))
    delete pjson.dependencies;

  pjson.buildConfig = this.buildConfig;
  if (!hasProperties(pjson.buildConfig))
    delete pjson.buildConfig;

  // dedupe jspm properties against base properties
  if (this.jspmPrefix) {
    for (var p in pjson) {
      if (this.originalPjson[p] === pjson[p])
        delete pjson[p];
    }
    this.originalPjson.jspm = pjson;
  }

  // NB check that the file hasn't changed since we opened it and if so, prompt
  return asp(fs.writeFile)(this.fileName, stringify(this.originalPjson) + config.newLine);
};

// can take an existing non-jspm package.json
function doInitPrompts(pjson, pjsonPath, prefixed) {
  var baseDir = path.dirname(pjsonPath);
  var base;

  pjson.directories = pjson.directories || {};

  return Promise.resolve()
  .then(function() {
    return ui.confirm('Would you like jspm to prefix the jspm package.json properties under %jspm%?', prefixed);
  })
  .then(function(prefix) {
    prefixed = prefix;
    return ui.input('Enter server baseURL (public folder path)', pjson.directories.baseURL || './');
  })
  .then(function(baseURL) {
    base = path.relative(process.cwd(), path.resolve(baseURL));
    baseURL = path.relative(baseDir, path.resolve(baseURL));
    if (!base)
      base = '.';
    base += path.sep;
    if (baseURL)
      pjson.directories.baseURL = baseURL;
    return ui.input('Enter jspm packages folder', pjson.directories.packages || base + 'jspm_packages');
  })
  .then(function(packages) {
    pjson.directories.packages = path.relative(baseDir, path.resolve(packages));
    return ui.input('Enter config file path', pjson.configFile || base + 'config.js');
  })
  .then(function(configFile) {
    pjson.configFile = path.relative(baseDir, path.resolve(configFile));
    return prefixed;
  });
}

module.exports = PackageJSON;
<|endoftext|>


<commit_before>'use strict';

var file = require('file'),
    fs = require('fs'),
    config = require('../../lib/config').config,
    semver = require('semver'),
    path = require('path'),
    log = require('../logger'),
    jade = require('jade'),
    pjson = require('../../package.json');

module.exports = function parse(bundleName) {
    var dir = path.resolve(process.cwd(), 'bundles/' + bundleName + '/');
    var manifestPath = path.join(dir, 'nodecg.json');

    // Return null if nodecg.json doesn't exist
    if (!fs.existsSync(manifestPath)) {
        return null;
    }

    // Read metadata from the nodecg.json manifest file
    var bundle = readManifest(manifestPath);
    bundle.dir = dir;

    // Don't load the bundle if it depends on a different version of NodeCG
    if (!isCompatible(bundle.nodecgDependency)) {
        log.error("[lib/bundles/parser.js] Did not load %s as it requires NodeCG %s. Current version is %s",
            bundle.name, bundle.nodecgDependency, pjson.version);
        return null;
    }

    // If there is a config file for this bundle, parse it
    var cfgPath = path.resolve(process.cwd(), 'cfg', bundle.name + '.json');
    bundle.config = readConfig(cfgPath);

    // Read all HTML/CSS/JS files in the package's "dashboard" dir into memory
    // They will then get passed to dashboard.jade at the time of 'GET' and be templated into the final rendered page
    bundle.dashboard = {};
    bundle.dashboard.dir = path.join(bundle.dir, 'dashboard');

    readDashboardResources(bundle);
    readDashboardPanels(bundle);

    return bundle;
};

function readManifest(path) {
    // Parse the JSON from nodecg.json
    var manifest = JSON.parse(fs.readFileSync(path, 'utf8'));

    // Copy the JSON we use into the beginnings of our bundle object
    return {
        name: manifest.name,
        version: manifest.version,
        description: manifest.description,
        homepage: manifest.homepage,
        authors: manifest.authors,
        license: manifest.license,
        nodecgDependency: manifest.nodecgDependency,
        extension: manifest.extension,
        bundleDependencies: manifest.bundleDependencies
    };
}

function readDashboardPanels(bundle) {
    bundle.dashboard.panels = [];
    var manifestPath = path.join(bundle.dashboard.dir, 'panels.json');

    try {
        var manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
    } catch(e) {
        log.warn("[%s] dashboard/panels.json not found or not valid, this bundle will not have any dashboard panels", bundle.name);
        return;
    }

    manifest.forEach(function(panel, index) {
        try {
            var missingProps = [];
            if (typeof(panel.name) === 'undefined') missingProps.push('name');
            if (typeof(panel.title) === 'undefined') missingProps.push('title');
            if (typeof(panel.file) === 'undefined') missingProps.push('file');
            if (missingProps.length) {
                log.error("[%s] Panel #%d could not be parsed as it is missing the following properties:", bundle.name, index, missingProps.join(', '));
                return;
            }

            panel.file = path.join(bundle.dashboard.dir, panel.file);
            panel.width = panel.width || 2;
            panel.faIcon = panel.faIcon || 'fa-question-circle';
            panel.viewUrl = panel.viewUrl || '/view/' + bundle.name;

            switch (path.extname(panel.file)) {
                case '.jade':
                    // Render the panel as Jade, giving it access to both this specific bundle's config and NodeCG's config
                    panel.body = jade.renderFile(panel.file, {
                        bundleConfig: bundle.config,
                        ncgConfig: config
                    });
                    break;
                case '.html':
                    // Copy the HTML verbatim with no further processing
                    panel.body = fs.readFileSync(panel.file, {encoding: 'utf8'});
                    break;
            }

            bundle.dashboard.panels.push(panel);
        } catch (e) {
            log.error("[lib/bundles/parser] Error parsing panel '%s' for bundle %s:\n", panel.id, bundle.name, e.message);
        }
    });
}

function readDashboardResources(bundle) {
    // Arrays with the JS, CSS, and HTML
    bundle.dashboard.js = [];
    bundle.dashboard.css = [];

    try {
        var dashboardDir = fs.readdirSync(bundle.dashboard.dir); // returns just the filenames of each file in the folder, not full path
    } catch(e) {
        log.warn("[%s] No dashboard folder found, this bundle will not have dashboard panels", bundle.name);
    }

    dashboardDir.forEach(function(file) {
        var filepath = path.join(bundle.dashboard.dir, file);
        if (!fs.statSync(filepath).isFile()) {
            // Skip directories
            return;
        }

        switch (path.extname(filepath)) {
            case '.js':
                bundle.dashboard.js.push(fs.readFileSync(filepath, {encoding: 'utf8'}));
                break;
            case '.css':
                bundle.dashboard.css.push(fs.readFileSync(filepath, {encoding: 'utf8'}));
                break;
        }
    });
}

function readConfig(cfgPath) {
    if (!fs.existsSync(cfgPath)) {
        // Skip if config does not exist
        return null;
    }

    return JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
}

function isCompatible(nodecgDependency) {
    return !!semver.satisfies(pjson.version, nodecgDependency);
}
<commit_msg>Fix parser not returning when a bundle's dashboard dir is non-existant<commit_after>'use strict';

var file = require('file'),
    fs = require('fs'),
    config = require('../../lib/config').config,
    semver = require('semver'),
    path = require('path'),
    log = require('../logger'),
    jade = require('jade'),
    pjson = require('../../package.json');

module.exports = function parse(bundleName) {
    var dir = path.resolve(process.cwd(), 'bundles/' + bundleName + '/');
    var manifestPath = path.join(dir, 'nodecg.json');

    // Return null if nodecg.json doesn't exist
    if (!fs.existsSync(manifestPath)) {
        return null;
    }

    // Read metadata from the nodecg.json manifest file
    var bundle = readManifest(manifestPath);
    bundle.dir = dir;

    // Don't load the bundle if it depends on a different version of NodeCG
    if (!isCompatible(bundle.nodecgDependency)) {
        log.error("[lib/bundles/parser.js] Did not load %s as it requires NodeCG %s. Current version is %s",
            bundle.name, bundle.nodecgDependency, pjson.version);
        return null;
    }

    // If there is a config file for this bundle, parse it
    var cfgPath = path.resolve(process.cwd(), 'cfg', bundle.name + '.json');
    bundle.config = readConfig(cfgPath);

    // Read all HTML/CSS/JS files in the package's "dashboard" dir into memory
    // They will then get passed to dashboard.jade at the time of 'GET' and be templated into the final rendered page
    bundle.dashboard = {};
    bundle.dashboard.dir = path.join(bundle.dir, 'dashboard');

    readDashboardResources(bundle);
    readDashboardPanels(bundle);

    return bundle;
};

function readManifest(path) {
    // Parse the JSON from nodecg.json
    var manifest = JSON.parse(fs.readFileSync(path, 'utf8'));

    // Copy the JSON we use into the beginnings of our bundle object
    return {
        name: manifest.name,
        version: manifest.version,
        description: manifest.description,
        homepage: manifest.homepage,
        authors: manifest.authors,
        license: manifest.license,
        nodecgDependency: manifest.nodecgDependency,
        extension: manifest.extension,
        bundleDependencies: manifest.bundleDependencies
    };
}

function readDashboardPanels(bundle) {
    bundle.dashboard.panels = [];
    var manifestPath = path.join(bundle.dashboard.dir, 'panels.json');

    try {
        var manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
    } catch(e) {
        log.warn("[%s] dashboard/panels.json not found or not valid, this bundle will not have any dashboard panels", bundle.name);
        return;
    }

    manifest.forEach(function(panel, index) {
        try {
            var missingProps = [];
            if (typeof(panel.name) === 'undefined') missingProps.push('name');
            if (typeof(panel.title) === 'undefined') missingProps.push('title');
            if (typeof(panel.file) === 'undefined') missingProps.push('file');
            if (missingProps.length) {
                log.error("[%s] Panel #%d could not be parsed as it is missing the following properties:", bundle.name, index, missingProps.join(', '));
                return;
            }

            panel.file = path.join(bundle.dashboard.dir, panel.file);
            panel.width = panel.width || 2;
            panel.faIcon = panel.faIcon || 'fa-question-circle';
            panel.viewUrl = panel.viewUrl || '/view/' + bundle.name;

            switch (path.extname(panel.file)) {
                case '.jade':
                    // Render the panel as Jade, giving it access to both this specific bundle's config and NodeCG's config
                    panel.body = jade.renderFile(panel.file, {
                        bundleConfig: bundle.config,
                        ncgConfig: config
                    });
                    break;
                case '.html':
                    // Copy the HTML verbatim with no further processing
                    panel.body = fs.readFileSync(panel.file, {encoding: 'utf8'});
                    break;
            }

            bundle.dashboard.panels.push(panel);
        } catch (e) {
            log.error("[lib/bundles/parser] Error parsing panel '%s' for bundle %s:\n", panel.id, bundle.name, e.message);
        }
    });
}

function readDashboardResources(bundle) {
    // Arrays with the JS, CSS, and HTML
    bundle.dashboard.js = [];
    bundle.dashboard.css = [];

    try {
        var dashboardDir = fs.readdirSync(bundle.dashboard.dir); // returns just the filenames of each file in the folder, not full path
    } catch(e) {
        log.warn("[%s] No dashboard folder found, this bundle will not have dashboard panels", bundle.name);
        return;
    }

    dashboardDir.forEach(function(file) {
        var filepath = path.join(bundle.dashboard.dir, file);
        if (!fs.statSync(filepath).isFile()) {
            // Skip directories
            return;
        }

        switch (path.extname(filepath)) {
            case '.js':
                bundle.dashboard.js.push(fs.readFileSync(filepath, {encoding: 'utf8'}));
                break;
            case '.css':
                bundle.dashboard.css.push(fs.readFileSync(filepath, {encoding: 'utf8'}));
                break;
        }
    });
}

function readConfig(cfgPath) {
    if (!fs.existsSync(cfgPath)) {
        // Skip if config does not exist
        return null;
    }

    return JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
}

function isCompatible(nodecgDependency) {
    return !!semver.satisfies(pjson.version, nodecgDependency);
}
<|endoftext|>