<commit_before>/**
 * @fileoverview
 * Partial implementation of an SB2 JSON importer.
 * Parses provided JSON and then generates all needed
 * scratch-vm runtime structures.
 */

var Blocks = require('../engine/blocks');
var Clone = require('../sprites/clone');
var Sprite = require('../sprites/sprite');
var Color = require('../util/color.js');
var uid = require('../util/uid');
var specMap = require('./sb2specmap');
var Variable = require('../engine/variable');
var List = require('../engine/list');

/**
 * Top-level handler. Parse provided JSON,
 * and process the top-level object (the stage object).
 * @param {!string} json SB2-format JSON to load.
 * @param {!Runtime} runtime Runtime object to load all structures into.
 */
function sb2import (json, runtime) {
    parseScratchObject(
        JSON.parse(json),
        runtime,
        true
    );
}

/**
 * Parse a single "Scratch object" and create all its in-memory VM objects.
 * @param {!Object} object From-JSON "Scratch object:" sprite, stage, watcher.
 * @param {!Runtime} runtime Runtime object to load all structures into.
 * @param {boolean} topLevel Whether this is the top-level object (stage).
 */
function parseScratchObject (object, runtime, topLevel) {
    if (!object.hasOwnProperty('objName')) {
        // Watcher/monitor - skip this object until those are implemented in VM.
        // @todo
        return;
    }
    // Blocks container for this object.
    var blocks = new Blocks();
    // @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
    var sprite = new Sprite(blocks, runtime);
    // Sprite/stage name from JSON.
    if (object.hasOwnProperty('objName')) {
        sprite.name = object.objName;
    }
    // Costumes from JSON.
    if (object.hasOwnProperty('costumes')) {
        for (var i = 0; i < object.costumes.length; i++) {
            var costume = object.costumes[i];
            // @todo: Make sure all the relevant metadata is being pulled out.
            sprite.costumes.push({
                skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/'
                    + costume.baseLayerMD5 + '/get/',
                name: costume.costumeName,
                bitmapResolution: costume.bitmapResolution,
                rotationCenterX: costume.rotationCenterX,
                rotationCenterY: costume.rotationCenterY
            });
        }
    }
    // If included, parse any and all scripts/blocks on the object.
    if (object.hasOwnProperty('scripts')) {
        parseScripts(object.scripts, blocks);
    }
    // Create the first clone, and load its run-state from JSON.
    var target = sprite.createClone();
    // Add it to the runtime's list of targets.
    runtime.targets.push(target);
    // Load target properties from JSON.
    if (object.hasOwnProperty('variables')) {
        for (var j = 0; j < object.variables.length; j++) {
            var variable = object.variables[j];
            target.variables[variable.name] = new Variable(
                variable.name,
                variable.value,
                variable.isPersistent
            );
        }
    }
    if (object.hasOwnProperty('lists')) {
        for (var k = 0; k < object.lists.length; k++) {
            var list = object.lists[k];
            // @todo: monitor properties.
            target.lists[list.listName] = new List(
                list.listName,
                list.contents
            );
        }
    }
    if (object.hasOwnProperty('scratchX')) {
        target.x = object.scratchX;
    }
    if (object.hasOwnProperty('scratchY')) {
        target.y = object.scratchY;
    }
    if (object.hasOwnProperty('direction')) {
        target.direction = object.direction;
    }
    if (object.hasOwnProperty('scale')) {
        // SB2 stores as 1.0 = 100%; we use % in the VM.
        target.size = object.scale * 100;
    }
    if (object.hasOwnProperty('visible')) {
        target.visible = object.visible;
    }
    if (object.hasOwnProperty('currentCostumeIndex')) {
        target.currentCostume = Math.round(object.currentCostumeIndex);
    }
    if (object.hasOwnProperty('rotationStyle')) {
        if (object.rotationStyle == 'none') {
            target.rotationStyle = Clone.ROTATION_STYLE_NONE;
        } else if (object.rotationStyle == 'leftRight') {
            target.rotationStyle = Clone.ROTATION_STYLE_LEFT_RIGHT;
        } else if (object.rotationStyle == 'normal') {
            target.rotationStyle = Clone.ROTATION_STYLE_ALL_AROUND;
        }
    }
    target.isStage = topLevel;
    target.updateAllDrawableProperties();
    // The stage will have child objects; recursively process them.
    if (object.children) {
        for (var m = 0; m < object.children.length; m++) {
            parseScratchObject(object.children[m], runtime, false);
        }
    }
}

/**
 * Parse a Scratch object's scripts into VM blocks.
 * This should only handle top-level scripts that include X, Y coordinates.
 * @param {!Object} scripts Scripts object from SB2 JSON.
 * @param {!Blocks} blocks Blocks object to load parsed blocks into.
 */
function parseScripts (scripts, blocks) {
    for (var i = 0; i < scripts.length; i++) {
        var script = scripts[i];
        var scriptX = script[0];
        var scriptY = script[1];
        var blockList = script[2];
        var parsedBlockList = parseBlockList(blockList);
        if (parsedBlockList[0]) {
            // Adjust script coordinates to account for
            // larger block size in scratch-blocks.
            // @todo: Determine more precisely the right formulas here.
            parsedBlockList[0].x = scriptX * 1.1;
            parsedBlockList[0].y = scriptY * 1.1;
            parsedBlockList[0].topLevel = true;
            parsedBlockList[0].parent = null;
        }
        // Flatten children and create add the blocks.
        var convertedBlocks = flatten(parsedBlockList);
        for (var j = 0; j < convertedBlocks.length; j++) {
            blocks.createBlock(convertedBlocks[j]);
        }
    }
}

/**
 * Parse any list of blocks from SB2 JSON into a list of VM-format blocks.
 * Could be used to parse a top-level script,
 * a list of blocks in a branch (e.g., in forever),
 * or a list of blocks in an argument (e.g., move [pick random...]).
 * @param {Array.<Object>} blockList SB2 JSON-format block list.
 * @return {Array.<Object>} Scratch VM-format block list.
 */
function parseBlockList (blockList) {
    var resultingList = [];
    var previousBlock = null; // For setting next.
    for (var i = 0; i < blockList.length; i++) {
        var block = blockList[i];
        var parsedBlock = parseBlock(block);
        if (previousBlock) {
            parsedBlock.parent = previousBlock.id;
            previousBlock.next = parsedBlock.id;
        }
        previousBlock = parsedBlock;
        resultingList.push(parsedBlock);
    }
    return resultingList;
}

/**
 * Flatten a block tree into a block list.
 * Children are temporarily stored on the `block.children` property.
 * @param {Array.<Object>} blocks list generated by `parseBlockList`.
 * @return {Array.<Object>} Flattened list to be passed to `blocks.createBlock`.
 */
function flatten (blocks) {
    var finalBlocks = [];
    for (var i = 0; i < blocks.length; i++) {
        var block = blocks[i];
        finalBlocks.push(block);
        if (block.children) {
            finalBlocks = finalBlocks.concat(flatten(block.children));
        }
        delete block.children;
    }
    return finalBlocks;
}

/**
 * Convert a Scratch 2.0 procedure string (e.g., "my_procedure %s %b %n")
 * into an argument map. This allows us to provide the expected inputs
 * to a mutated procedure call.
 * @param {string} procCode Scratch 2.0 procedure string.
 * @return {Object} Argument map compatible with those in sb2specmap.
 */
function parseProcedureArgMap (procCode) {
    var argMap = [
        {} // First item in list is op string.
    ];
    var INPUT_PREFIX = 'input';
    var inputCount = 0;
    // Split by %n, %b, %s.
    var parts = procCode.split(/(?=[^\\]\%[nbs])/);
    for (var i = 0; i < parts.length; i++) {
        var part = parts[i].trim();
        if (part.substring(0, 1) == '%') {
            var argType = part.substring(1, 2);
            var arg = {
                type: 'input',
                inputName: INPUT_PREFIX + (inputCount++)
            };
            if (argType == 'n') {
                arg.inputOp = 'math_number';
            } else if (argType == 's') {
                arg.inputOp = 'text';
            }
            argMap.push(arg);
        }
    }
    return argMap;
}

/**
 * Parse a single SB2 JSON-formatted block and its children.
 * @param {!Object} sb2block SB2 JSON-formatted block.
 * @return {Object} Scratch VM format block.
 */
