mirror of
https://github.com/scratchfoundation/scratch-analysis.git
synced 2024-11-24 16:48:13 -05:00
commit
50d9f834b4
21 changed files with 1171 additions and 0 deletions
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/node_modules
|
||||||
|
/.nyc_output
|
||||||
|
/coverage
|
3
.eslintrc
Normal file
3
.eslintrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": ["scratch", "scratch/node"]
|
||||||
|
}
|
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
## NPM
|
||||||
|
/node_modules
|
||||||
|
npm-*
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
## Code Coverage
|
||||||
|
.nyc_output/
|
||||||
|
coverage/
|
45
README.md
Normal file
45
README.md
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
## scratch-analysis
|
||||||
|
#### Analysis tool for summarizing the structure, composition, and complexity of [Scratch](https://scratch.mit.edu) programs.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
```bash
|
||||||
|
npm install scratch-analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
const analysis = require('scratch-analysis');
|
||||||
|
analysis(buffer, function (err, result) {
|
||||||
|
// handle any validation errors and ...
|
||||||
|
// do something interesting with the results!
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Analysis Modules
|
||||||
|
### General
|
||||||
|
The `scratch-analysis` module will return an object containing high-level summary information about the project:
|
||||||
|
|
||||||
|
| Key | Attributes |
|
||||||
|
| ----------------- | -------------------------------------------------------- |
|
||||||
|
| `scripts` | `count` |
|
||||||
|
| `blocks` | `count`, `unique`, `list`, `frequency` |
|
||||||
|
| `sprites` | `count` |
|
||||||
|
| `variables` | `count` |
|
||||||
|
| `lists` | `count` |
|
||||||
|
| `costumes` | `count`, `list`, `hash` |
|
||||||
|
| `sounds` | `count`, `list`, `hash` |
|
||||||
|
| `extensions` | `count`, `list` |
|
||||||
|
| `comments` | `count` |
|
||||||
|
|
||||||
|
### Concepts
|
||||||
|
**Coming Soon**
|
||||||
|
|
||||||
|
### Complexity
|
||||||
|
**Coming Soon**
|
||||||
|
|
||||||
|
### Classification
|
||||||
|
**Coming Soon**
|
||||||
|
|
||||||
|
## References
|
||||||
|
### New Frameworks for Studying and Assessing the Development of Computational Thinking
|
||||||
|
Author(s): Karen Brennan, Mitchel Resnick
|
||||||
|
PDF: [Download](https://web.media.mit.edu/~kbrennan/files/Brennan_Resnick_AERA2012_CT.pdf)
|
23
lib/index.js
Normal file
23
lib/index.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
const parser = require('scratch-parser');
|
||||||
|
|
||||||
|
const sb2 = require('./sb2');
|
||||||
|
const sb3 = require('./sb3');
|
||||||
|
|
||||||
|
module.exports = function (buffer, callback) {
|
||||||
|
parser(buffer, false, (err, result) => {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
// Extract only the project object from the parser results
|
||||||
|
const project = result[0];
|
||||||
|
|
||||||
|
// Push project object to the appropriate analysis handler
|
||||||
|
switch (project.projectVersion) {
|
||||||
|
case 2:
|
||||||
|
sb2(project, callback);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
sb3(project, callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
185
lib/sb2.js
Normal file
185
lib/sb2.js
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
const utility = require('./utility');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of items matching the specified attribute.
|
||||||
|
* @param {object} project Project object (SB2 format)
|
||||||
|
* @param {string} attribute Attribute to extract and flatten
|
||||||
|
* @return {array} Array of specified attribute
|
||||||
|
*/
|
||||||
|
const flatten = function (project, attribute) {
|
||||||
|
// Storage object
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
// If attribute exists at the top level of the project, append it
|
||||||
|
if (typeof project[attribute] !== 'undefined') {
|
||||||
|
result = project[attribute];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over child elements and append to result array
|
||||||
|
for (let i in project.children) {
|
||||||
|
const child = project.children[i];
|
||||||
|
if (typeof child[attribute] !== 'undefined') {
|
||||||
|
result = result.concat(child[attribute]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract summary information from a specific project attribute. Will attempt
|
||||||
|
* to concatinate all children when generating summary.
|
||||||
|
* @param {object} project Project object (SB2 format)
|
||||||
|
* @param {string} attribute Attribute key
|
||||||
|
* @param {string} id "id" key
|
||||||
|
* @param {string} hash "hash" key
|
||||||
|
* @return {object} Summary information
|
||||||
|
*/
|
||||||
|
const extract = function (project, attribute, id, hash) {
|
||||||
|
// Create storage objects and flatten project
|
||||||
|
let idList = null;
|
||||||
|
let hashList = null;
|
||||||
|
let elements = flatten(project, attribute);
|
||||||
|
|
||||||
|
// Extract ids if specified
|
||||||
|
if (typeof id !== 'undefined') {
|
||||||
|
idList = [];
|
||||||
|
for (var x in elements) {
|
||||||
|
idList.push(elements[x][id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract hashes if specified
|
||||||
|
if (typeof hash !== 'undefined') {
|
||||||
|
hashList = [];
|
||||||
|
for (var y in elements) {
|
||||||
|
hashList.push(elements[y][hash]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build result and return
|
||||||
|
var result = {
|
||||||
|
count: elements.length
|
||||||
|
};
|
||||||
|
if (idList !== null) result.id = idList;
|
||||||
|
if (hashList !== null) result.hash = hashList;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract number of sprites from a project object. Will attempt to ignore
|
||||||
|
* "children" which are not sprites.
|
||||||
|
* @param {object} input Project object (SB2 format)
|
||||||
|
* @return {object} Sprite information
|
||||||
|
*/
|
||||||
|
const sprites = function (input) {
|
||||||
|
let result = 0;
|
||||||
|
|
||||||
|
for (let i in input.children) {
|
||||||
|
if (input.children[i].hasOwnProperty('spriteInfo')) result++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {count: result};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts all blocks and generates a frequency count.
|
||||||
|
* @param {object} project Project object (SB2 format)
|
||||||
|
* @return {object} Block information
|
||||||
|
*/
|
||||||
|
const blocks = function (project) {
|
||||||
|
// Storage objects
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walk scripts array(s) and build block list.
|
||||||
|
* @param {array} stack Stack of blocks
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
const walk = function (stack) {
|
||||||
|
for (let i in stack) {
|
||||||
|
// Skip if item is not array
|
||||||
|
if (!Array.isArray(stack[i])) continue;
|
||||||
|
|
||||||
|
// Recurse if first item is not a string
|
||||||
|
if (typeof stack[i][0] !== 'string') {
|
||||||
|
walk(stack[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to block list
|
||||||
|
result.push(stack[i][0]);
|
||||||
|
|
||||||
|
// Don't pull in params from procedures
|
||||||
|
if (stack[i][0] === 'procDef') continue;
|
||||||
|
|
||||||
|
// Move to next item and walk
|
||||||
|
walk(stack[i].slice(1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(flatten(project, 'scripts'));
|
||||||
|
|
||||||
|
// Generate frequency count
|
||||||
|
const freq = utility.frequency(result);
|
||||||
|
|
||||||
|
// Build result and return
|
||||||
|
return {
|
||||||
|
count: result.length,
|
||||||
|
unique: Object.keys(freq).length,
|
||||||
|
id: result,
|
||||||
|
frequency: freq
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts extension information.
|
||||||
|
* @param {object} project Project object (SB2 format)
|
||||||
|
* @return {object} Extension information
|
||||||
|
*/
|
||||||
|
const extensions = function (project) {
|
||||||
|
const result = {count: 0, id: []};
|
||||||
|
const ext = project.info.savedExtensions;
|
||||||
|
|
||||||
|
// Check to ensure project includes any extensions
|
||||||
|
if (typeof ext === 'undefined') return result;
|
||||||
|
|
||||||
|
// Iterate over extensions and build list
|
||||||
|
for (let i in ext) {
|
||||||
|
result.id.push(ext[i].extensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count and return
|
||||||
|
result.count = result.id.length;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes a project and returns summary information about the project.
|
||||||
|
* @param {object} project Project object (SB2 format)
|
||||||
|
* @param {Function} callback Callback function
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
module.exports = function (project, callback) {
|
||||||
|
// Create metadata object
|
||||||
|
const meta = {
|
||||||
|
scripts: extract(project, 'scripts'),
|
||||||
|
variables: extract(project, 'variables', 'name'),
|
||||||
|
lists: extract(project, 'lists', 'listName'),
|
||||||
|
comments: extract(project, 'scriptComments'),
|
||||||
|
sounds: extract(project, 'sounds', 'soundName', 'md5'),
|
||||||
|
costumes: extract(project, 'costumes', 'costumeName', 'baseLayerMD5')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sprites
|
||||||
|
meta.sprites = sprites(project);
|
||||||
|
|
||||||
|
// Blocks
|
||||||
|
meta.blocks = blocks(project);
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
meta.extensions = extensions(project);
|
||||||
|
|
||||||
|
// Return all metadata
|
||||||
|
return callback(null, meta);
|
||||||
|
};
|
110
lib/sb3.js
Normal file
110
lib/sb3.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
const utility = require('./utility');
|
||||||
|
|
||||||
|
const scripts = function (targets) {
|
||||||
|
// Storage objects
|
||||||
|
let occurrences = 0;
|
||||||
|
|
||||||
|
// Iterate over all blocks in each target, and look for "top level" blocks
|
||||||
|
for (let t in targets) {
|
||||||
|
for (let b in targets[t].blocks) {
|
||||||
|
if (targets[t].blocks[b].topLevel) occurrences++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
count: occurrences
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const variables = function (targets, attribute) {
|
||||||
|
// Storage objects
|
||||||
|
let occurrences = 0;
|
||||||
|
let idList = [];
|
||||||
|
|
||||||
|
for (let t in targets) {
|
||||||
|
for (let a in targets[t][attribute]) {
|
||||||
|
const variable = targets[t][attribute][a];
|
||||||
|
occurrences++;
|
||||||
|
idList.push(variable[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
count: occurrences,
|
||||||
|
id: idList
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate over targets, extract attribute, and aggregate results
|
||||||
|
const extract = function (targets, attribute, id, hash) {
|
||||||
|
// Storage objects
|
||||||
|
let occurrences = 0;
|
||||||
|
let idList = [];
|
||||||
|
let hashList = [];
|
||||||
|
|
||||||
|
for (let t in targets) {
|
||||||
|
for (let a in targets[t][attribute]) {
|
||||||
|
const asset = targets[t][attribute][a];
|
||||||
|
occurrences++;
|
||||||
|
if (typeof id !== 'undefined') idList.push(asset[id]);
|
||||||
|
if (typeof hash !== 'undefined') hashList.push(asset[hash]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {count: occurrences};
|
||||||
|
if (typeof id !== 'undefined') result.id = idList;
|
||||||
|
if (typeof hash !== 'undefined') result.hash = hashList;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sprites = function (targets) {
|
||||||
|
return {
|
||||||
|
count: targets.length - 1
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const blocks = function (targets) {
|
||||||
|
// Storage object
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
for (let t in targets) {
|
||||||
|
for (let a in targets[t].blocks) {
|
||||||
|
const block = targets[t].blocks[a];
|
||||||
|
if (!block.shadow) result.push(block.opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate block frequency
|
||||||
|
const freq = utility.frequency(result);
|
||||||
|
|
||||||
|
// Return summary
|
||||||
|
return {
|
||||||
|
count: result.length,
|
||||||
|
unique: Object.keys(freq).length,
|
||||||
|
id: result,
|
||||||
|
frequency: freq
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const extensions = function (list) {
|
||||||
|
return {
|
||||||
|
count: list.length,
|
||||||
|
id: list
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function (project, callback) {
|
||||||
|
const meta = {
|
||||||
|
scripts: scripts(project.targets),
|
||||||
|
variables: variables(project.targets, 'variables'),
|
||||||
|
lists: variables(project.targets, 'lists'),
|
||||||
|
comments: extract(project.targets, 'comments'),
|
||||||
|
sounds: extract(project.targets, 'sounds', 'name', 'md5ext'),
|
||||||
|
costumes: extract(project.targets, 'costumes', 'name', 'md5ext'),
|
||||||
|
sprites: sprites(project.targets),
|
||||||
|
blocks: blocks(project.targets),
|
||||||
|
extensions: extensions(project.extensions)
|
||||||
|
};
|
||||||
|
|
||||||
|
callback(null, meta);
|
||||||
|
};
|
23
lib/utility.js
Normal file
23
lib/utility.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Utility methods for costructing Scratch project summaries.
|
||||||
|
*/
|
||||||
|
class Utility {
|
||||||
|
/**
|
||||||
|
* Tallies term frequency from an array of strings.
|
||||||
|
* @param {array} input Array of strings
|
||||||
|
* @return {object} Frequency information
|
||||||
|
*/
|
||||||
|
static frequency (input) {
|
||||||
|
const result = Object.create(null);
|
||||||
|
|
||||||
|
for (let i in input) {
|
||||||
|
var term = input[i];
|
||||||
|
if (typeof result[term] === 'undefined') result[term] = 0;
|
||||||
|
result[term]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Utility;
|
28
package.json
Normal file
28
package.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "scratch-analysis",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Analysis tool for summarizing the structure, composition, and complexity of Scratch programs.",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "npm run test:lint && npm run test:unit && npm run test:integration",
|
||||||
|
"test:lint": "eslint .",
|
||||||
|
"test:unit": "tap test/unit/*.js",
|
||||||
|
"test:integration": "tap test/integration/*.js",
|
||||||
|
"test:coverage": "tap test/{unit,integration}/*.js --coverage --coverage-report=lcov"
|
||||||
|
},
|
||||||
|
"author": "Scratch Foundation",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"scratch-parser": "4.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"eslint": "^5.10.0",
|
||||||
|
"eslint-config-scratch": "^5.0.0",
|
||||||
|
"tap": "^12.1.1"
|
||||||
|
}
|
||||||
|
}
|
BIN
test/fixtures/invalid/garbage.jpg
vendored
Normal file
BIN
test/fixtures/invalid/garbage.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/sb2/complex.sb2
vendored
Normal file
BIN
test/fixtures/sb2/complex.sb2
vendored
Normal file
Binary file not shown.
71
test/fixtures/sb2/default.json
vendored
Normal file
71
test/fixtures/sb2/default.json
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"objName": "Stage",
|
||||||
|
"sounds": [{
|
||||||
|
"soundName": "pop",
|
||||||
|
"soundID": -1,
|
||||||
|
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
|
||||||
|
"sampleCount": 258,
|
||||||
|
"rate": 11025,
|
||||||
|
"format": ""
|
||||||
|
}],
|
||||||
|
"costumes": [{
|
||||||
|
"costumeName": "backdrop1",
|
||||||
|
"baseLayerID": -1,
|
||||||
|
"baseLayerMD5": "739b5e2a2435f6e1ec2993791b423146.png",
|
||||||
|
"bitmapResolution": 1,
|
||||||
|
"rotationCenterX": 240,
|
||||||
|
"rotationCenterY": 180
|
||||||
|
}],
|
||||||
|
"currentCostumeIndex": 0,
|
||||||
|
"penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png",
|
||||||
|
"penLayerID": -1,
|
||||||
|
"tempoBPM": 60,
|
||||||
|
"videoAlpha": 0.5,
|
||||||
|
"children": [{
|
||||||
|
"objName": "Sprite1",
|
||||||
|
"sounds": [{
|
||||||
|
"soundName": "meow",
|
||||||
|
"soundID": -1,
|
||||||
|
"md5": "83c36d806dc92327b9e7049a565c6bff.wav",
|
||||||
|
"sampleCount": 18688,
|
||||||
|
"rate": 22050,
|
||||||
|
"format": ""
|
||||||
|
}],
|
||||||
|
"costumes": [{
|
||||||
|
"costumeName": "costume1",
|
||||||
|
"baseLayerID": -1,
|
||||||
|
"baseLayerMD5": "09dc888b0b7df19f70d81588ae73420e.svg",
|
||||||
|
"bitmapResolution": 1,
|
||||||
|
"rotationCenterX": 47,
|
||||||
|
"rotationCenterY": 55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"costumeName": "costume2",
|
||||||
|
"baseLayerID": -1,
|
||||||
|
"baseLayerMD5": "3696356a03a8d938318876a593572843.svg",
|
||||||
|
"bitmapResolution": 1,
|
||||||
|
"rotationCenterX": 47,
|
||||||
|
"rotationCenterY": 55
|
||||||
|
}],
|
||||||
|
"currentCostumeIndex": 0,
|
||||||
|
"scratchX": 0,
|
||||||
|
"scratchY": 0,
|
||||||
|
"scale": 1,
|
||||||
|
"direction": 90,
|
||||||
|
"rotationStyle": "normal",
|
||||||
|
"isDraggable": false,
|
||||||
|
"indexInLibrary": 1,
|
||||||
|
"visible": true,
|
||||||
|
"spriteInfo": {
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"info": {
|
||||||
|
"videoOn": false,
|
||||||
|
"userAgent": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/50.0.2661.102 Safari\/537.36",
|
||||||
|
"swfVersion": "v446",
|
||||||
|
"scriptCount": 0,
|
||||||
|
"spriteCount": 1,
|
||||||
|
"hasCloudData": false,
|
||||||
|
"flashVersion": "MAC 21,0,0,242"
|
||||||
|
}
|
||||||
|
}
|
BIN
test/fixtures/sb2/default.sb2
vendored
Normal file
BIN
test/fixtures/sb2/default.sb2
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/sb3/complex.sb3
vendored
Normal file
BIN
test/fixtures/sb3/complex.sb3
vendored
Normal file
Binary file not shown.
103
test/fixtures/sb3/default.json
vendored
Normal file
103
test/fixtures/sb3/default.json
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"isStage": true,
|
||||||
|
"name": "Stage",
|
||||||
|
"variables": {
|
||||||
|
"`jEk@4|i[#Fk?(8x)AV.-my variable": [
|
||||||
|
"my variable",
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lists": {},
|
||||||
|
"broadcasts": {},
|
||||||
|
"blocks": {},
|
||||||
|
"comments": {},
|
||||||
|
"currentCostume": 0,
|
||||||
|
"costumes": [
|
||||||
|
{
|
||||||
|
"assetId": "cd21514d0531fdffb22204e0ec5ed84a",
|
||||||
|
"name": "backdrop1",
|
||||||
|
"md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg",
|
||||||
|
"dataFormat": "svg",
|
||||||
|
"rotationCenterX": 240,
|
||||||
|
"rotationCenterY": 180
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sounds": [
|
||||||
|
{
|
||||||
|
"assetId": "83a9787d4cb6f3b7632b4ddfebf74367",
|
||||||
|
"name": "pop",
|
||||||
|
"dataFormat": "wav",
|
||||||
|
"format": "",
|
||||||
|
"rate": 44100,
|
||||||
|
"sampleCount": 1032,
|
||||||
|
"md5ext": "83a9787d4cb6f3b7632b4ddfebf74367.wav"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"volume": 100,
|
||||||
|
"layerOrder": 0,
|
||||||
|
"tempo": 60,
|
||||||
|
"videoTransparency": 50,
|
||||||
|
"videoState": "on",
|
||||||
|
"textToSpeechLanguage": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"isStage": false,
|
||||||
|
"name": "Sprite1",
|
||||||
|
"variables": {},
|
||||||
|
"lists": {},
|
||||||
|
"broadcasts": {},
|
||||||
|
"blocks": {},
|
||||||
|
"comments": {},
|
||||||
|
"currentCostume": 0,
|
||||||
|
"costumes": [
|
||||||
|
{
|
||||||
|
"assetId": "b7853f557e4426412e64bb3da6531a99",
|
||||||
|
"name": "costume1",
|
||||||
|
"bitmapResolution": 1,
|
||||||
|
"md5ext": "b7853f557e4426412e64bb3da6531a99.svg",
|
||||||
|
"dataFormat": "svg",
|
||||||
|
"rotationCenterX": 48,
|
||||||
|
"rotationCenterY": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"assetId": "e6ddc55a6ddd9cc9d84fe0b4c21e016f",
|
||||||
|
"name": "costume2",
|
||||||
|
"bitmapResolution": 1,
|
||||||
|
"md5ext": "e6ddc55a6ddd9cc9d84fe0b4c21e016f.svg",
|
||||||
|
"dataFormat": "svg",
|
||||||
|
"rotationCenterX": 46,
|
||||||
|
"rotationCenterY": 53
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sounds": [
|
||||||
|
{
|
||||||
|
"assetId": "83c36d806dc92327b9e7049a565c6bff",
|
||||||
|
"name": "Meow",
|
||||||
|
"dataFormat": "wav",
|
||||||
|
"format": "",
|
||||||
|
"rate": 44100,
|
||||||
|
"sampleCount": 37376,
|
||||||
|
"md5ext": "83c36d806dc92327b9e7049a565c6bff.wav"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"volume": 100,
|
||||||
|
"layerOrder": 1,
|
||||||
|
"visible": true,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"size": 100,
|
||||||
|
"direction": 90,
|
||||||
|
"draggable": false,
|
||||||
|
"rotationStyle": "all around"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"monitors": [],
|
||||||
|
"extensions": [],
|
||||||
|
"meta": {
|
||||||
|
"semver": "3.0.0",
|
||||||
|
"vm": "0.2.0-prerelease.20181217191056",
|
||||||
|
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36"
|
||||||
|
}
|
||||||
|
}
|
BIN
test/fixtures/sb3/default.sb3
vendored
Normal file
BIN
test/fixtures/sb3/default.sb3
vendored
Normal file
Binary file not shown.
24
test/unit/error.js
Normal file
24
test/unit/error.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const test = require('tap').test;
|
||||||
|
const analysis = require('../../lib/index');
|
||||||
|
|
||||||
|
const invalidBinary = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../fixtures/invalid/garbage.jpg')
|
||||||
|
);
|
||||||
|
|
||||||
|
test('invalid object', t => {
|
||||||
|
analysis('{}', (err, result) => {
|
||||||
|
t.type(err, 'object');
|
||||||
|
t.type(result, 'undefined');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid binary', t => {
|
||||||
|
analysis(invalidBinary, (err, result) => {
|
||||||
|
t.type(err, 'string');
|
||||||
|
t.type(result, 'undefined');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
256
test/unit/sb2.js
Normal file
256
test/unit/sb2.js
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const test = require('tap').test;
|
||||||
|
const analysis = require('../../lib/index');
|
||||||
|
|
||||||
|
const defaultObject = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../fixtures/sb2/default.json')
|
||||||
|
);
|
||||||
|
const defaultBinary = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../fixtures/sb2/default.sb2')
|
||||||
|
);
|
||||||
|
const complexBinary = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../fixtures/sb2/complex.sb2')
|
||||||
|
);
|
||||||
|
|
||||||
|
test('defalt (object)', t => {
|
||||||
|
analysis(defaultObject, (err, result) => {
|
||||||
|
t.true(typeof err === 'undefined' || err === null);
|
||||||
|
t.type(result, 'object');
|
||||||
|
|
||||||
|
t.type(result.scripts, 'object');
|
||||||
|
t.equal(result.scripts.count, 0);
|
||||||
|
|
||||||
|
t.type(result.variables, 'object');
|
||||||
|
t.equal(result.variables.count, 0);
|
||||||
|
t.deepEqual(result.variables.id, []);
|
||||||
|
|
||||||
|
t.type(result.lists, 'object');
|
||||||
|
t.equal(result.lists.count, 0);
|
||||||
|
t.deepEqual(result.lists.id, []);
|
||||||
|
|
||||||
|
t.type(result.comments, 'object');
|
||||||
|
t.equal(result.comments.count, 0);
|
||||||
|
|
||||||
|
t.type(result.sounds, 'object');
|
||||||
|
t.equal(result.sounds.count, 2);
|
||||||
|
t.deepEqual(result.sounds.id, [
|
||||||
|
'pop',
|
||||||
|
'meow'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.sounds.hash, [
|
||||||
|
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||||
|
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.costumes, 'object');
|
||||||
|
t.equal(result.costumes.count, 3);
|
||||||
|
t.deepEqual(result.costumes.id, [
|
||||||
|
'backdrop1',
|
||||||
|
'costume1',
|
||||||
|
'costume2'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.costumes.hash, [
|
||||||
|
'739b5e2a2435f6e1ec2993791b423146.png',
|
||||||
|
'09dc888b0b7df19f70d81588ae73420e.svg',
|
||||||
|
'3696356a03a8d938318876a593572843.svg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.sprites, 'object');
|
||||||
|
t.equal(result.sprites.count, 1);
|
||||||
|
|
||||||
|
t.type(result.blocks, 'object');
|
||||||
|
t.equal(result.blocks.count, 0);
|
||||||
|
t.equal(result.blocks.unique, 0);
|
||||||
|
t.deepEqual(result.blocks.id, []);
|
||||||
|
t.deepEqual(result.blocks.frequency, {});
|
||||||
|
|
||||||
|
t.type(result.extensions, 'object');
|
||||||
|
t.equal(result.extensions.count, 0);
|
||||||
|
t.deepEqual(result.extensions.id, []);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defalt (binary)', t => {
|
||||||
|
analysis(defaultBinary, (err, result) => {
|
||||||
|
t.true(typeof err === 'undefined' || err === null);
|
||||||
|
t.type(result, 'object');
|
||||||
|
|
||||||
|
t.type(result.scripts, 'object');
|
||||||
|
t.equal(result.scripts.count, 0);
|
||||||
|
|
||||||
|
t.type(result.variables, 'object');
|
||||||
|
t.equal(result.variables.count, 0);
|
||||||
|
t.deepEqual(result.variables.id, []);
|
||||||
|
|
||||||
|
t.type(result.lists, 'object');
|
||||||
|
t.equal(result.lists.count, 0);
|
||||||
|
t.deepEqual(result.lists.id, []);
|
||||||
|
|
||||||
|
t.type(result.comments, 'object');
|
||||||
|
t.equal(result.comments.count, 0);
|
||||||
|
|
||||||
|
t.type(result.sounds, 'object');
|
||||||
|
t.equal(result.sounds.count, 2);
|
||||||
|
t.deepEqual(result.sounds.id, [
|
||||||
|
'pop',
|
||||||
|
'meow'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.sounds.hash, [
|
||||||
|
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||||
|
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.costumes, 'object');
|
||||||
|
t.equal(result.costumes.count, 3);
|
||||||
|
t.deepEqual(result.costumes.id, [
|
||||||
|
'backdrop1',
|
||||||
|
'costume1',
|
||||||
|
'costume2'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.costumes.hash, [
|
||||||
|
'739b5e2a2435f6e1ec2993791b423146.png',
|
||||||
|
'f9a1c175dbe2e5dee472858dd30d16bb.svg',
|
||||||
|
'6e8bd9ae68fdb02b7e1e3df656a75635.svg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.sprites, 'object');
|
||||||
|
t.equal(result.sprites.count, 1);
|
||||||
|
|
||||||
|
t.type(result.blocks, 'object');
|
||||||
|
t.equal(result.blocks.count, 0);
|
||||||
|
t.equal(result.blocks.unique, 0);
|
||||||
|
t.deepEqual(result.blocks.id, []);
|
||||||
|
t.deepEqual(result.blocks.frequency, {});
|
||||||
|
|
||||||
|
t.type(result.extensions, 'object');
|
||||||
|
t.equal(result.extensions.count, 0);
|
||||||
|
t.deepEqual(result.extensions.id, []);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('complex (binary)', t => {
|
||||||
|
analysis(complexBinary, (err, result) => {
|
||||||
|
t.true(typeof err === 'undefined' || err === null);
|
||||||
|
t.type(result, 'object');
|
||||||
|
|
||||||
|
t.type(result.scripts, 'object');
|
||||||
|
t.equal(result.scripts.count, 6);
|
||||||
|
|
||||||
|
t.type(result.variables, 'object');
|
||||||
|
t.equal(result.variables.count, 2);
|
||||||
|
t.deepEqual(result.variables.id, [
|
||||||
|
'global',
|
||||||
|
'local'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.lists, 'object');
|
||||||
|
t.equal(result.lists.count, 2);
|
||||||
|
t.deepEqual(result.lists.id, [
|
||||||
|
'globallist',
|
||||||
|
'locallist'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.comments, 'object');
|
||||||
|
t.equal(result.comments.count, 0);
|
||||||
|
|
||||||
|
t.type(result.sounds, 'object');
|
||||||
|
t.equal(result.sounds.count, 2);
|
||||||
|
t.deepEqual(result.sounds.id, [
|
||||||
|
'pop',
|
||||||
|
'meow'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.sounds.hash, [
|
||||||
|
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||||
|
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.costumes, 'object');
|
||||||
|
t.equal(result.costumes.count, 3);
|
||||||
|
t.deepEqual(result.costumes.id, [
|
||||||
|
'backdrop1',
|
||||||
|
'costume1',
|
||||||
|
'costume2'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.costumes.hash, [
|
||||||
|
'5b465b3b07d39019109d8dc6d6ee6593.svg',
|
||||||
|
'f9a1c175dbe2e5dee472858dd30d16bb.svg',
|
||||||
|
'6e8bd9ae68fdb02b7e1e3df656a75635.svg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.sprites, 'object');
|
||||||
|
t.equal(result.sprites.count, 1);
|
||||||
|
|
||||||
|
t.type(result.blocks, 'object');
|
||||||
|
t.equal(result.blocks.count, 34);
|
||||||
|
t.equal(result.blocks.unique, 18);
|
||||||
|
t.deepEqual(result.blocks.id, [
|
||||||
|
'whenGreenFlag',
|
||||||
|
'doForever',
|
||||||
|
'changeGraphicEffect:by:',
|
||||||
|
'whenGreenFlag',
|
||||||
|
'deleteLine:ofList:',
|
||||||
|
'deleteLine:ofList:',
|
||||||
|
'doForever',
|
||||||
|
'forward:',
|
||||||
|
'turnRight:',
|
||||||
|
'randomFrom:to:',
|
||||||
|
'bounceOffEdge',
|
||||||
|
'whenGreenFlag',
|
||||||
|
'doForever',
|
||||||
|
'setGraphicEffect:to:',
|
||||||
|
'xpos',
|
||||||
|
'whenGreenFlag',
|
||||||
|
'doForever',
|
||||||
|
'call',
|
||||||
|
'randomFrom:to:',
|
||||||
|
'heading',
|
||||||
|
'randomFrom:to:',
|
||||||
|
'heading',
|
||||||
|
'procDef',
|
||||||
|
'setVar:to:',
|
||||||
|
'getParam',
|
||||||
|
'setVar:to:',
|
||||||
|
'getParam',
|
||||||
|
'append:toList:',
|
||||||
|
'getParam',
|
||||||
|
'append:toList:',
|
||||||
|
'getParam',
|
||||||
|
'LEGO WeDo 2.0\u001FwhenTilted',
|
||||||
|
'LEGO WeDo 2.0\u001FsetLED',
|
||||||
|
'randomFrom:to:'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.blocks.frequency, {
|
||||||
|
'LEGO WeDo 2.0\u001FsetLED': 1,
|
||||||
|
'LEGO WeDo 2.0\u001FwhenTilted': 1,
|
||||||
|
'bounceOffEdge': 1,
|
||||||
|
'call': 1,
|
||||||
|
'changeGraphicEffect:by:': 1,
|
||||||
|
'doForever': 4,
|
||||||
|
'deleteLine:ofList:': 2,
|
||||||
|
'forward:': 1,
|
||||||
|
'getParam': 4,
|
||||||
|
'heading': 2,
|
||||||
|
'procDef': 1,
|
||||||
|
'append:toList:': 2,
|
||||||
|
'randomFrom:to:': 4,
|
||||||
|
'setGraphicEffect:to:': 1,
|
||||||
|
'setVar:to:': 2,
|
||||||
|
'turnRight:': 1,
|
||||||
|
'whenGreenFlag': 4,
|
||||||
|
'xpos': 1
|
||||||
|
});
|
||||||
|
|
||||||
|
t.type(result.extensions, 'object');
|
||||||
|
t.equal(result.extensions.count, 1);
|
||||||
|
t.deepEqual(result.extensions.id, [
|
||||||
|
'LEGO WeDo 2.0'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
260
test/unit/sb3.js
Normal file
260
test/unit/sb3.js
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const test = require('tap').test;
|
||||||
|
const analysis = require('../../lib/index');
|
||||||
|
|
||||||
|
const defaultObject = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../fixtures/sb3/default.json')
|
||||||
|
);
|
||||||
|
const defaultBinary = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../fixtures/sb3/default.sb3')
|
||||||
|
);
|
||||||
|
const complexBinary = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../fixtures/sb3/complex.sb3')
|
||||||
|
);
|
||||||
|
|
||||||
|
test('defalt (object)', t => {
|
||||||
|
analysis(defaultObject, (err, result) => {
|
||||||
|
t.true(typeof err === 'undefined' || err === null);
|
||||||
|
t.type(result, 'object');
|
||||||
|
|
||||||
|
t.type(result.scripts, 'object');
|
||||||
|
t.equal(result.scripts.count, 0);
|
||||||
|
|
||||||
|
t.type(result.variables, 'object');
|
||||||
|
t.equal(result.variables.count, 1);
|
||||||
|
t.deepEqual(result.variables.id, [
|
||||||
|
'my variable'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.lists, 'object');
|
||||||
|
t.equal(result.lists.count, 0);
|
||||||
|
t.deepEqual(result.lists.id, []);
|
||||||
|
|
||||||
|
t.type(result.comments, 'object');
|
||||||
|
t.equal(result.comments.count, 0);
|
||||||
|
|
||||||
|
t.type(result.sounds, 'object');
|
||||||
|
t.equal(result.sounds.count, 2);
|
||||||
|
t.deepEqual(result.sounds.id, [
|
||||||
|
'pop',
|
||||||
|
'Meow'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.sounds.hash, [
|
||||||
|
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||||
|
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.costumes, 'object');
|
||||||
|
t.equal(result.costumes.count, 3);
|
||||||
|
t.deepEqual(result.costumes.id, [
|
||||||
|
'backdrop1',
|
||||||
|
'costume1',
|
||||||
|
'costume2'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.costumes.hash, [
|
||||||
|
'cd21514d0531fdffb22204e0ec5ed84a.svg',
|
||||||
|
'b7853f557e4426412e64bb3da6531a99.svg',
|
||||||
|
'e6ddc55a6ddd9cc9d84fe0b4c21e016f.svg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.sprites, 'object');
|
||||||
|
t.equal(result.sprites.count, 1);
|
||||||
|
|
||||||
|
t.type(result.blocks, 'object');
|
||||||
|
t.equal(result.blocks.count, 0);
|
||||||
|
t.equal(result.blocks.unique, 0);
|
||||||
|
t.deepEqual(result.blocks.id, []);
|
||||||
|
t.deepEqual(result.blocks.frequency, {});
|
||||||
|
|
||||||
|
t.type(result.extensions, 'object');
|
||||||
|
t.equal(result.extensions.count, 0);
|
||||||
|
t.deepEqual(result.extensions.id, []);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defalt (binary)', t => {
|
||||||
|
analysis(defaultBinary, (err, result) => {
|
||||||
|
t.true(typeof err === 'undefined' || err === null);
|
||||||
|
t.type(result, 'object');
|
||||||
|
|
||||||
|
t.type(result.scripts, 'object');
|
||||||
|
t.equal(result.scripts.count, 0);
|
||||||
|
|
||||||
|
t.type(result.variables, 'object');
|
||||||
|
t.equal(result.variables.count, 1);
|
||||||
|
t.deepEqual(result.variables.id, [
|
||||||
|
'my variable'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.lists, 'object');
|
||||||
|
t.equal(result.lists.count, 0);
|
||||||
|
t.deepEqual(result.lists.id, []);
|
||||||
|
|
||||||
|
t.type(result.comments, 'object');
|
||||||
|
t.equal(result.comments.count, 0);
|
||||||
|
|
||||||
|
t.type(result.sounds, 'object');
|
||||||
|
t.equal(result.sounds.count, 2);
|
||||||
|
t.deepEqual(result.sounds.id, [
|
||||||
|
'pop',
|
||||||
|
'Meow'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.sounds.hash, [
|
||||||
|
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||||
|
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.costumes, 'object');
|
||||||
|
t.equal(result.costumes.count, 3);
|
||||||
|
t.deepEqual(result.costumes.id, [
|
||||||
|
'backdrop1',
|
||||||
|
'costume1',
|
||||||
|
'costume2'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.costumes.hash, [
|
||||||
|
'cd21514d0531fdffb22204e0ec5ed84a.svg',
|
||||||
|
'b7853f557e4426412e64bb3da6531a99.svg',
|
||||||
|
'e6ddc55a6ddd9cc9d84fe0b4c21e016f.svg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.sprites, 'object');
|
||||||
|
t.equal(result.sprites.count, 1);
|
||||||
|
|
||||||
|
t.type(result.blocks, 'object');
|
||||||
|
t.equal(result.blocks.count, 0);
|
||||||
|
t.equal(result.blocks.unique, 0);
|
||||||
|
t.deepEqual(result.blocks.id, []);
|
||||||
|
t.deepEqual(result.blocks.frequency, {});
|
||||||
|
|
||||||
|
t.type(result.extensions, 'object');
|
||||||
|
t.equal(result.extensions.count, 0);
|
||||||
|
t.deepEqual(result.extensions.id, []);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('complex (binary)', t => {
|
||||||
|
analysis(complexBinary, (err, result) => {
|
||||||
|
t.true(typeof err === 'undefined' || err === null);
|
||||||
|
t.type(result, 'object');
|
||||||
|
|
||||||
|
t.type(result.scripts, 'object');
|
||||||
|
t.equal(result.scripts.count, 6);
|
||||||
|
|
||||||
|
t.type(result.variables, 'object');
|
||||||
|
t.equal(result.variables.count, 2);
|
||||||
|
t.deepEqual(result.variables.id, [
|
||||||
|
'global',
|
||||||
|
'local'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.lists, 'object');
|
||||||
|
t.equal(result.lists.count, 2);
|
||||||
|
t.deepEqual(result.lists.id, [
|
||||||
|
'globallist',
|
||||||
|
'locallist'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.comments, 'object');
|
||||||
|
t.equal(result.comments.count, 0);
|
||||||
|
|
||||||
|
t.type(result.sounds, 'object');
|
||||||
|
t.equal(result.sounds.count, 2);
|
||||||
|
t.deepEqual(result.sounds.id, [
|
||||||
|
'pop',
|
||||||
|
'meow'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.sounds.hash, [
|
||||||
|
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||||
|
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.costumes, 'object');
|
||||||
|
t.equal(result.costumes.count, 3);
|
||||||
|
t.deepEqual(result.costumes.id, [
|
||||||
|
'backdrop1',
|
||||||
|
'costume1',
|
||||||
|
'costume2'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.costumes.hash, [
|
||||||
|
'7633d36de03d1df75808f581bbccc742.svg',
|
||||||
|
'e6bcb4046c157f60c9f5c3bb5f299fce.svg',
|
||||||
|
'64208764c777be25d34d813dc0b743c7.svg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.type(result.sprites, 'object');
|
||||||
|
t.equal(result.sprites.count, 1);
|
||||||
|
|
||||||
|
t.type(result.blocks, 'object');
|
||||||
|
t.equal(result.blocks.count, 34);
|
||||||
|
t.equal(result.blocks.unique, 18);
|
||||||
|
t.deepEqual(result.blocks.id, [
|
||||||
|
'event_whenflagclicked',
|
||||||
|
'control_forever',
|
||||||
|
'looks_changeeffectby',
|
||||||
|
'event_whenflagclicked',
|
||||||
|
'data_deleteoflist',
|
||||||
|
'data_deleteoflist',
|
||||||
|
'control_forever',
|
||||||
|
'motion_movesteps',
|
||||||
|
'motion_turnright',
|
||||||
|
'operator_random',
|
||||||
|
'motion_ifonedgebounce',
|
||||||
|
'event_whenflagclicked',
|
||||||
|
'control_forever',
|
||||||
|
'looks_seteffectto',
|
||||||
|
'motion_xposition',
|
||||||
|
'event_whenflagclicked',
|
||||||
|
'control_forever',
|
||||||
|
'procedures_call',
|
||||||
|
'operator_random',
|
||||||
|
'motion_direction',
|
||||||
|
'operator_random',
|
||||||
|
'motion_direction',
|
||||||
|
'procedures_definition',
|
||||||
|
'data_setvariableto',
|
||||||
|
'argument_reporter_string_number',
|
||||||
|
'data_setvariableto',
|
||||||
|
'argument_reporter_string_number',
|
||||||
|
'data_addtolist',
|
||||||
|
'argument_reporter_string_number',
|
||||||
|
'data_addtolist',
|
||||||
|
'argument_reporter_string_number',
|
||||||
|
'wedo2_whenTilted',
|
||||||
|
'wedo2_setLightHue',
|
||||||
|
'operator_random'
|
||||||
|
]);
|
||||||
|
t.deepEqual(result.blocks.frequency, {
|
||||||
|
argument_reporter_string_number: 4,
|
||||||
|
control_forever: 4,
|
||||||
|
data_addtolist: 2,
|
||||||
|
data_deleteoflist: 2,
|
||||||
|
data_setvariableto: 2,
|
||||||
|
event_whenflagclicked: 4,
|
||||||
|
looks_changeeffectby: 1,
|
||||||
|
looks_seteffectto: 1,
|
||||||
|
motion_direction: 2,
|
||||||
|
motion_ifonedgebounce: 1,
|
||||||
|
motion_movesteps: 1,
|
||||||
|
motion_turnright: 1,
|
||||||
|
motion_xposition: 1,
|
||||||
|
operator_random: 4,
|
||||||
|
procedures_call: 1,
|
||||||
|
procedures_definition: 1,
|
||||||
|
wedo2_setLightHue: 1,
|
||||||
|
wedo2_whenTilted: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
t.type(result.extensions, 'object');
|
||||||
|
t.equal(result.extensions.count, 1);
|
||||||
|
t.deepEqual(result.extensions.id, [
|
||||||
|
'wedo2'
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
7
test/unit/spec.js
Normal file
7
test/unit/spec.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const test = require('tap').test;
|
||||||
|
const analysis = require('../../lib/index');
|
||||||
|
|
||||||
|
test('spec', t => {
|
||||||
|
t.type(analysis, 'function');
|
||||||
|
t.end();
|
||||||
|
});
|
19
test/unit/utility.js
Normal file
19
test/unit/utility.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const test = require('tap').test;
|
||||||
|
const utility = require('../../lib/utility');
|
||||||
|
|
||||||
|
test('spec', t => {
|
||||||
|
t.type(utility, 'function');
|
||||||
|
t.type(utility.frequency, 'function');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('frequency', t => {
|
||||||
|
const input = ['foo', 'foo', 'foo', 'bar', 'bar', 'baz'];
|
||||||
|
const result = utility.frequency(input);
|
||||||
|
t.deepEqual(result, {
|
||||||
|
foo: 3,
|
||||||
|
bar: 2,
|
||||||
|
baz: 1
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
Loading…
Reference in a new issue