mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 06:23:37 -05:00
Merge remote-tracking branch 'LLK/develop' into develop
This commit is contained in:
commit
ed650ba487
25 changed files with 579 additions and 27 deletions
|
@ -6,6 +6,9 @@ sudo: false
|
|||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
before_install:
|
||||
# Install the most up to date scratch-* dependencies
|
||||
- rm -rf node_modules/scratch-*
|
||||
after_script:
|
||||
- |
|
||||
# RELEASE_BRANCHES and NPM_TOKEN defined in Travis settings panel
|
||||
|
|
|
@ -437,7 +437,7 @@
|
|||
<category name="Data" colour="#FF8C1A" custom="VARIABLE">
|
||||
</category>
|
||||
<category name="Lists" colour="#FF8C1A">
|
||||
<block type="data_list"></block>
|
||||
<block type="data_listcontents"></block>
|
||||
<block type="data_addtolist">
|
||||
<value name="ITEM">
|
||||
<shadow type="text">
|
||||
|
|
|
@ -22,6 +22,7 @@ Scratch3MotionBlocks.prototype.getPrimitives = function() {
|
|||
'motion_turnleft': this.turnLeft,
|
||||
'motion_pointindirection': this.pointInDirection,
|
||||
'motion_glidesecstoxy': this.glide,
|
||||
'motion_setrotationstyle': this.setRotationStyle,
|
||||
'motion_changexby': this.changeX,
|
||||
'motion_setx': this.setX,
|
||||
'motion_changeyby': this.changeY,
|
||||
|
@ -96,6 +97,10 @@ Scratch3MotionBlocks.prototype.glide = function (args, util) {
|
|||
}
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.setRotationStyle = function (args, util) {
|
||||
util.target.setRotationStyle(args.STYLE);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.changeX = function (args, util) {
|
||||
var dx = Cast.toNumber(args.DX);
|
||||
util.target.setXY(util.target.x + dx, util.target.y);
|
||||
|
|
32
src/blocks/scratch3_procedures.js
Normal file
32
src/blocks/scratch3_procedures.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
function Scratch3ProcedureBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
Scratch3ProcedureBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'procedures_defnoreturn': this.defNoReturn,
|
||||
'procedures_callnoreturn': this.callNoReturn
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3ProcedureBlocks.prototype.defNoReturn = function () {
|
||||
// No-op: execute the blocks.
|
||||
};
|
||||
|
||||
Scratch3ProcedureBlocks.prototype.callNoReturn = function (args, util) {
|
||||
if (!util.stackFrame.executed) {
|
||||
var procedureName = args.mutation.name;
|
||||
util.stackFrame.executed = true;
|
||||
util.startProcedure(procedureName);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Scratch3ProcedureBlocks;
|
|
@ -1,3 +1,4 @@
|
|||
var mutationAdapter = require('./mutation-adapter');
|
||||
var html = require('htmlparser2');
|
||||
|
||||
/**
|
||||
|
@ -138,6 +139,9 @@ function domToBlock (blockDOM, blocks, isTopBlock, parent) {
|
|||
// Link next block to this block.
|
||||
block.next = childBlockNode.attribs.id;
|
||||
break;
|
||||
case 'mutation':
|
||||
block.mutation = mutationAdapter(xmlChild);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var adapter = require('./adapter');
|
||||
var mutationAdapter = require('./mutation-adapter');
|
||||
var xmlEscape = require('../util/xml-escape');
|
||||
|
||||
/**
|
||||
|
@ -116,6 +117,16 @@ Blocks.prototype.getInputs = function (id) {
|
|||
return inputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get mutation data for a block.
|
||||
* @param {?string} id ID of block to query.
|
||||
* @return {!Object} Mutation for the block.
|
||||
*/
|
||||
Blocks.prototype.getMutation = function (id) {
|
||||
if (typeof this._blocks[id] === 'undefined') return null;
|
||||
return this._blocks[id].mutation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the top-level script for a given block.
|
||||
* @param {?string} id ID of block to query.
|
||||
|
@ -130,6 +141,23 @@ Blocks.prototype.getTopLevelScript = function (id) {
|
|||
return block.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the procedure definition for a given name.
|
||||
* @param {?string} name Name of procedure to query.
|
||||
* @return {?string} ID of procedure definition.
|
||||
*/
|
||||
Blocks.prototype.getProcedureDefinition = function (name) {
|
||||
for (var id in this._blocks) {
|
||||
var block = this._blocks[id];
|
||||
if ((block.opcode == 'procedures_defnoreturn' ||
|
||||
block.opcode == 'procedures_defreturn') &&
|
||||
block.fields['NAME'].value == name) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
@ -226,12 +254,16 @@ Blocks.prototype.createBlock = function (block, opt_isFlyoutBlock) {
|
|||
*/
|
||||
Blocks.prototype.changeBlock = function (args) {
|
||||
// Validate
|
||||
if (args.element !== 'field') return;
|
||||
if (args.element !== 'field' && args.element !== 'mutation') return;
|
||||
if (typeof this._blocks[args.id] === 'undefined') return;
|
||||
if (typeof this._blocks[args.id].fields[args.name] === 'undefined') return;
|
||||
|
||||
// Update block value
|
||||
this._blocks[args.id].fields[args.name].value = args.value;
|
||||
if (args.element == 'field') {
|
||||
// Update block value
|
||||
if (!this._blocks[args.id].fields[args.name]) return;
|
||||
this._blocks[args.id].fields[args.name].value = args.value;
|
||||
} else if (args.element == 'mutation') {
|
||||
this._blocks[args.id].mutation = mutationAdapter(args.value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -355,6 +387,10 @@ Blocks.prototype.blockToXML = function (blockId) {
|
|||
' type="' + block.opcode + '"' +
|
||||
xy +
|
||||
'>';
|
||||
// Add any mutation. Must come before inputs.
|
||||
if (block.mutation) {
|
||||
xmlString += this.mutationToXML(block.mutation);
|
||||
}
|
||||
// Add any inputs on this block.
|
||||
for (var input in block.inputs) {
|
||||
var blockInput = block.inputs[input];
|
||||
|
@ -389,6 +425,25 @@ Blocks.prototype.blockToXML = function (blockId) {
|
|||
return xmlString;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively encode a mutation object to XML.
|
||||
* @param {!Object} mutation Object representing a mutation.
|
||||
* @return {string} XML string representing a mutation.
|
||||
*/
|
||||
Blocks.prototype.mutationToXML = function (mutation) {
|
||||
var mutationString = '<' + mutation.tagName;
|
||||
for (var prop in mutation) {
|
||||
if (prop == 'children' || prop == 'tagName') continue;
|
||||
mutationString += ' ' + prop + '="' + mutation[prop] + '"';
|
||||
}
|
||||
mutationString += '>';
|
||||
for (var i = 0; i < mutation.children.length; i++) {
|
||||
mutationString += this.mutationToXML(mutation.children[i]);
|
||||
}
|
||||
mutationString += '</' + mutation.tagName + '>';
|
||||
return mutationString;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
|
|
@ -132,6 +132,12 @@ var execute = function (sequencer, thread) {
|
|||
argValues[inputName] = currentStackFrame.reported[inputName];
|
||||
}
|
||||
|
||||
// Add any mutation to args (e.g., for procedures).
|
||||
var mutation = target.blocks.getMutation(currentBlockId);
|
||||
if (mutation) {
|
||||
argValues.mutation = mutation;
|
||||
}
|
||||
|
||||
// If we've gotten this far, all of the input blocks are evaluated,
|
||||
// and `argValues` is fully populated. So, execute the block primitive.
|
||||
// First, clear `currentStackFrame.reported`, so any subsequent execution
|
||||
|
@ -155,6 +161,9 @@ var execute = function (sequencer, thread) {
|
|||
startBranch: function (branchNum) {
|
||||
sequencer.stepToBranch(thread, branchNum);
|
||||
},
|
||||
startProcedure: function (procedureName) {
|
||||
sequencer.stepToProcedure(thread, procedureName);
|
||||
},
|
||||
startHats: function(requestedHat, opt_matchFields, opt_target) {
|
||||
return (
|
||||
runtime.startHats(requestedHat, opt_matchFields, opt_target)
|
||||
|
|
39
src/engine/mutation-adapter.js
Normal file
39
src/engine/mutation-adapter.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
var html = require('htmlparser2');
|
||||
|
||||
/**
|
||||
* Adapter between mutator XML or DOM and block representation which can be
|
||||
* used by the Scratch runtime.
|
||||
* @param {(Object|string)} mutation Mutation XML string or DOM.
|
||||
* @return {Object} Object representing the mutation.
|
||||
*/
|
||||
module.exports = function (mutation) {
|
||||
var mutationParsed;
|
||||
// Check if the mutation is already parsed; if not, parse it.
|
||||
if (typeof mutation === 'object') {
|
||||
mutationParsed = mutation;
|
||||
} else {
|
||||
mutationParsed = html.parseDOM(mutation)[0];
|
||||
}
|
||||
return mutatorTagToObject(mutationParsed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a part of a mutation DOM to a mutation VM object, recursively.
|
||||
* @param {Object} dom DOM object for mutation tag.
|
||||
* @return {Object} Object representing useful parts of this mutation.
|
||||
*/
|
||||
function mutatorTagToObject (dom) {
|
||||
var obj = Object.create(null);
|
||||
obj.tagName = dom.name;
|
||||
obj.children = [];
|
||||
for (var prop in dom.attribs) {
|
||||
if (prop == 'xmlns') continue;
|
||||
obj[prop] = dom.attribs[prop];
|
||||
}
|
||||
for (var i = 0; i < dom.children.length; i++) {
|
||||
obj.children.push(
|
||||
mutatorTagToObject(dom.children[i])
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}
|
|
@ -16,7 +16,8 @@ var defaultBlockPackages = {
|
|||
'scratch3_operators': require('../blocks/scratch3_operators'),
|
||||
'scratch3_sound': require('../blocks/scratch3_sound'),
|
||||
'scratch3_sensing': require('../blocks/scratch3_sensing'),
|
||||
'scratch3_data': require('../blocks/scratch3_data')
|
||||
'scratch3_data': require('../blocks/scratch3_data'),
|
||||
'scratch3_procedures': require('../blocks/scratch3_procedures')
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -124,6 +124,16 @@ Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Step a procedure.
|
||||
* @param {!Thread} thread Thread object to step to procedure.
|
||||
* @param {!string} procedureName Name of procedure defined in this target.
|
||||
*/
|
||||
Sequencer.prototype.stepToProcedure = function (thread, procedureName) {
|
||||
var definition = thread.target.blocks.getProcedureDefinition(procedureName);
|
||||
thread.pushStack(definition);
|
||||
};
|
||||
|
||||
/**
|
||||
* Step a thread into an input reporter, and manage its status appropriately.
|
||||
* @param {!Thread} thread Thread object to step to reporter.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
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');
|
||||
|
@ -123,7 +124,16 @@ function parseScratchObject (object, runtime, topLevel) {
|
|||
target.visible = object.visible;
|
||||
}
|
||||
if (object.hasOwnProperty('currentCostumeIndex')) {
|
||||
target.currentCostume = object.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();
|
||||
|
@ -332,6 +342,14 @@ function parseBlock (sb2block) {
|
|||
};
|
||||
}
|
||||
}
|
||||
// Special cases to generate mutations.
|
||||
if (oldOpcode == 'call') {
|
||||
activeBlock.mutation = {
|
||||
tagName: 'mutation',
|
||||
children: [],
|
||||
name: sb2block[1]
|
||||
};
|
||||
}
|
||||
return activeBlock;
|
||||
}
|
||||
|
||||
|
|
|
@ -1374,15 +1374,20 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'procDef':{
|
||||
'opcode':'proc_def',
|
||||
'argMap':[]
|
||||
'opcode':'procedures_defnoreturn',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'field',
|
||||
'fieldName':'NAME'
|
||||
}
|
||||
]
|
||||
},
|
||||
'getParam':{
|
||||
'opcode':'proc_param',
|
||||
'argMap':[]
|
||||
},
|
||||
'call':{
|
||||
'opcode':'proc_call',
|
||||
'opcode':'procedures_callnoreturn',
|
||||
'argMap':[]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -29,6 +29,20 @@ function Clone(sprite, runtime) {
|
|||
* @type {?Number}
|
||||
*/
|
||||
this.drawableID = null;
|
||||
|
||||
/**
|
||||
* Map of current graphic effect values.
|
||||
* @type {!Object.<string, number>}
|
||||
*/
|
||||
this.effects = {
|
||||
'color': 0,
|
||||
'fisheye': 0,
|
||||
'whirl': 0,
|
||||
'pixelate': 0,
|
||||
'mosaic': 0,
|
||||
'brightness': 0,
|
||||
'ghost': 0
|
||||
};
|
||||
}
|
||||
util.inherits(Clone, Target);
|
||||
|
||||
|
@ -38,7 +52,6 @@ util.inherits(Clone, Target);
|
|||
Clone.prototype.initDrawable = function () {
|
||||
if (this.renderer) {
|
||||
this.drawableID = this.renderer.createDrawable();
|
||||
this.updateAllDrawableProperties();
|
||||
}
|
||||
// If we're a clone, start the hats.
|
||||
if (!this.isOriginal) {
|
||||
|
@ -99,18 +112,29 @@ Clone.prototype.size = 100;
|
|||
Clone.prototype.currentCostume = 0;
|
||||
|
||||
/**
|
||||
* Map of current graphic effect values.
|
||||
* @type {!Object.<string, number>}
|
||||
* Rotation style for "all around"/spinning.
|
||||
* @enum
|
||||
*/
|
||||
Clone.prototype.effects = {
|
||||
'color': 0,
|
||||
'fisheye': 0,
|
||||
'whirl': 0,
|
||||
'pixelate': 0,
|
||||
'mosaic': 0,
|
||||
'brightness': 0,
|
||||
'ghost': 0
|
||||
};
|
||||
Clone.ROTATION_STYLE_ALL_AROUND = 'all around';
|
||||
|
||||
/**
|
||||
* Rotation style for "left-right"/flipping.
|
||||
* @enum
|
||||
*/
|
||||
Clone.ROTATION_STYLE_LEFT_RIGHT = 'left-right';
|
||||
|
||||
/**
|
||||
* Rotation style for "no rotation."
|
||||
* @enum
|
||||
*/
|
||||
Clone.ROTATION_STYLE_NONE = 'don\'t rotate';
|
||||
|
||||
/**
|
||||
* Current rotation style.
|
||||
* @type {!string}
|
||||
*/
|
||||
Clone.prototype.rotationStyle = Clone.ROTATION_STYLE_ALL_AROUND;
|
||||
|
||||
// End clone-level properties.
|
||||
|
||||
/**
|
||||
|
@ -131,6 +155,26 @@ Clone.prototype.setXY = function (x, y) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the rendered direction and scale, after applying rotation style.
|
||||
* @return {Object<string, number>} Direction and scale to render.
|
||||
*/
|
||||
Clone.prototype._getRenderedDirectionAndScale = function () {
|
||||
// Default: no changes to `this.direction` or `this.scale`.
|
||||
var finalDirection = this.direction;
|
||||
var finalScale = [this.size, this.size];
|
||||
if (this.rotationStyle == Clone.ROTATION_STYLE_NONE) {
|
||||
// Force rendered direction to be 90.
|
||||
finalDirection = 90;
|
||||
} else if (this.rotationStyle === Clone.ROTATION_STYLE_LEFT_RIGHT) {
|
||||
// Force rendered direction to be 90, and flip drawable if needed.
|
||||
finalDirection = 90;
|
||||
var scaleFlip = (this.direction < 0) ? -1 : 1;
|
||||
finalScale = [scaleFlip * this.size, this.size];
|
||||
}
|
||||
return {direction: finalDirection, scale: finalScale};
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the direction of a clone.
|
||||
* @param {!number} direction New direction of clone.
|
||||
|
@ -142,8 +186,10 @@ Clone.prototype.setDirection = function (direction) {
|
|||
// Keep direction between -179 and +180.
|
||||
this.direction = MathUtil.wrapClamp(direction, -179, 180);
|
||||
if (this.renderer) {
|
||||
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
direction: this.direction
|
||||
direction: renderedDirectionScale.direction,
|
||||
scale: renderedDirectionScale.scale
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -192,8 +238,10 @@ Clone.prototype.setSize = function (size) {
|
|||
// Keep size between 5% and 535%.
|
||||
this.size = MathUtil.clamp(size, 5, 535);
|
||||
if (this.renderer) {
|
||||
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
scale: [this.size, this.size]
|
||||
direction: renderedDirectionScale.direction,
|
||||
scale: renderedDirectionScale.scale
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -231,6 +279,7 @@ Clone.prototype.clearEffects = function () {
|
|||
*/
|
||||
Clone.prototype.setCostume = function (index) {
|
||||
// Keep the costume index within possible values.
|
||||
index = Math.round(index);
|
||||
this.currentCostume = MathUtil.wrapClamp(
|
||||
index, 0, this.sprite.costumes.length - 1
|
||||
);
|
||||
|
@ -241,6 +290,27 @@ Clone.prototype.setCostume = function (index) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the rotation style for this clone.
|
||||
* @param {!string} rotationStyle New rotation style.
|
||||
*/
|
||||
Clone.prototype.setRotationStyle = function (rotationStyle) {
|
||||
if (rotationStyle == Clone.ROTATION_STYLE_NONE) {
|
||||
this.rotationStyle = Clone.ROTATION_STYLE_NONE;
|
||||
} else if (rotationStyle == Clone.ROTATION_STYLE_ALL_AROUND) {
|
||||
this.rotationStyle = Clone.ROTATION_STYLE_ALL_AROUND;
|
||||
} else if (rotationStyle == Clone.ROTATION_STYLE_LEFT_RIGHT) {
|
||||
this.rotationStyle = Clone.ROTATION_STYLE_LEFT_RIGHT;
|
||||
}
|
||||
if (this.renderer) {
|
||||
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
direction: renderedDirectionScale.direction,
|
||||
scale: renderedDirectionScale.scale
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a costume index of this clone, by name of the costume.
|
||||
* @param {?string} costumeName Name of a costume.
|
||||
|
@ -275,10 +345,11 @@ Clone.prototype.getSoundIndexByName = function (soundName) {
|
|||
*/
|
||||
Clone.prototype.updateAllDrawableProperties = function () {
|
||||
if (this.renderer) {
|
||||
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
position: [this.x, this.y],
|
||||
direction: this.direction,
|
||||
scale: [this.size, this.size],
|
||||
direction: renderedDirectionScale.direction,
|
||||
scale: renderedDirectionScale.scale,
|
||||
visible: this.visible,
|
||||
skin: this.sprite.costumes[this.currentCostume].skin
|
||||
});
|
||||
|
@ -340,6 +411,7 @@ Clone.prototype.makeClone = function () {
|
|||
newClone.visible = this.visible;
|
||||
newClone.size = this.size;
|
||||
newClone.currentCostume = this.currentCostume;
|
||||
newClone.rotationStyle = this.rotationStyle;
|
||||
newClone.effects = JSON.parse(JSON.stringify(this.effects));
|
||||
newClone.variables = JSON.parse(JSON.stringify(this.variables));
|
||||
newClone.lists = JSON.parse(JSON.stringify(this.lists));
|
||||
|
|
|
@ -54,7 +54,6 @@ Sprite.prototype.createClone = function () {
|
|||
this.clones.push(newClone);
|
||||
if (newClone.isOriginal) {
|
||||
newClone.initDrawable();
|
||||
newClone.updateAllDrawableProperties();
|
||||
}
|
||||
return newClone;
|
||||
};
|
||||
|
|
13
test/unit/sprites_clone.js
Normal file
13
test/unit/sprites_clone.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
var test = require('tap').test;
|
||||
var Clone = require('../../src/sprites/clone');
|
||||
var Sprite = require('../../src/sprites/sprite');
|
||||
|
||||
test('clone effects', function (t) {
|
||||
// Create two clones and ensure they have different graphic effect objects.
|
||||
// Regression test for Github issue #224
|
||||
var spr = new Sprite();
|
||||
var a = new Clone(spr, null);
|
||||
var b = new Clone(spr, null);
|
||||
t.ok(a.effects !== b.effects);
|
||||
t.end();
|
||||
});
|
179
test/unit/util_cast.js
Normal file
179
test/unit/util_cast.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
var test = require('tap').test;
|
||||
var cast = require('../../src/util/cast');
|
||||
|
||||
test('toNumber', function (t) {
|
||||
// Numeric
|
||||
t.strictEqual(cast.toNumber(0), 0);
|
||||
t.strictEqual(cast.toNumber(1), 1);
|
||||
t.strictEqual(cast.toNumber(3.14), 3.14);
|
||||
|
||||
// String
|
||||
t.strictEqual(cast.toNumber('0'), 0);
|
||||
t.strictEqual(cast.toNumber('1'), 1);
|
||||
t.strictEqual(cast.toNumber('3.14'), 3.14);
|
||||
t.strictEqual(cast.toNumber('0.1e10'), 1000000000);
|
||||
t.strictEqual(cast.toNumber('foobar'), 0);
|
||||
|
||||
// Boolean
|
||||
t.strictEqual(cast.toNumber(true), 1);
|
||||
t.strictEqual(cast.toNumber(false), 0);
|
||||
t.strictEqual(cast.toNumber('true'), 0);
|
||||
t.strictEqual(cast.toNumber('false'), 0);
|
||||
|
||||
// Undefined & object
|
||||
t.strictEqual(cast.toNumber(undefined), 0);
|
||||
t.strictEqual(cast.toNumber({}), 0);
|
||||
t.strictEqual(cast.toNumber(NaN), 0);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('toBoolean', function (t) {
|
||||
// Numeric
|
||||
t.strictEqual(cast.toBoolean(0), false);
|
||||
t.strictEqual(cast.toBoolean(1), true);
|
||||
t.strictEqual(cast.toBoolean(3.14), true);
|
||||
|
||||
// String
|
||||
t.strictEqual(cast.toBoolean('0'), false);
|
||||
t.strictEqual(cast.toBoolean('1'), true);
|
||||
t.strictEqual(cast.toBoolean('3.14'), true);
|
||||
t.strictEqual(cast.toBoolean('0.1e10'), true);
|
||||
t.strictEqual(cast.toBoolean('foobar'), true);
|
||||
|
||||
// Boolean
|
||||
t.strictEqual(cast.toBoolean(true), true);
|
||||
t.strictEqual(cast.toBoolean(false), false);
|
||||
|
||||
// Undefined & object
|
||||
t.strictEqual(cast.toBoolean(undefined), false);
|
||||
t.strictEqual(cast.toBoolean({}), true);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('toString', function (t) {
|
||||
// Numeric
|
||||
t.strictEqual(cast.toString(0), '0');
|
||||
t.strictEqual(cast.toString(1), '1');
|
||||
t.strictEqual(cast.toString(3.14), '3.14');
|
||||
|
||||
// String
|
||||
t.strictEqual(cast.toString('0'), '0');
|
||||
t.strictEqual(cast.toString('1'), '1');
|
||||
t.strictEqual(cast.toString('3.14'), '3.14');
|
||||
t.strictEqual(cast.toString('0.1e10'), '0.1e10');
|
||||
t.strictEqual(cast.toString('foobar'), 'foobar');
|
||||
|
||||
// Boolean
|
||||
t.strictEqual(cast.toString(true), 'true');
|
||||
t.strictEqual(cast.toString(false), 'false');
|
||||
|
||||
// Undefined & object
|
||||
t.strictEqual(cast.toString(undefined), 'undefined');
|
||||
t.strictEqual(cast.toString({}), '[object Object]');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('toRbgColorList', function (t) {
|
||||
// Hex (minimal, see "color" util tests)
|
||||
t.deepEqual(cast.toRgbColorList('#000'), [0,0,0]);
|
||||
t.deepEqual(cast.toRgbColorList('#000000'), [0,0,0]);
|
||||
t.deepEqual(cast.toRgbColorList('#fff'), [255,255,255]);
|
||||
t.deepEqual(cast.toRgbColorList('#ffffff'), [255,255,255]);
|
||||
|
||||
// Decimal (minimal, see "color" util tests)
|
||||
t.deepEqual(cast.toRgbColorList(0), [0,0,0]);
|
||||
t.deepEqual(cast.toRgbColorList(1), [0,0,1]);
|
||||
t.deepEqual(cast.toRgbColorList(16777215), [255,255,255]);
|
||||
|
||||
// Malformed
|
||||
t.deepEqual(cast.toRgbColorList('ffffff'), [0,0,0]);
|
||||
t.deepEqual(cast.toRgbColorList('foobar'), [0,0,0]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('compare', function (t) {
|
||||
// Numeric
|
||||
t.strictEqual(cast.compare(0, 0), 0);
|
||||
t.strictEqual(cast.compare(1, 0), 1);
|
||||
t.strictEqual(cast.compare(0, 1), -1);
|
||||
t.strictEqual(cast.compare(1, 1), 0);
|
||||
|
||||
// String
|
||||
t.strictEqual(cast.compare('0', '0'), 0);
|
||||
t.strictEqual(cast.compare('0.1e10', '1000000000'), 0);
|
||||
t.strictEqual(cast.compare('foobar', 'FOOBAR'), 0);
|
||||
t.ok(cast.compare('dog', 'cat') > 0);
|
||||
|
||||
// Boolean
|
||||
t.strictEqual(cast.compare(true, true), 0);
|
||||
t.strictEqual(cast.compare(true, false), 1);
|
||||
t.strictEqual(cast.compare(false, true), -1);
|
||||
t.strictEqual(cast.compare(true, true), 0);
|
||||
|
||||
// Undefined & object
|
||||
t.strictEqual(cast.compare(undefined, undefined), 0);
|
||||
t.strictEqual(cast.compare(undefined, 'undefined'), 0);
|
||||
t.strictEqual(cast.compare({}, {}), 0);
|
||||
t.strictEqual(cast.compare({}, '[object Object]'), 0);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('isInt', function (t) {
|
||||
// Numeric
|
||||
t.strictEqual(cast.isInt(0), true);
|
||||
t.strictEqual(cast.isInt(1), true);
|
||||
t.strictEqual(cast.isInt(0.0), true);
|
||||
t.strictEqual(cast.isInt(3.14), false);
|
||||
t.strictEqual(cast.isInt(NaN), true);
|
||||
|
||||
// String
|
||||
t.strictEqual(cast.isInt('0'), true);
|
||||
t.strictEqual(cast.isInt('1'), true);
|
||||
t.strictEqual(cast.isInt('0.0'), false);
|
||||
t.strictEqual(cast.isInt('0.1e10'), false);
|
||||
t.strictEqual(cast.isInt('3.14'), false);
|
||||
|
||||
// Boolean
|
||||
t.strictEqual(cast.isInt(true), true);
|
||||
t.strictEqual(cast.isInt(false), true);
|
||||
|
||||
// Undefined & object
|
||||
t.strictEqual(cast.isInt(undefined), false);
|
||||
t.strictEqual(cast.isInt({}), false);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('toListIndex', function (t) {
|
||||
var list = [0,1,2,3,4,5];
|
||||
var empty = [];
|
||||
|
||||
// Valid
|
||||
t.strictEqual(cast.toListIndex(1, list.length), 1);
|
||||
t.strictEqual(cast.toListIndex(6, list.length), 6);
|
||||
|
||||
// Invalid
|
||||
t.strictEqual(cast.toListIndex(-1, list.length), cast.LIST_INVALID);
|
||||
t.strictEqual(cast.toListIndex(0.1, list.length), cast.LIST_INVALID);
|
||||
t.strictEqual(cast.toListIndex(0, list.length), cast.LIST_INVALID);
|
||||
t.strictEqual(cast.toListIndex(7, list.length), cast.LIST_INVALID);
|
||||
|
||||
// "all"
|
||||
t.strictEqual(cast.toListIndex('all', list.length), cast.LIST_ALL);
|
||||
|
||||
// "last"
|
||||
t.strictEqual(cast.toListIndex('last', list.length), list.length);
|
||||
t.strictEqual(cast.toListIndex('last', empty.length), cast.LIST_INVALID);
|
||||
|
||||
// "random"
|
||||
var random = cast.toListIndex('random', list.length);
|
||||
t.ok(random <= list.length);
|
||||
t.ok(random > 0);
|
||||
t.strictEqual(cast.toListIndex('random', empty.length), cast.LIST_INVALID);
|
||||
|
||||
// "any" (alias for "random")
|
||||
var any = cast.toListIndex('any', list.length);
|
||||
t.ok(any <= list.length);
|
||||
t.ok(any > 0);
|
||||
t.strictEqual(cast.toListIndex('any', empty.length), cast.LIST_INVALID);
|
||||
t.end();
|
||||
});
|
62
test/unit/util_color.js
Normal file
62
test/unit/util_color.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
var test = require('tap').test;
|
||||
var color = require('../../src/util/color');
|
||||
|
||||
test('decimalToHex', function (t) {
|
||||
t.strictEqual(color.decimalToHex(0), '#000000');
|
||||
t.strictEqual(color.decimalToHex(1), '#000001');
|
||||
t.strictEqual(color.decimalToHex(16777215), '#ffffff');
|
||||
t.strictEqual(color.decimalToHex(-16777215), '#000001');
|
||||
t.strictEqual(color.decimalToHex(99999999), '#5f5e0ff');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('decimalToRgb', function (t) {
|
||||
t.deepEqual(color.decimalToRgb(0), {r:0,g:0,b:0});
|
||||
t.deepEqual(color.decimalToRgb(1), {r:0,g:0,b:1});
|
||||
t.deepEqual(color.decimalToRgb(16777215), {r:255,g:255,b:255});
|
||||
t.deepEqual(color.decimalToRgb(-16777215), {r:0,g:0,b:1});
|
||||
t.deepEqual(color.decimalToRgb(99999999), {r:245,g:224,b:255});
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('hexToRgb', function (t) {
|
||||
t.deepEqual(color.hexToRgb('#000'), {r:0,g:0,b:0});
|
||||
t.deepEqual(color.hexToRgb('#000000'), {r:0,g:0,b:0});
|
||||
t.deepEqual(color.hexToRgb('#fff'), {r:255,g:255,b:255});
|
||||
t.deepEqual(color.hexToRgb('#ffffff'), {r:255,g:255,b:255});
|
||||
t.deepEqual(color.hexToRgb('#0fa'), {r:0,g:255,b:170});
|
||||
t.deepEqual(color.hexToRgb('#00ffaa'), {r:0,g:255,b:170});
|
||||
|
||||
t.deepEqual(color.hexToRgb('000'), {r:0,g:0,b:0});
|
||||
t.deepEqual(color.hexToRgb('fff'), {r:255,g:255,b:255});
|
||||
t.deepEqual(color.hexToRgb('00ffaa'), {r:0,g:255,b:170});
|
||||
|
||||
t.deepEqual(color.hexToRgb('0'), null);
|
||||
t.deepEqual(color.hexToRgb('hello world'), null);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rgbToHex', function (t) {
|
||||
t.strictEqual(color.rgbToHex({r:0,g:0,b:0}), '#000000');
|
||||
t.strictEqual(color.rgbToHex({r:255,g:255,b:255}), '#ffffff');
|
||||
t.strictEqual(color.rgbToHex({r:0,g:255,b:170}), '#00ffaa');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rgbToDecimal', function (t) {
|
||||
t.strictEqual(color.rgbToDecimal({r:0,g:0,b:0}), 0);
|
||||
t.strictEqual(color.rgbToDecimal({r:255,g:255,b:255}), 16777215);
|
||||
t.strictEqual(color.rgbToDecimal({r:0,g:255,b:170}), 65450);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('hexToDecimal', function (t) {
|
||||
t.strictEqual(color.hexToDecimal('#000'), 0);
|
||||
t.strictEqual(color.hexToDecimal('#000000'), 0);
|
||||
t.strictEqual(color.hexToDecimal('#fff'), 16777215);
|
||||
t.strictEqual(color.hexToDecimal('#ffffff'), 16777215);
|
||||
t.strictEqual(color.hexToDecimal('#0fa'), 65450);
|
||||
t.strictEqual(color.hexToDecimal('#00ffaa'), 65450);
|
||||
t.end();
|
||||
});
|
37
test/unit/util_math.js
Normal file
37
test/unit/util_math.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
var test = require('tap').test;
|
||||
var math = require('../../src/util/math-util');
|
||||
|
||||
test('degToRad', function (t) {
|
||||
// @todo This is incorrect
|
||||
t.strictEqual(math.degToRad(0), 1.5707963267948966);
|
||||
t.strictEqual(math.degToRad(1), 1.5533430342749535);
|
||||
t.strictEqual(math.degToRad(180), -1.5707963267948966);
|
||||
t.strictEqual(math.degToRad(360), -4.71238898038469);
|
||||
t.strictEqual(math.degToRad(720), -10.995574287564276);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('radToDeg', function (t) {
|
||||
t.strictEqual(math.radToDeg(0), 0);
|
||||
t.strictEqual(math.radToDeg(1), 57.29577951308232);
|
||||
t.strictEqual(math.radToDeg(180), 10313.240312354817);
|
||||
t.strictEqual(math.radToDeg(360), 20626.480624709635);
|
||||
t.strictEqual(math.radToDeg(720), 41252.96124941927);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('clamp', function (t) {
|
||||
t.strictEqual(math.clamp(0, 0, 10), 0);
|
||||
t.strictEqual(math.clamp(1, 0, 10), 1);
|
||||
t.strictEqual(math.clamp(-10, 0, 10), 0);
|
||||
t.strictEqual(math.clamp(100, 0, 10), 10);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('wrapClamp', function (t) {
|
||||
t.strictEqual(math.wrapClamp(0, 0, 10), 0);
|
||||
t.strictEqual(math.wrapClamp(1, 0, 10), 1);
|
||||
t.strictEqual(math.wrapClamp(-10, 0, 10), 1);
|
||||
t.strictEqual(math.wrapClamp(100, 0, 10), 1);
|
||||
t.end();
|
||||
});
|
9
test/unit/util_xml.js
Normal file
9
test/unit/util_xml.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
var test = require('tap').test;
|
||||
var xml = require('../../src/util/xml-escape');
|
||||
|
||||
test('escape', function (t) {
|
||||
var input = '<foo bar="he & llo \'"></foo>';
|
||||
var output = '<foo bar="he & llo '"></foo>';
|
||||
t.strictEqual(xml(input), output);
|
||||
t.end();
|
||||
});
|
Loading…
Reference in a new issue