function parseBlock (sb2block) {
    // First item in block object is the old opcode (e.g., 'forward:').
    var oldOpcode = sb2block[0];
    // Convert the block using the specMap. See sb2specmap.js.
    if (!oldOpcode || !specMap[oldOpcode]) {
        console.warn('Couldn\'t find SB2 block: ', oldOpcode);
        return;
    }
    var blockMetadata = specMap[oldOpcode];
    // Block skeleton.
    var activeBlock = {
        id: uid(), // Generate a new block unique ID.
        opcode: blockMetadata.opcode, // Converted, e.g. "motion_movesteps".
        inputs: {}, // Inputs to this block and the blocks they point to.
        fields: {}, // Fields on this block and their values.
        next: null, // Next block.
        shadow: false, // No shadow blocks in an SB2 by default.
        children: [] // Store any generated children, flattened in `flatten`.
    };
    // For a procedure call, generate argument map from proc string.
    if (oldOpcode == 'call') {
        blockMetadata.argMap = parseProcedureArgMap(sb2block[1]);
    }
    // Look at the expected arguments in `blockMetadata.argMap.`
    // The basic problem here is to turn positional SB2 arguments into
    // non-positional named Scratch VM arguments.
    for (var i = 0; i < blockMetadata.argMap.length; i++) {
        var expectedArg = blockMetadata.argMap[i];
        var providedArg = sb2block[i + 1]; // (i = 0 is opcode)
        // Whether the input is obscuring a shadow.
        var shadowObscured = false;
        // Positional argument is an input.
        if (expectedArg.type == 'input') {
            // Create a new block and input metadata.
            var inputUid = uid();
            activeBlock.inputs[expectedArg.inputName] = {
                name: expectedArg.inputName,
                block: null,
                shadow: null
            };
            if (typeof providedArg == 'object' && providedArg) {
                // Block or block list occupies the input.
                var innerBlocks;
                if (typeof providedArg[0] == 'object' && providedArg[0]) {
                    // Block list occupies the input.
                    innerBlocks = parseBlockList(providedArg);
                } else {
                    // Single block occupies the input.
                    innerBlocks = [parseBlock(providedArg)];
                }
                for (var j = 0; j < innerBlocks.length; j++) {
                    innerBlocks[j].parent = activeBlock.id;
                }
                // Obscures any shadow.
                shadowObscured = true;
                activeBlock.inputs[expectedArg.inputName].block = (
                    innerBlocks[0].id
                );
                activeBlock.children = (
                    activeBlock.children.concat(innerBlocks)
                );
            }
            // Generate a shadow block to occupy the input.
            if (!expectedArg.inputOp) {
                // No editable shadow input; e.g., for a boolean.
                continue;
            }
            // Each shadow has a field generated for it automatically.
            // Value to be filled in the field.
            var fieldValue = providedArg;
            // Shadows' field names match the input name, except for these:
            var fieldName = expectedArg.inputName;
            if (expectedArg.inputOp == 'math_number' ||
                expectedArg.inputOp == 'math_whole_number' ||
                expectedArg.inputOp == 'math_positive_number' ||
                expectedArg.inputOp == 'math_integer' ||
                expectedArg.inputOp == 'math_angle') {
                fieldName = 'NUM';
                // Fields are given Scratch 2.0 default values if obscured.
                if (shadowObscured) {
                    fieldValue = 10;
                }
            } else if (expectedArg.inputOp == 'text') {
                fieldName = 'TEXT';
                if (shadowObscured) {
                    fieldValue = '';
                }
            } else if (expectedArg.inputOp == 'colour_picker') {
                // Convert SB2 color to hex.
                fieldValue = Color.decimalToHex(providedArg);
                fieldName = 'COLOUR';
                if (shadowObscured) {
                    fieldValue = '#990000';
                }
            }
            var fields = {};
            fields[fieldName] = {
                name: fieldName,
                value: fieldValue
            };
            activeBlock.children.push({
                id: inputUid,
                opcode: expectedArg.inputOp,
                inputs: {},
                fields: fields,
                next: null,
                topLevel: false,
                parent: activeBlock.id,
                shadow: true
            });
            activeBlock.inputs[expectedArg.inputName].shadow = inputUid;
            // If no block occupying the input, alias to the shadow.
            if (!activeBlock.inputs[expectedArg.inputName].block) {
                activeBlock.inputs[expectedArg.inputName].block = inputUid;
            }
        } else if (expectedArg.type == 'field') {
            // Add as a field on this block.
            activeBlock.fields[expectedArg.fieldName] = {
                name: expectedArg.fieldName,
                value: providedArg
            };
        }
    }
    // Special cases to generate mutations.
    if (oldOpcode == 'stopScripts') {
        // Mutation for stop block: if the argument is 'other scripts',
        // the block needs a next connection.
        if (sb2block[1] == 'other scripts in sprite' ||
            sb2block[1] == 'other scripts in stage') {
            activeBlock.mutation = {
                tagName: 'mutation',
                hasnext: 'true',
                children: []
            };
        }
    } else if (oldOpcode == 'procDef') {
        // Mutation for procedure definition:
        // store all 2.0 proc data.
        var procData = sb2block.slice(1);
        activeBlock.mutation = {
            tagName: 'mutation',
            proccode: procData[0], // e.g., "abc %n %b %s"
            argumentnames: JSON.stringify(procData[1]), // e.g. ['arg1', 'arg2']
            argumentdefaults: JSON.stringify(procData[2]), // e.g., [1, 'abc']
            warp: procData[3], // Warp mode, e.g., true/false.
            children: []
        };
    } else if (oldOpcode == 'call') {
        // Mutation for procedure call:
        // string for proc code (e.g., "abc %n %b %s").
        activeBlock.mutation = {
            tagName: 'mutation',
            children: [],
            proccode: sb2block[1]
        };
    } else if (oldOpcode == 'getParam') {
        // Mutation for procedure parameter.
        activeBlock.mutation = {
            tagName: 'mutation',
            children: [],
            paramname: sb2block[1], // Name of parameter.
            shape: sb2block[2] // Shape - in 2.0, 'r' or 'b'.
        };
    }
    return activeBlock;
}

module.exports = sb2import;
<commit_msg>Fix parent setting in sb2 importer (#283)<commit_after>/**
 * @fileoverview
 * Partial implementation of an SB2 JSON importer.
 * Parses provided JSON and then generates all needed
 * scratch-vm runtime structures.
 */

var Blocks = require('../engine/blocks');
var Clone = require('../sprites/clone');
var Sprite = require('../sprites/sprite');
var Color = require('../util/color.js');
var uid = require('../util/uid');
var specMap = require('./sb2specmap');
var Variable = require('../engine/variable');
var List = require('../engine/list');

/**
 * Top-level handler. Parse provided JSON,
 * and process the top-level object (the stage object).
 * @param {!string} json SB2-format JSON to load.
 * @param {!Runtime} runtime Runtime object to load all structures into.
 */
function sb2import (json, runtime) {
    parseScratchObject(
        JSON.parse(json),
        runtime,
        true
    );
}

/**
 * Parse a single "Scratch object" and create all its in-memory VM objects.
 * @param {!Object} object From-JSON "Scratch object:" sprite, stage, watcher.
 * @param {!Runtime} runtime Runtime object to load all structures into.
 * @param {boolean} topLevel Whether this is the top-level object (stage).
 */
function parseScratchObject (object, runtime, topLevel) {
    if (!object.hasOwnProperty('objName')) {
        // Watcher/monitor - skip this object until those are implemented in VM.
        // @todo
        return;
    }
    // Blocks container for this object.
    var blocks = new Blocks();
    // @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
    var sprite = new Sprite(blocks, runtime);
    // Sprite/stage name from JSON.
    if (object.hasOwnProperty('objName')) {
        sprite.name = object.objName;
    }
    // Costumes from JSON.
    if (object.hasOwnProperty('costumes')) {
        for (var i = 0; i < object.costumes.length; i++) {
            var costume = object.costumes[i];
            // @todo: Make sure all the relevant metadata is being pulled out.
            sprite.costumes.push({
                skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/'
                    + costume.baseLayerMD5 + '/get/',
                name: costume.costumeName,
                bitmapResolution: costume.bitmapResolution,
                rotationCenterX: costume.rotationCenterX,
                rotationCenterY: costume.rotationCenterY
            });
        }
    }
    // If included, parse any and all scripts/blocks on the object.
    if (object.hasOwnProperty('scripts')) {
        parseScripts(object.scripts, blocks);
    }
    // Create the first clone, and load its run-state from JSON.
    var target = sprite.createClone();
    // Add it to the runtime's list of targets.
    runtime.targets.push(target);
    // Load target properties from JSON.
    if (object.hasOwnProperty('variables')) {
        for (var j = 0; j < object.variables.length; j++) {
            var variable = object.variables[j];
            target.variables[variable.name] = new Variable(
                variable.name,
                variable.value,
                variable.isPersistent
            );
        }
    }
    if (object.hasOwnProperty('lists')) {
        for (var k = 0; k < object.lists.length; k++) {
            var list = object.lists[k];
            // @todo: monitor properties.
            target.lists[list.listName] = new List(
                list.listName,
                list.contents
            );
        }
    }
    if (object.hasOwnProperty('scratchX')) {
        target.x = object.scratchX;
    }
    if (object.hasOwnProperty('scratchY')) {
        target.y = object.scratchY;
    }
    if (object.hasOwnProperty('direction')) {
        target.direction = object.direction;
    }
    if (object.hasOwnProperty('scale')) {
        // SB2 stores as 1.0 = 100%; we use % in the VM.
        target.size = object.scale * 100;
    }
    if (object.hasOwnProperty('visible')) {
        target.visible = object.visible;
    }
    if (object.hasOwnProperty('currentCostumeIndex')) {
        target.currentCostume = Math.round(object.currentCostumeIndex);
    }
    if (object.hasOwnProperty('rotationStyle')) {
        if (object.rotationStyle == 'none') {
            target.rotationStyle = Clone.ROTATION_STYLE_NONE;
        } else if (object.rotationStyle == 'leftRight') {
            target.rotationStyle = Clone.ROTATION_STYLE_LEFT_RIGHT;
        } else if (object.rotationStyle == 'normal') {
            target.rotationStyle = Clone.ROTATION_STYLE_ALL_AROUND;
        }
    }
    target.isStage = topLevel;
    target.updateAllDrawableProperties();
    // The stage will have child objects; recursively process them.
    if (object.children) {
        for (var m = 0; m < object.children.length; m++) {
            parseScratchObject(object.children[m], runtime, false);
        }
    }
}

/**
 * Parse a Scratch object's scripts into VM blocks.
 * This should only handle top-level scripts that include X, Y coordinates.
 * @param {!Object} scripts Scripts object from SB2 JSON.
 * @param {!Blocks} blocks Blocks object to load parsed blocks into.
 */
function parseScripts (scripts, blocks) {
    for (var i = 0; i < scripts.length; i++) {
        var script = scripts[i];
        var scriptX = script[0];
        var scriptY = script[1];
        var blockList = script[2];
        var parsedBlockList = parseBlockList(blockList);
        if (parsedBlockList[0]) {
            // Adjust script coordinates to account for
            // larger block size in scratch-blocks.
            // @todo: Determine more precisely the right formulas here.
            parsedBlockList[0].x = scriptX * 1.1;
            parsedBlockList[0].y = scriptY * 1.1;
            parsedBlockList[0].topLevel = true;
            parsedBlockList[0].parent = null;
        }
        // Flatten children and create add the blocks.
        var convertedBlocks = flatten(parsedBlockList);
        for (var j = 0; j < convertedBlocks.length; j++) {
            blocks.createBlock(convertedBlocks[j]);
        }
    }
}

/**
 * Parse any list of blocks from SB2 JSON into a list of VM-format blocks.
 * Could be used to parse a top-level script,
 * a list of blocks in a branch (e.g., in forever),
 * or a list of blocks in an argument (e.g., move [pick random...]).
 * @param {Array.<Object>} blockList SB2 JSON-format block list.
 * @return {Array.<Object>} Scratch VM-format block list.
 */
function parseBlockList (blockList) {
    var resultingList = [];
    var previousBlock = null; // For setting next.
    for (var i = 0; i < blockList.length; i++) {
        var block = blockList[i];
        var parsedBlock = parseBlock(block);
        if (previousBlock) {
            parsedBlock.parent = previousBlock.id;
            previousBlock.next = parsedBlock.id;
        }
        previousBlock = parsedBlock;
        resultingList.push(parsedBlock);
    }
    return resultingList;
}

/**
 * Flatten a block tree into a block list.
 * Children are temporarily stored on the `block.children` property.
 * @param {Array.<Object>} blocks list generated by `parseBlockList`.
 * @return {Array.<Object>} Flattened list to be passed to `blocks.createBlock`.
 */
function flatten (blocks) {
    var finalBlocks = [];
    for (var i = 0; i < blocks.length; i++) {
        var block = blocks[i];
        finalBlocks.push(block);
        if (block.children) {
            finalBlocks = finalBlocks.concat(flatten(block.children));
        }
        delete block.children;
    }
    return finalBlocks;
}

/**
 * Convert a Scratch 2.0 procedure string (e.g., "my_procedure %s %b %n")
 * into an argument map. This allows us to provide the expected inputs
 * to a mutated procedure call.
 * @param {string} procCode Scratch 2.0 procedure string.
 * @return {Object} Argument map compatible with those in sb2specmap.
 */
function parseProcedureArgMap (procCode) {
    var argMap = [
        {} // First item in list is op string.
    ];
    var INPUT_PREFIX = 'input';
    var inputCount = 0;
    // Split by %n, %b, %s.
    var parts = procCode.split(/(?=[^\\]\%[nbs])/);
    for (var i = 0; i < parts.length; i++) {
        var part = parts[i].trim();
        if (part.substring(0, 1) == '%') {
            var argType = part.substring(1, 2);
            var arg = {
                type: 'input',
                inputName: INPUT_PREFIX + (inputCount++)
            };
            if (argType == 'n') {
                arg.inputOp = 'math_number';
            } else if (argType == 's') {
                arg.inputOp = 'text';
            }
            argMap.push(arg);
        }
    }
    return argMap;
}

/**
 * Parse a single SB2 JSON-formatted block and its children.
 * @param {!Object} sb2block SB2 JSON-formatted block.
 * @return {Object} Scratch VM format block.
 */
function parseBlock (sb2block) {
    // First item in block object is the old opcode (e.g., 'forward:').
    var oldOpcode = sb2block[0];
    // Convert the block using the specMap. See sb2specmap.js.
    if (!oldOpcode || !specMap[oldOpcode]) {
        console.warn('Couldn\'t find SB2 block: ', oldOpcode);
        return;
    }
    var blockMetadata = specMap[oldOpcode];
    // Block skeleton.
    var activeBlock = {
        id: uid(), // Generate a new block unique ID.
        opcode: blockMetadata.opcode, // Converted, e.g. "motion_movesteps".
        inputs: {}, // Inputs to this block and the blocks they point to.
        fields: {}, // Fields on this block and their values.
        next: null, // Next block.
        shadow: false, // No shadow blocks in an SB2 by default.
        children: [] // Store any generated children, flattened in `flatten`.
    };
    // For a procedure call, generate argument map from proc string.
    if (oldOpcode == 'call') {
        blockMetadata.argMap = parseProcedureArgMap(sb2block[1]);
    }
    // Look at the expected arguments in `blockMetadata.argMap.`
    // The basic problem here is to turn positional SB2 arguments into
    // non-positional named Scratch VM arguments.
    for (var i = 0; i < blockMetadata.argMap.length; i++) {
        var expectedArg = blockMetadata.argMap[i];
        var providedArg = sb2block[i + 1]; // (i = 0 is opcode)
        // Whether the input is obscuring a shadow.
        var shadowObscured = false;
        // Positional argument is an input.
        if (expectedArg.type == 'input') {
            // Create a new block and input metadata.
            var inputUid = uid();
            activeBlock.inputs[expectedArg.inputName] = {
                name: expectedArg.inputName,
                block: null,
                shadow: null
            };
            if (typeof providedArg == 'object' && providedArg) {
                // Block or block list occupies the input.
                var innerBlocks;
                if (typeof providedArg[0] == 'object' && providedArg[0]) {
                    // Block list occupies the input.
                    innerBlocks = parseBlockList(providedArg);
                } else {
                    // Single block occupies the input.
                    innerBlocks = [parseBlock(providedArg)];
                }
                var previousBlock = null;
                for (var j = 0; j < innerBlocks.length; j++) {
                    if (j == 0) {
                        innerBlocks[j].parent = activeBlock.id;
                    } else {
                        innerBlocks[j].parent = previousBlock;
                    }
                    previousBlock = innerBlocks[j].id;
                }
                // Obscures any shadow.
                shadowObscured = true;
                activeBlock.inputs[expectedArg.inputName].block = (
                    innerBlocks[0].id
                );
                activeBlock.children = (
                    activeBlock.children.concat(innerBlocks)
                );
            }
            // Generate a shadow block to occupy the input.
            if (!expectedArg.inputOp) {
                // No editable shadow input; e.g., for a boolean.
                continue;
            }
            // Each shadow has a field generated for it automatically.
            // Value to be filled in the field.
            var fieldValue = providedArg;
            // Shadows' field names match the input name, except for these:
            var fieldName = expectedArg.inputName;
            if (expectedArg.inputOp == 'math_number' ||
                expectedArg.inputOp == 'math_whole_number' ||
                expectedArg.inputOp == 'math_positive_number' ||
                expectedArg.inputOp == 'math_integer' ||
                expectedArg.inputOp == 'math_angle') {
                fieldName = 'NUM';
                // Fields are given Scratch 2.0 default values if obscured.
                if (shadowObscured) {
                    fieldValue = 10;
                }
            } else if (expectedArg.inputOp == 'text') {
                fieldName = 'TEXT';
                if (shadowObscured) {
                    fieldValue = '';
                }
            } else if (expectedArg.inputOp == 'colour_picker') {
                // Convert SB2 color to hex.
                fieldValue = Color.decimalToHex(providedArg);
                fieldName = 'COLOUR';
                if (shadowObscured) {
                    fieldValue = '#990000';
                }
            }
            var fields = {};
            fields[fieldName] = {
                name: fieldName,
                value: fieldValue
            };
            activeBlock.children.push({
                id: inputUid,
                opcode: expectedArg.inputOp,
                inputs: {},
                fields: fields,
                next: null,
                topLevel: false,
                parent: activeBlock.id,
                shadow: true
            });
            activeBlock.inputs[expectedArg.inputName].shadow = inputUid;
            // If no block occupying the input, alias to the shadow.
            if (!activeBlock.inputs[expectedArg.inputName].block) {
                activeBlock.inputs[expectedArg.inputName].block = inputUid;
            }
        } else if (expectedArg.type == 'field') {
            // Add as a field on this block.
            activeBlock.fields[expectedArg.fieldName] = {
                name: expectedArg.fieldName,
                value: providedArg
            };
        }
    }
    // Special cases to generate mutations.
    if (oldOpcode == 'stopScripts') {
        // Mutation for stop block: if the argument is 'other scripts',
        // the block needs a next connection.
        if (sb2block[1] == 'other scripts in sprite' ||
            sb2block[1] == 'other scripts in stage') {
            activeBlock.mutation = {
                tagName: 'mutation',
                hasnext: 'true',
                children: []
            };
        }
    } else if (oldOpcode == 'procDef') {
        // Mutation for procedure definition:
        // store all 2.0 proc data.
        var procData = sb2block.slice(1);
        activeBlock.mutation = {
            tagName: 'mutation',
            proccode: procData[0], // e.g., "abc %n %b %s"
            argumentnames: JSON.stringify(procData[1]), // e.g. ['arg1', 'arg2']
            argumentdefaults: JSON.stringify(procData[2]), // e.g., [1, 'abc']
            warp: procData[3], // Warp mode, e.g., true/false.
            children: []
        };
    } else if (oldOpcode == 'call') {
        // Mutation for procedure call:
        // string for proc code (e.g., "abc %n %b %s").
        activeBlock.mutation = {
            tagName: 'mutation',
            children: [],
            proccode: sb2block[1]
        };
    } else if (oldOpcode == 'getParam') {
        // Mutation for procedure parameter.
        activeBlock.mutation = {
            tagName: 'mutation',
            children: [],
            paramname: sb2block[1], // Name of parameter.
            shape: sb2block[2] // Shape - in 2.0, 'r' or 'b'.
        };
    }
    return activeBlock;
}

module.exports = sb2import;
<|endoftext|>


<commit_before>(function($, Proud) {

  var $body = $('body');

  Proud.behaviors.iconpicker = {
    reRunAjax: true, // runs the code below after a panel loads
    attach: function(context, settings) {

    /* testing var for icons
    var icons = [ 'fa-regular fa-address-card', 'fa-brands fa-airbnb'];
    */

    $('input.iconpicker').once('fontIconPicker', function(){
         $(this).fontIconPicker({
          source: ProudFA.icons
        });
    });

  }};

  Proud.behaviors.draggableCheckboxes = {
    reRunAjax: true,
    attach: function(context, settings) {
      // Init Dragula
      $('[data-draggable-checkboxes]').once('drag-checkbox', function() {
        dragula([this], {
          moves: function (el, container, handle) {
            return handle.className.indexOf('handle') > 0;
          }
        });
      });
    }
  };

  Proud.behaviors.draggableGroups = {
    reRunAjax: true,
    attach: function(context, settings) {
      // Init drag
      $('[data-draggable-group]').once('panelsopen', function() {
        var that = this;
        dragula([that], {
          moves: function (el, container, handle) {
            return handle.className.indexOf('handle') > 0;
          }
        }).on('drop', function (el) {
          Proud.behaviors.groups.recalulateWeight($(that));
        });
      });
    }
  };

  Proud.behaviors.groups = {
    attach: function(context, settings) {
      // Add row button
      $body.on('click', '.group-add-row', function(e) {
        e.preventDefault();
        var draggable = $(e.target).siblings('[data-draggable-group]'),
            count = draggable.children().length,
            template = draggable.data('json_template')
                        .replace(/GROUP\_REPLACE\_KEY/g, count)
                        .replace(/GROUP\_REPLACE\_TITLE/g, count + 1);
        draggable.append(template);
      });

      // Delete row button
      $body.on('click', '.group-delete-row', function(e) {
        e.preventDefault();
        // Item being deleted
        var item = $(e.target).closest('div.panel');
        item.remove();
        // Recaculate weights
        Proud.behaviors.groups.recalulateWeight(item.parent());
      });
    }
  };

  Proud.behaviors.groups.recalulateWeight = function($draggable) {
    $.each($draggable.children(), function(id, child) {
      $($.find('input.group-weight', child)).val(id);
    });
  }

})(jQuery, Proud);<commit_msg>adding custom theme to the icon picker so I can target with styles<commit_after>(function($, Proud) {

  var $body = $('body');

  Proud.behaviors.iconpicker = {
    reRunAjax: true, // runs the code below after a panel loads
    attach: function(context, settings) {

    /* testing var for icons
    var icons = [ 'fa-regular fa-address-card', 'fa-brands fa-airbnb'];
    */

    $('input.iconpicker').once('fontIconPicker', function(){
         $(this).fontIconPicker({
          theme: 'proudcity-iconpicker',
          source: ProudFA.icons
        });
    });

  }};

  Proud.behaviors.draggableCheckboxes = {
    reRunAjax: true,
    attach: function(context, settings) {
      // Init Dragula
      $('[data-draggable-checkboxes]').once('drag-checkbox', function() {
        dragula([this], {
          moves: function (el, container, handle) {
            return handle.className.indexOf('handle') > 0;
          }
        });
      });
    }
  };

  Proud.behaviors.draggableGroups = {
    reRunAjax: true,
    attach: function(context, settings) {
      // Init drag
      $('[data-draggable-group]').once('panelsopen', function() {
        var that = this;
        dragula([that], {
          moves: function (el, container, handle) {
            return handle.className.indexOf('handle') > 0;
          }
        }).on('drop', function (el) {
          Proud.behaviors.groups.recalulateWeight($(that));
        });
      });
    }
  };

  Proud.behaviors.groups = {
    attach: function(context, settings) {
      // Add row button
      $body.on('click', '.group-add-row', function(e) {
        e.preventDefault();
        var draggable = $(e.target).siblings('[data-draggable-group]'),
            count = draggable.children().length,
            template = draggable.data('json_template')
                        .replace(/GROUP\_REPLACE\_KEY/g, count)
                        .replace(/GROUP\_REPLACE\_TITLE/g, count + 1);
        draggable.append(template);
      });

      // Delete row button
      $body.on('click', '.group-delete-row', function(e) {
        e.preventDefault();
        // Item being deleted
        var item = $(e.target).closest('div.panel');
        item.remove();
        // Recaculate weights
        Proud.behaviors.groups.recalulateWeight(item.parent());
      });
    }
  };

  Proud.behaviors.groups.recalulateWeight = function($draggable) {
    $.each($draggable.children(), function(id, child) {
      $($.find('input.group-weight', child)).val(id);
    });
  }

})(jQuery, Proud);<|endoftext|>


<commit_before>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, typeIn, render, setupOnerror } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

const FAKE_DATA = [
  { name: 'Batman', characters: 'Bruce Wayne' },
  { name: 'Superman', characters: 'Kal-El' },
  { name: 'Green Arrow', characters: 'Oliver Queen' },
  { name: 'Wonder Woman', characters: 'Princess Diana' },
  { name: 'Martian Manhunter', characters: 'Martian Manhunter' },
  { name: 'Robin/Nightwing', characters: 'Dick Grayson' },
  { name: 'Spider Man', characters: 'Peter Parker' },
  { name: 'Iron Man', characters: 'Tony Stark' },
  { name: 'Thor', characters: 'Thor Odinson' },
  { name: 'Hulk', characters: 'Bruce Banner' },
  { name: 'Wolverine', characters: 'James Howlett' }
];

module('Integration | Component | o-s-s/infinite-select', function (hooks) {
  setupRenderingTest(hooks);

  module('search is enabled', function () {
    module('no onSearch hook has been passed', function () {
      test('should throw an error', async function (assert) {
        setupOnerror((err) => {
          assert.equal(
            err.message,
            '[component][OSS::InfiniteSelect] Search is enabled without an `onSearch` action being passed'
          );
        });

        await render(hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{true}} />`);
      });
    });

    module('with onSearch hook', function () {
      test('it calls the onSearch hook with the typed keyword', async function (assert) {
        this.onSearch = (keyword) => {
          assert.equal(keyword, 'b', 'Enters the onSearch hook');
        };

        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect
                @items={{this.items}} @searchEnabled={{true}} @onSearch={{this.onSearch}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select input.upf-input').exists();
        await typeIn('.upf-infinite-select input.upf-input', 'b');
      });

      test('it uses the passed placeholder when present', async function (assert) {
        this.onSearch = () => {};
        this.onSelect = () => {};

        await render(
          hbs`
            <OSS::InfiniteSelect
              @items={{this.items}} @searchEnabled={{true}} @onSearch={{this.onSearch}} @searchPlaceholder="Foobar"
              @onSelect={{this.onSelect}}/>
          `
        );

        assert.dom('.upf-infinite-select input.upf-input').exists();
        assert.dom('.upf-infinite-select input.upf-input').hasAttribute('placeholder', 'Foobar');
      });
    });
  });

  module('item selection', function () {
    module('onSelect is not passed', function () {
      test('should throw an error', async function (assert) {
        setupOnerror((err) => {
          assert.equal(err.message, '[component][OSS::InfiniteSelect] `onSelect` action is mandatory');
        });

        await render(hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} />`);
      });
    });

    module('onSelect is passed', function () {
      test('it calls the onSelect hook with the clicked item', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = (item) => {
          assert.equal(item.name, 'Batman');
        };

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        await click('.upf-infinite-select__items-container .upf-infinite-select__item:first-child');
      });
    });
  });

  module('loading states', function () {
    module('full content loading', function () {
      test('should display rows of skeleton loading in place of the content', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @loading={{true}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select__items-container .upf-infinite-select__item').doesNotExist();
        assert.dom('.upf-infinite-select__items-container .upf-skeleton-effect').exists({ count: 5 });
      });
    });

    module('additional content loading', function () {
      test('should display rows of skeleton loading after the existing content', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @loadingMore={{true}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert
          .dom('.upf-infinite-select__items-container .upf-infinite-select__item')
          .exists({ count: FAKE_DATA.length });
        assert.dom('.upf-infinite-select__items-container .upf-skeleton-effect').exists({ count: 3 });
      });
    });
  });

  module('it renders', function () {
    module('with itemLabel', function () {
      test('it uses the passed itemLabel argument as display key', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @itemLabel="characters" @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert
          .dom('.upf-infinite-select__items-container .upf-infinite-select__item')
          .exists({ count: FAKE_DATA.length });

        let displayedNames = [];
        document.querySelectorAll('.upf-infinite-select__items-container .upf-infinite-select__item').forEach((x) => {
          displayedNames.push(x.textContent.trim());
        });

        assert.deepEqual(
          displayedNames,
          FAKE_DATA.map((x) => x.characters)
        );
      });
    });

    module('with option block', function () {
      test('it uses the passed block to display options', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`
            <OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}>
              <:option as |opt|>
                Comics - {{opt.name}}
              </:option>
            </OSS::InfiniteSelect>
          `
        );

        assert
          .dom('.upf-infinite-select__items-container .upf-infinite-select__item')
          .exists({ count: FAKE_DATA.length });

        let displayedNames = [];
        document.querySelectorAll('.upf-infinite-select__items-container .upf-infinite-select__item').forEach((x) => {
          displayedNames.push(x.textContent.trim());
        });

        assert.deepEqual(
          displayedNames,
          FAKE_DATA.map((x) => `Comics - ${x.name}`)
        );
      });
    });

    module('with empty items', function () {
      test('it should render the empty state', async function (assert) {
        this.items = [];
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select__items-container').hasClass('upf-infinite-select__items-container--empty');
        assert
          .dom('.upf-infinite-select__items-container img')
          .hasAttribute('src', '/@upfluence/oss-components/assets/images/empty-state-skeleton.png');
      });
    });

    module('with inline', function () {
      test('it should render with undefined arg', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select.upf-infinite-select--absolute').exists({ count: 1 });
      });
      test('it should render with falsy arg', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @inline={{false}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select.upf-infinite-select--absolute').exists({ count: 1 });
      });
      test('it should render with truly arg', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @inline={{true}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select').exists({ count: 1 });
        assert.dom('.upf-infinite-select--absolute').doesNotExist();
      });
    });
  });
});
<commit_msg>PR feedback - wording on tests<commit_after>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, typeIn, render, setupOnerror } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

const FAKE_DATA = [
  { name: 'Batman', characters: 'Bruce Wayne' },
  { name: 'Superman', characters: 'Kal-El' },
  { name: 'Green Arrow', characters: 'Oliver Queen' },
  { name: 'Wonder Woman', characters: 'Princess Diana' },
  { name: 'Martian Manhunter', characters: 'Martian Manhunter' },
  { name: 'Robin/Nightwing', characters: 'Dick Grayson' },
  { name: 'Spider Man', characters: 'Peter Parker' },
  { name: 'Iron Man', characters: 'Tony Stark' },
  { name: 'Thor', characters: 'Thor Odinson' },
  { name: 'Hulk', characters: 'Bruce Banner' },
  { name: 'Wolverine', characters: 'James Howlett' }
];

module('Integration | Component | o-s-s/infinite-select', function (hooks) {
  setupRenderingTest(hooks);

  module('search is enabled', function () {
    module('no onSearch hook has been passed', function () {
      test('should throw an error', async function (assert) {
        setupOnerror((err) => {
          assert.equal(
            err.message,
            '[component][OSS::InfiniteSelect] Search is enabled without an `onSearch` action being passed'
          );
        });

        await render(hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{true}} />`);
      });
    });

    module('with onSearch hook', function () {
      test('it calls the onSearch hook with the typed keyword', async function (assert) {
        this.onSearch = (keyword) => {
          assert.equal(keyword, 'b', 'Enters the onSearch hook');
        };

        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect
                @items={{this.items}} @searchEnabled={{true}} @onSearch={{this.onSearch}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select input.upf-input').exists();
        await typeIn('.upf-infinite-select input.upf-input', 'b');
      });

      test('it uses the passed placeholder when present', async function (assert) {
        this.onSearch = () => {};
        this.onSelect = () => {};

        await render(
          hbs`
            <OSS::InfiniteSelect
              @items={{this.items}} @searchEnabled={{true}} @onSearch={{this.onSearch}} @searchPlaceholder="Foobar"
              @onSelect={{this.onSelect}}/>
          `
        );

        assert.dom('.upf-infinite-select input.upf-input').exists();
        assert.dom('.upf-infinite-select input.upf-input').hasAttribute('placeholder', 'Foobar');
      });
    });
  });

  module('item selection', function () {
    module('onSelect is not passed', function () {
      test('should throw an error', async function (assert) {
        setupOnerror((err) => {
          assert.equal(err.message, '[component][OSS::InfiniteSelect] `onSelect` action is mandatory');
        });

        await render(hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} />`);
      });
    });

    module('onSelect is passed', function () {
      test('it calls the onSelect hook with the clicked item', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = (item) => {
          assert.equal(item.name, 'Batman');
        };

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        await click('.upf-infinite-select__items-container .upf-infinite-select__item:first-child');
      });
    });
  });

  module('loading states', function () {
    module('full content loading', function () {
      test('should display rows of skeleton loading in place of the content', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @loading={{true}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select__items-container .upf-infinite-select__item').doesNotExist();
        assert.dom('.upf-infinite-select__items-container .upf-skeleton-effect').exists({ count: 5 });
      });
    });

    module('additional content loading', function () {
      test('should display rows of skeleton loading after the existing content', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @loadingMore={{true}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert
          .dom('.upf-infinite-select__items-container .upf-infinite-select__item')
          .exists({ count: FAKE_DATA.length });
        assert.dom('.upf-infinite-select__items-container .upf-skeleton-effect').exists({ count: 3 });
      });
    });
  });

  module('it renders', function () {
    module('with itemLabel', function () {
      test('it uses the passed itemLabel argument as display key', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @itemLabel="characters" @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert
          .dom('.upf-infinite-select__items-container .upf-infinite-select__item')
          .exists({ count: FAKE_DATA.length });

        let displayedNames = [];
        document.querySelectorAll('.upf-infinite-select__items-container .upf-infinite-select__item').forEach((x) => {
          displayedNames.push(x.textContent.trim());
        });

        assert.deepEqual(
          displayedNames,
          FAKE_DATA.map((x) => x.characters)
        );
      });
    });

    module('with option block', function () {
      test('it uses the passed block to display options', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`
            <OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}>
              <:option as |opt|>
                Comics - {{opt.name}}
              </:option>
            </OSS::InfiniteSelect>
          `
        );

        assert
          .dom('.upf-infinite-select__items-container .upf-infinite-select__item')
          .exists({ count: FAKE_DATA.length });

        let displayedNames = [];
        document.querySelectorAll('.upf-infinite-select__items-container .upf-infinite-select__item').forEach((x) => {
          displayedNames.push(x.textContent.trim());
        });

        assert.deepEqual(
          displayedNames,
          FAKE_DATA.map((x) => `Comics - ${x.name}`)
        );
      });
    });

    module('with empty items', function () {
      test('it should render the empty state', async function (assert) {
        this.items = [];
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select__items-container').hasClass('upf-infinite-select__items-container--empty');
        assert
          .dom('.upf-infinite-select__items-container img')
          .hasAttribute('src', '/@upfluence/oss-components/assets/images/empty-state-skeleton.png');
      });
    });

    module('with inline', function () {
      test('it should render with undefined arg', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select.upf-infinite-select--absolute').exists({ count: 1 });
      });
      test('it should render with falsy arg', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @inline={{false}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select.upf-infinite-select--absolute').exists({ count: 1 });
      });
      test('it should render with truthy arg', async function (assert) {
        this.items = FAKE_DATA;
        this.onSelect = () => {};

        await render(
          hbs`<OSS::InfiniteSelect @items={{this.items}} @inline={{true}} @searchEnabled={{false}} @onSelect={{this.onSelect}}/>`
        );

        assert.dom('.upf-infinite-select').exists({ count: 1 });
        assert.dom('.upf-infinite-select--absolute').doesNotExist();
      });
    });
  });
});
<|endoftext|>


<commit_before>var test = require('tap').test,
    join = require('path').join,
    log = require('../lib/log'),
    fn = require('../lib/reporter');

log.pause();

test('zzz', function(t) {

    function cb(err, msg) {
        t.equal(err, 'Total of 1 error found in 1 file.');
        t.equal(msg, undefined);
    }

    function print(str) {
        t.equal(str.slice(0, 13), 'JSLint Report');
    }

    var env = {opts:{print:true}, cwd:__dirname},
        rep = fn(env, print, cb),
        offenses = {
            'lib/foo.js': [{
                line: 41,
                col: 1,
                msg: "Expected 'bar' at column 9, not column 1.",
                evidence: 'bar(errors);'
            }]
        };

    t.plan(3);
    rep(offenses, cb, 'ohhai');
});

test('yyy', function(t) {

    function cb(err, msg) {
        t.equal(err, 'Total of 2 errors found in 1 file.');
        t.equal(msg, undefined);
    }

    var env = {opts:{}, cwd:__dirname},
        rep = fn(env, function(){}, cb),
        offenses = {
            'lib/foo.js': [
                {
                    line: 41,
                    col: 1,
                    msg: "Expected 'bar' at column 9, not column 1.",
                    evidence: 'bar();'
                },
                {
                    line: 55,
                    col: 1,
                    msg: "Expected 'zoo' at column 9, not column 1.",
                    evidence: 'zoo();'
                }
            ],
            'lib/bar.js': [
                {
                    line: 41,
                    col: 1,
                    msg: "Expected 'bar' at column 9, not column 1.",
                    evidence: 'bar();'
                }
            ],
        };

    t.plan(2);
    rep(offenses, 'ohhai');
});

test('reporter gets an error string', function(t) {

    function cb(err, msg) {
        t.equal(err, 'ohnoes.');
        t.equal(msg, undefined);
    }

    var env = {opts:{}, cwd:__dirname},
        rep = fn(env, function(){}, cb),
        offenses = 'ohnoes.';

    t.plan(2);
    rep(offenses, 'ohhai');
});
<commit_msg>update test<commit_after>var test = require('tap').test,
    join = require('path').join,
    log = require('../lib/log'),
    fn = require('../lib/reporter');

log.pause();

test('zzz', function(t) {

    function cb(err, msg) {
        t.equal(err, 'Total of 1 error found in 1 file.');
        t.equal(msg, undefined);
    }

    function print(str) {
        t.equal(str.slice(0, 13), 'JSLint Report');
    }

    var env = {opts:{print:true}, cwd:__dirname},
        rep = fn(env, print, cb),
        offenses = {
            'lib/foo.js': [{
                line: 41,
                col: 1,
                msg: "Expected 'bar' at column 9, not column 1.",
                evidence: 'bar(errors);'
            }]
        };

    t.plan(3);
    rep(offenses, cb, 'ohhai');
});

test('yyy', function(t) {

    function cb(err, msg) {
        t.equal(err, 'Total of 3 errors found in 2 files.');
        t.equal(msg, undefined);
    }

    var env = {opts:{}, cwd:__dirname},
        rep = fn(env, function(){}, cb),
        offenses = {
            'lib/foo.js': [
                {
                    line: 41,
                    col: 1,
                    msg: "Expected 'bar' at column 9, not column 1.",
                    evidence: 'bar();'
                },
                {
                    line: 55,
                    col: 1,
                    msg: "Expected 'zoo' at column 9, not column 1.",
                    evidence: 'zoo();'
                }
            ],
            'lib/bar.js': [
                {
                    line: 41,
                    col: 1,
                    msg: "Expected 'bar' at column 9, not column 1.",
                    evidence: 'bar();'
                }
            ],
        };

    t.plan(2);
    rep(offenses, 'ohhai');
});

test('reporter gets an error string', function(t) {

    function cb(err, msg) {
        t.equal(err, 'ohnoes.');
        t.equal(msg, undefined);
    }

    var env = {opts:{}, cwd:__dirname},
        rep = fn(env, function(){}, cb),
        offenses = 'ohnoes.';

    t.plan(2);
    rep(offenses, 'ohhai');
});
<|endoftext|>


<commit_before>"use strict";

var tester = require('./_lib/tester');

exports.examples = tester([
	{
		rules: 'not',
		value: 33,
		error: 'RNOT1'
	},
	{
		rules: 'not hello',
		value: 33,
		error: 'R8'
	},
	{
		rules: 'not format ^3$',
		value: 33,
		expect: true
	},
	{
		rules: 'not format ^3$',
		value: 3,
		expect: false
	},
	{
		rules: 'not type string',
		value: "",
		expect: false
	},
	{
		rules: 'not type string',
		value: null,
		expect: true
	},
	{
		rules: 'not exact_length 3',
		value: "",
		expect: true
	},
	{
		rules: 'not exact_length 3',
		value: "111",
		expect: false
	}
]);
<commit_msg>add error check for rule-not test<commit_after>"use strict";

var tester = require('./_lib/tester');

exports.examples = tester([
	{
		rules: 'not',
		value: 33,
		error: 'RNOT1'
	},
	{
		rules: 'not hello',
		value: 33,
		error: 'R8'
	},
	{
		rules: 'not format ^3$',
		value: 33,
		expect: true
	},
	{
		rules: 'not format ^3$',
		value: 3,
		verr: {
			rule: 'not',
			params: [{format: '^3$'}],
			index: null
		}
	},
	{
		rules: 'not type string',
		value: "",
		verr: {
			rule: 'not',
			params: [{type: 'string'}],
			index: null
		}
	},
	{
		rules: 'not type string',
		value: null,
		expect: true
	},
	{
		rules: 'not exact_length 3',
		value: "",
		expect: true
	},
	{
		rules: 'not exact_length 3',
		value: "111",
		verr: {
			rule: 'not',
			params: [{exact_length: 3}],
			index: null
		}
	}
]);
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Methods for dragging a workspace visually.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

goog.module('Blockly.WorkspaceDragger');
goog.module.declareLegacyNamespace();

goog.require('Blockly.utils.Coordinate');

goog.requireType('Blockly.WorkspaceSvg');

/**
 * Class for a workspace dragger.  It moves the workspace around when it is
 * being dragged by a mouse or touch.
 * Note that the workspace itself manages whether or not it has a drag surface
 * and how to do translations based on that.  This simply passes the right
 * commands based on events.
 * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag.
 * @constructor
 */
const WorkspaceDragger = function(workspace) {
  /**
   * @type {!Blockly.WorkspaceSvg}
   * @private
   */
  this.workspace_ = workspace;

  /**
   * Whether horizontal scroll is enabled.
   * @type {boolean}
   * @private
   */
  this.horizontalScrollEnabled_ = this.workspace_.isMovableHorizontally();

  /**
   * Whether vertical scroll is enabled.
   * @type {boolean}
   * @private
   */
  this.verticalScrollEnabled_ = this.workspace_.isMovableVertically();

  /**
   * The scroll position of the workspace at the beginning of the drag.
   * Coordinate system: pixel coordinates.
   * @type {!Blockly.utils.Coordinate}
   * @protected
   */
  this.startScrollXY_ = new Blockly.utils.Coordinate(
      workspace.scrollX, workspace.scrollY);
};

/**
 * Sever all links from this object.
 * @package
 * @suppress {checkTypes}
 */
WorkspaceDragger.prototype.dispose = function() {
  this.workspace_ = null;
};

/**
 * Start dragging the workspace.
 * @package
 */
WorkspaceDragger.prototype.startDrag = function() {
  if (Blockly.selected) {
    Blockly.selected.unselect();
  }
  this.workspace_.setupDragSurface();
};

/**
 * Finish dragging the workspace and put everything back where it belongs.
 * @param {!Blockly.utils.Coordinate} currentDragDeltaXY How far the pointer has
 *     moved from the position at the start of the drag, in pixel coordinates.
 * @package
 */
WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) {
  // Make sure everything is up to date.
  this.drag(currentDragDeltaXY);
  this.workspace_.resetDragSurface();
};

/**
 * Move the workspace based on the most recent mouse movements.
 * @param {!Blockly.utils.Coordinate} currentDragDeltaXY How far the pointer has
 *     moved from the position at the start of the drag, in pixel coordinates.
 * @package
 */
WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) {
  const newXY = Blockly.utils.Coordinate.sum(this.startScrollXY_, currentDragDeltaXY);

  if (this.horizontalScrollEnabled_ && this.verticalScrollEnabled_) {
    this.workspace_.scroll(newXY.x, newXY.y);
  } else if (this.horizontalScrollEnabled_) {
    this.workspace_.scroll(newXY.x, this.workspace_.scrollY);
  } else if (this.verticalScrollEnabled_) {
    this.workspace_.scroll(this.workspace_.scrollX, newXY.y);
  } else {
    throw new TypeError('Invalid state.');
  }
};

exports = WorkspaceDragger;
<commit_msg>Migrate core/workspace_dragger.js named requires<commit_after>/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Methods for dragging a workspace visually.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

goog.module('Blockly.WorkspaceDragger');
goog.module.declareLegacyNamespace();

// TODO(#5073): Add Blockly require after fixing circular dependency.
// goog.require('Blockly');
const Coordinate = goog.require('Blockly.utils.Coordinate');
/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');

/**
 * Class for a workspace dragger.  It moves the workspace around when it is
 * being dragged by a mouse or touch.
 * Note that the workspace itself manages whether or not it has a drag surface
 * and how to do translations based on that.  This simply passes the right
 * commands based on events.
 * @param {!WorkspaceSvg} workspace The workspace to drag.
 * @constructor
 */
const WorkspaceDragger = function(workspace) {
  /**
   * @type {!WorkspaceSvg}
   * @private
   */
  this.workspace_ = workspace;

  /**
   * Whether horizontal scroll is enabled.
   * @type {boolean}
   * @private
   */
  this.horizontalScrollEnabled_ = this.workspace_.isMovableHorizontally();

  /**
   * Whether vertical scroll is enabled.
   * @type {boolean}
   * @private
   */
  this.verticalScrollEnabled_ = this.workspace_.isMovableVertically();

  /**
   * The scroll position of the workspace at the beginning of the drag.
   * Coordinate system: pixel coordinates.
   * @type {!Coordinate}
   * @protected
   */
  this.startScrollXY_ = new Coordinate(
      workspace.scrollX, workspace.scrollY);
};

/**
 * Sever all links from this object.
 * @package
 * @suppress {checkTypes}
 */
WorkspaceDragger.prototype.dispose = function() {
  this.workspace_ = null;
};

/**
 * Start dragging the workspace.
 * @package
 */
WorkspaceDragger.prototype.startDrag = function() {
  if (Blockly.selected) {
    Blockly.selected.unselect();
  }
  this.workspace_.setupDragSurface();
};

/**
 * Finish dragging the workspace and put everything back where it belongs.
 * @param {!Coordinate} currentDragDeltaXY How far the pointer has
 *     moved from the position at the start of the drag, in pixel coordinates.
 * @package
 */
WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) {
  // Make sure everything is up to date.
  this.drag(currentDragDeltaXY);
  this.workspace_.resetDragSurface();
};

/**
 * Move the workspace based on the most recent mouse movements.
 * @param {!Coordinate} currentDragDeltaXY How far the pointer has
 *     moved from the position at the start of the drag, in pixel coordinates.
 * @package
 */
WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) {
  const newXY = Coordinate.sum(this.startScrollXY_, currentDragDeltaXY);

  if (this.horizontalScrollEnabled_ && this.verticalScrollEnabled_) {
    this.workspace_.scroll(newXY.x, newXY.y);
  } else if (this.horizontalScrollEnabled_) {
    this.workspace_.scroll(newXY.x, this.workspace_.scrollY);
  } else if (this.verticalScrollEnabled_) {
    this.workspace_.scroll(this.workspace_.scrollX, newXY.y);
  } else {
    throw new TypeError('Invalid state.');
  }
};

exports = WorkspaceDragger;
<|endoftext|>


<commit_before>var fs = require("fs");
var net = require("net");
require.paths.push(__dirname + "/../Common/node");
process.env["NODE_PATH"]=__dirname + "/../Common/node"; // for spawn'd nodelings
var lconfig = require("lconfig");
lconfig.load("Config/config.json");
var lconsole = require("lconsole");
var wrench = require("wrench");
var runIntegration = true;
var integrationOnly = false;

var runFiles = [];
var runGroups = [];

function writeLogLine() {
    fs.writeSync(logFd, "[" + (new Date()).toLocaleString() + "][" + console.outputModule + "] " + Array.prototype.slice.call(arguments).toString() + "\n");
}

// We're going to replace the logging here so we can have it all and show it later
console.log = writeLogLine;
console.warn = writeLogLine;
console.error = writeLogLine;

// Cleanup the old runs Me dir and then copy the stub in
try {
    wrench.rmdirSyncRecursive(lconfig.me);
} catch (E) {
    if (E.code != "ENOENT") {
        process.stderr.write("Error: " + E + "\n");
        process.exit(1);
    }
}
wrench.copyDirSyncRecursive(lconfig.me + ".tests", lconfig.me);

// Cleanup the old runs ijodtest dir
try {
    wrench.rmdirSyncRecursive("ijodtest");
} catch (E) {
    if (E.code != "ENOENT") {
        process.stderr.write("Error: " + E + "\n");
        process.exit(1);
    }
}

// Ladies and gentlemen, get your logs ready
var logFd = fs.openSync("locker.log", "w+");

// If we have args they can be either files or groups
if (process.argv.length > 2) {
    // It's nice to be helpful
    if (process.argv[2] == "-h" || process.argv[2] == "-?") {
        process.stdout.write("Usage: runTests [-l <group name>][-f] [files or groups to run]\n");
        process.stdout.write("  -h  You found me!\n");
        process.stdout.write("  -s  Suppress the output from test running\n");
        process.stdout.write("  -d  Use the dot matrix style reporter\n");
        process.stdout.write("  -nc Disable colors\n");
        process.stdout.write("  -u  Only run the unit tests, skip the front end tests\n");
        process.stdout.write("  -c  Only run the front end tests, skip the unit tests\n");
        process.stdout.write("  -l  List all of the available groups when no group is given or\n");
        process.stdout.write("      all of the files ran in a group.\n");
        process.stdout.write("  -f  The remaining arguments are treated as files to run\n");
        process.stdout.write("  -x  Output tests in xUnit format for CI reporting\n");
        process.stdout.write("\n");
        process.stdout.write("The list of groups are loaded from the Config/config.json and by default all\n");
        process.stdout.write("of them are ran.  A list of the groups to run can be specified as an\n");
        process.stdout.write("argument to the script.  If -f is used the list of arguments are\n");
        process.stdout.write("treated as individual files, not groups.\n");
        process.exit(0);
    }

    if (process.argv[2] == "-l") {
        var testGroups = JSON.parse(fs.readFileSync("Config/config.json")).testGroups;
        if (process.argv.length > 3) {
            if (testGroups.hasOwnProperty(process.argv[3])) {
                process.stdout.write("Files in group " + process.argv[3] + ":\n");
                for (var x = 0; x < testGroups[process.argv[3]].length; ++x) {
                    process.stdout.write("\t" + testGroups[process.argv[3]][x] + "\n");
                }
            } else {
                process.stdout.write(process.argv[3] + " is not a valid group.\n");
                process.exit(0);
            }
        } else {
            process.stdout.write("Available groups:\n");
            for (var key in testGroups) {
                if (testGroups.hasOwnProperty(key)) process.stdout.write("\t" + key + "\n");
            }
        }
        process.exit(0);
    }
    if (process.argv[2] == "-f") {
        // Get the files to run
        for (var x = 3; x < process.argv.length; ++x) {
            runFiles.push(process.argv[x]);
        }
    } else {
        // We'll process the groups later into indivdual files
        for (var x = 2; x < process.argv.length; ++x) {
            if (process.argv[x][0] != "-") runGroups.push(process.argv[x]);
        }
    }
}

// If they have specified any groups or defaulting to all we need to process this
if (runGroups.length > 0 || (runFiles.length === 0 && runGroups.length === 0)) {
    var testGroups = JSON.parse(fs.readFileSync("Config/config.json")).testGroups;
    if (runGroups.length === 0) {
        for (var key in testGroups) {
            if (testGroups.hasOwnProperty(key)) {
                runGroups.push(key);
            }
        }
    }
    runGroups.forEach(function(key) {
        if (testGroups.hasOwnProperty(key)) {
            runFiles = runFiles.concat(testGroups[key]);
        } else {
            process.stderr.write("Invalid test group " + key + ", skipping.\n");
        }
    });
}

if (runFiles.length === 0) {
    process.stderr.write("No tests were specified.\n");
    process.exit(1);
}

try {
    var lockerd = require(__dirname + "/../_lockerd.js");
} catch (E) {
    console.error("Locker did not start correctly, got the error: " + E);
    process.exit(1);
}

var checkLocker = function() {
    if (lockerd.alive === true) {
        runTests();
    } else {
        console.error('locker hasn\'t started yet, checking again in a second');
        setTimeout(checkLocker, 1000);
    }
}

checkLocker();

var runTests = function() {
    var xunit = false;
    var vowsArgument = [];//["--supress-stdout"];
    if (process.argv.indexOf("-x") > 0) {
        xunit = true;
        vowsArgument.push('--xunit');
    } else if (process.argv.indexOf("-d") > 0) {
        vowsArgument.push("--dot-matrix");
    } else {
        vowsArgument.push("--spec");
    }
    if (process.argv.indexOf("-u") > 0) {
        runIntegration = false
    }
    if (process.argv.indexOf("-c") > 0) {
        return runRake();
    }
    if (process.argv.indexOf("-s") > 0) {
        vowsArgument.push("--supress-stdout");
    }
    if (process.argv.indexOf("-nc") > 0) {
        vowsArgument.push("--nocolor");
    }

    var output = '';

    var vowsProcess = require("child_process").spawn(__dirname + "/../node_modules/vows/bin/vows", vowsArgument.concat(runFiles));
    vowsProcess.stdout.on("data", function(data) {
        if (xunit) output += data;
        process.stdout.write(data);
    });
    vowsProcess.stderr.on("data", function(data) {
        process.stderr.write(data);
    });
    vowsProcess.on("exit", function(code, signal) {
        if (xunit) {
            output = output.substring(output.indexOf('<testsuite name="Vows test"'));
            output = output.replace(/^\s+|\s+$/g, '');
            fs.writeFileSync('output.xml', output);
        }
        if (runIntegration) {
            runRake();
        } else {
            finished(code, signal);
        }
    });
}

var runRake = function() {
    var rakeProcess = require("child_process").spawn("rake", ["ci:setup:rspec","default"], { cwd: __dirname + "/integration"});
    rakeProcess.stdout.on("data", function(data) {
        process.stdout.write(data);
    });
    rakeProcess.stderr.on("data", function(data) {
        process.stderr.write(data);
    });
    rakeProcess.on("exit", function(code, signal) {
        finished(code, signal);
    });
}

var finished = function(exitCode, signal) {
    console.log('All tests done');
    if (exitCode > 0) {
        console.dir("vows process exited abnormally (code="+exitCode+", signal="+signal+")");
    }
    lockerd.shutdown(exitCode);
}
<commit_msg>if run with -c, leave the old me directory intact<commit_after>var fs = require("fs");
var net = require("net");
require.paths.push(__dirname + "/../Common/node");
process.env["NODE_PATH"]=__dirname + "/../Common/node"; // for spawn'd nodelings
var lconfig = require("lconfig");
lconfig.load("Config/config.json");
var lconsole = require("lconsole");
var wrench = require("wrench");
var runIntegration = true;
var integrationOnly = false;

var runFiles = [];
var runGroups = [];

function writeLogLine() {
    fs.writeSync(logFd, "[" + (new Date()).toLocaleString() + "][" + console.outputModule + "] " + Array.prototype.slice.call(arguments).toString() + "\n");
}

// We're going to replace the logging here so we can have it all and show it later
console.log = writeLogLine;
console.warn = writeLogLine;
console.error = writeLogLine;

if (process.argv.indexOf("-c") === -1) {
    try {
        wrench.rmdirSyncRecursive(lconfig.me);
    } catch (E) {
        if (E.code != "ENOENT") {
            process.stderr.write("Error: " + E + "\n");
            process.exit(1);
        }
    }
    wrench.copyDirSyncRecursive(lconfig.me + ".tests", lconfig.me);

    // Cleanup the old runs ijodtest dir
    try {
        wrench.rmdirSyncRecursive("ijodtest");
    } catch (E) {
        if (E.code != "ENOENT") {
            process.stderr.write("Error: " + E + "\n");
            process.exit(1);
        }
    }
}

// Cleanup the old runs Me dir and then copy the stub in

// Ladies and gentlemen, get your logs ready
var logFd = fs.openSync("locker.log", "w+");

// If we have args they can be either files or groups
if (process.argv.length > 2) {
    // It's nice to be helpful
    if (process.argv[2] == "-h" || process.argv[2] == "-?") {
        process.stdout.write("Usage: runTests [-l <group name>][-f] [files or groups to run]\n");
        process.stdout.write("  -h  You found me!\n");
        process.stdout.write("  -s  Suppress the output from test running\n");
        process.stdout.write("  -d  Use the dot matrix style reporter\n");
        process.stdout.write("  -nc Disable colors\n");
        process.stdout.write("  -u  Only run the unit tests, skip the front end tests\n");
        process.stdout.write("  -c  Only run the front end tests, skip the unit tests\n");
        process.stdout.write("  -l  List all of the available groups when no group is given or\n");
        process.stdout.write("      all of the files ran in a group.\n");
        process.stdout.write("  -f  The remaining arguments are treated as files to run\n");
        process.stdout.write("  -x  Output tests in xUnit format for CI reporting\n");
        process.stdout.write("\n");
        process.stdout.write("The list of groups are loaded from the Config/config.json and by default all\n");
        process.stdout.write("of them are ran.  A list of the groups to run can be specified as an\n");
        process.stdout.write("argument to the script.  If -f is used the list of arguments are\n");
        process.stdout.write("treated as individual files, not groups.\n");
        process.exit(0);
    }

    if (process.argv[2] == "-l") {
        var testGroups = JSON.parse(fs.readFileSync("Config/config.json")).testGroups;
        if (process.argv.length > 3) {
            if (testGroups.hasOwnProperty(process.argv[3])) {
                process.stdout.write("Files in group " + process.argv[3] + ":\n");
                for (var x = 0; x < testGroups[process.argv[3]].length; ++x) {
                    process.stdout.write("\t" + testGroups[process.argv[3]][x] + "\n");
                }
            } else {
                process.stdout.write(process.argv[3] + " is not a valid group.\n");
                process.exit(0);
            }
        } else {
            process.stdout.write("Available groups:\n");
            for (var key in testGroups) {
                if (testGroups.hasOwnProperty(key)) process.stdout.write("\t" + key + "\n");
            }
        }
        process.exit(0);
    }
    if (process.argv[2] == "-f") {
        // Get the files to run
        for (var x = 3; x < process.argv.length; ++x) {
            runFiles.push(process.argv[x]);
        }
    } else {
        // We'll process the groups later into indivdual files
        for (var x = 2; x < process.argv.length; ++x) {
            if (process.argv[x][0] != "-") runGroups.push(process.argv[x]);
        }
    }
}

// If they have specified any groups or defaulting to all we need to process this
if (runGroups.length > 0 || (runFiles.length === 0 && runGroups.length === 0)) {
    var testGroups = JSON.parse(fs.readFileSync("Config/config.json")).testGroups;
    if (runGroups.length === 0) {
        for (var key in testGroups) {
            if (testGroups.hasOwnProperty(key)) {
                runGroups.push(key);
            }
        }
    }
    runGroups.forEach(function(key) {
        if (testGroups.hasOwnProperty(key)) {
            runFiles = runFiles.concat(testGroups[key]);
        } else {
            process.stderr.write("Invalid test group " + key + ", skipping.\n");
        }
    });
}

if (runFiles.length === 0) {
    process.stderr.write("No tests were specified.\n");
    process.exit(1);
}

try {
    var lockerd = require(__dirname + "/../_lockerd.js");
} catch (E) {
    console.error("Locker did not start correctly, got the error: " + E);
    process.exit(1);
}

var checkLocker = function() {
    if (lockerd.alive === true) {
        runTests();
    } else {
        console.error('locker hasn\'t started yet, checking again in a second');
        setTimeout(checkLocker, 1000);
    }
}

checkLocker();

var runTests = function() {
    var xunit = false;
    var vowsArgument = [];//["--supress-stdout"];
    if (process.argv.indexOf("-x") > 0) {
        xunit = true;
        vowsArgument.push('--xunit');
    } else if (process.argv.indexOf("-d") > 0) {
        vowsArgument.push("--dot-matrix");
    } else {
        vowsArgument.push("--spec");
    }
    if (process.argv.indexOf("-u") > 0) {
        runIntegration = false
    }
    if (process.argv.indexOf("-c") > 0) {
        return runRake();
    }
    if (process.argv.indexOf("-s") > 0) {
        vowsArgument.push("--supress-stdout");
    }
    if (process.argv.indexOf("-nc") > 0) {
        vowsArgument.push("--nocolor");
    }

    var output = '';

    var vowsProcess = require("child_process").spawn(__dirname + "/../node_modules/vows/bin/vows", vowsArgument.concat(runFiles));
    vowsProcess.stdout.on("data", function(data) {
        if (xunit) output += data;
        process.stdout.write(data);
    });
    vowsProcess.stderr.on("data", function(data) {
        process.stderr.write(data);
    });
    vowsProcess.on("exit", function(code, signal) {
        if (xunit) {
            output = output.substring(output.indexOf('<testsuite name="Vows test"'));
            output = output.replace(/^\s+|\s+$/g, '');
            fs.writeFileSync('output.xml', output);
        }
        if (runIntegration) {
            runRake();
        } else {
            finished(code, signal);
        }
    });
}

var runRake = function() {
    var rakeProcess = require("child_process").spawn("rake", ["ci:setup:rspec","default"], { cwd: __dirname + "/integration"});
    rakeProcess.stdout.on("data", function(data) {
        process.stdout.write(data);
    });
    rakeProcess.stderr.on("data", function(data) {
        process.stderr.write(data);
    });
    rakeProcess.on("exit", function(code, signal) {
        finished(code, signal);
    });
}

var finished = function(exitCode, signal) {
    console.log('All tests done');
    if (exitCode > 0) {
        console.dir("vows process exited abnormally (code="+exitCode+", signal="+signal+")");
    }
    lockerd.shutdown(exitCode);
}
<|endoftext|>


<commit_before>
/* eslint spaced-comment: 0 */
/* eslint no-redeclare: 0 */
/* eslint no-unused-vars: 0 */

const assert = require('assert');

/// title: S-Expressions
/// type: rosetta-code

/// categories:
/// parsing
/// s-expressions

/// difficulty: ?

/// benchmark:
parseSexpr('((data "quoted data" 123 4.5) (data (!@# (4.5) "(more" "data)")))');

/// description:
///

///

/// S-Expressions are one convenient way to parse and store data.
///

///

Task:

///

///     Write a simple reader/parser for S-Expressions that handles quoted and unquoted strings, integers and floats.
///

///

/// The function should read a single but nested S-Expression from a string and
/// return it as a (nested) array.
///

///

///     Newlines and other whitespace may be ignored unless contained within a quoted string.
///

///
“<tt>()</tt>”  inside quoted strings are not interpreted, but treated as part of the string.
///

///

/// Handling escaped quotes inside a string is optional;  thus “<tt>(foo"bar)</tt>” maybe treated as a string “<tt>foo"bar</tt>”, or as an error.
///

///

/// For this, the reader need not recognize “<tt>\</tt>” for escaping, but should, in addition, recognize numbers if the language has appropriate datatypes.
///

///

/// Note that with the exception of “<tt>()"</tt>” (“<tt>\</tt>” if escaping is supported) and whitespace there are no special characters. Anything else is allowed without quotes.
///

///
The reader should be able to read the following input

///

///

///     ((data "quoted data" 123 4.5)
///     (data (!@# (4.5) "(more" "data)")))
///

///

///

/// and turn it into a native datastructure. (see the
/// Pike,
/// Python and
/// Ruby implementations
/// for examples of native data structures.)
///

///

/// challengeSeed:
function parseSexpr(str) {
  // Good luck!
  return true;
}

/// solutions:
function parseSexpr(str) {
  const t = str.match(/\s*("[^"]*"|\(|\)|"|[^\s()"]+)/g);
  for (var o, c = 0, i = t.length - 1; i >= 0; i--) {
    var n,
      ti = t[i].trim();
    if (ti == '"') return;
    else if (ti == '(') t[i] = '[', c += 1;
    else if (ti == ')') t[i] = ']', c -= 1;
    else if ((n = +ti) == ti) t[i] = n;
    else t[i] = `'${ti.replace('\'', '\\\'')}'`;
    if (i > 0 && ti != ']' && t[i - 1].trim() != '(') t.splice(i, 0, ',');
    if (!c) if (!o) o = true; else return;
  }
  return c ? undefined : eval(t.join(''));
}

/// tail:
const simpleSExpr = '(data1 data2 data3)';
const simpleSolution = ['data1', 'data2', 'data3'];

const basicSExpr = '((data "quoted data" 123 4.5) (data (!@# (4.5) "(more" "data)")))';
const basicSolution = [["data","\"quoted data\"",123,4.5],["data",["!@#",[4.5],"\"(more\"","\"data)\""]]];

/// tests:
assert(typeof parseSexpr === 'function', 'message: parseSexpr is a function.');
assert.deepEqual(parseSexpr(simpleSExpr), simpleSolution, "message: parseSexpr('(data1 data2 data3)') should return ['data1', 'data2', 'data3']");
assert.deepEqual(parseSexpr(basicSExpr), basicSolution, "message: parseSexpr('(data1 data2 data3)') should return an array with 3 elements");

/// id: 59667989bf71cf555dd5d2ff
<commit_msg>fix: Added missing difficulty.<commit_after>
/* eslint spaced-comment: 0 */
/* eslint no-redeclare: 0 */
/* eslint no-unused-vars: 0 */

const assert = require('assert');

/// title: S-Expressions
/// type: rosetta-code

/// categories:
/// parsing
/// s-expressions

/// difficulty: 4

/// benchmark:
parseSexpr('((data "quoted data" 123 4.5) (data (!@# (4.5) "(more" "data)")))');

/// description:
///

///

/// S-Expressions are one convenient way to parse and store data.
///

///

Task:

///

///     Write a simple reader/parser for S-Expressions that handles quoted and unquoted strings, integers and floats.
///

///

/// The function should read a single but nested S-Expression from a string and
/// return it as a (nested) array.
///

///

///     Newlines and other whitespace may be ignored unless contained within a quoted string.
///

///
“<tt>()</tt>”  inside quoted strings are not interpreted, but treated as part of the string.
///

///

/// Handling escaped quotes inside a string is optional;  thus “<tt>(foo"bar)</tt>” maybe treated as a string “<tt>foo"bar</tt>”, or as an error.
///

///

/// For this, the reader need not recognize “<tt>\</tt>” for escaping, but should, in addition, recognize numbers if the language has appropriate datatypes.
///

///

/// Note that with the exception of “<tt>()"</tt>” (“<tt>\</tt>” if escaping is supported) and whitespace there are no special characters. Anything else is allowed without quotes.
///

///
The reader should be able to read the following input

///

///

///     ((data "quoted data" 123 4.5)
///     (data (!@# (4.5) "(more" "data)")))
///

///

///

/// and turn it into a native datastructure. (see the
/// Pike,
/// Python and
/// Ruby implementations
/// for examples of native data structures.)
///

///

/// challengeSeed:
function parseSexpr(str) {
  // Good luck!
  return true;
}

/// solutions:
function parseSexpr(str) {
  const t = str.match(/\s*("[^"]*"|\(|\)|"|[^\s()"]+)/g);
  for (var o, c = 0, i = t.length - 1; i >= 0; i--) {
    var n,
      ti = t[i].trim();
    if (ti == '"') return;
    else if (ti == '(') t[i] = '[', c += 1;
    else if (ti == ')') t[i] = ']', c -= 1;
    else if ((n = +ti) == ti) t[i] = n;
    else t[i] = `'${ti.replace('\'', '\\\'')}'`;
    if (i > 0 && ti != ']' && t[i - 1].trim() != '(') t.splice(i, 0, ',');
    if (!c) if (!o) o = true; else return;
  }
  return c ? undefined : eval(t.join(''));
}

/// tail:
const simpleSExpr = '(data1 data2 data3)';
const simpleSolution = ['data1', 'data2', 'data3'];

const basicSExpr = '((data "quoted data" 123 4.5) (data (!@# (4.5) "(more" "data)")))';
const basicSolution = [["data","\"quoted data\"",123,4.5],["data",["!@#",[4.5],"\"(more\"","\"data)\""]]];

/// tests:
assert(typeof parseSexpr === 'function', 'message: parseSexpr is a function.');
assert.deepEqual(parseSexpr(simpleSExpr), simpleSolution, "message: parseSexpr('(data1 data2 data3)') should return ['data1', 'data2', 'data3']");
assert.deepEqual(parseSexpr(basicSExpr), basicSolution, "message: parseSexpr('(data1 data2 data3)') should return an array with 3 elements");

/// id: 59667989bf71cf555dd5d2ff
<|endoftext|>