mirror of
https://github.com/scratchfoundation/scratch-analysis.git
synced 2024-11-28 02:25:45 -05:00
commit
1db9e69c90
23 changed files with 591 additions and 84 deletions
|
@ -1,3 +1,7 @@
|
|||
{
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
"requireConfigFile": false
|
||||
},
|
||||
"extends": ["scratch", "scratch/node"]
|
||||
}
|
||||
|
|
21
.travis.yml
Normal file
21
.travis.yml
Normal file
|
@ -0,0 +1,21 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 16
|
||||
- lts/*
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
jobs:
|
||||
include:
|
||||
- stage: release
|
||||
node_js: 16
|
||||
script: echo deploying...
|
||||
deploy:
|
||||
- provider: script
|
||||
script: npx semantic-release --branches develop
|
||||
on:
|
||||
branch: develop
|
||||
stages:
|
||||
- test
|
||||
- name: release
|
||||
if: type != pull_request AND branch = develop
|
8
Dockerfile
Normal file
8
Dockerfile
Normal file
|
@ -0,0 +1,8 @@
|
|||
FROM node:16
|
||||
|
||||
RUN mkdir -p /var/app/current
|
||||
WORKDIR /var/app/current
|
||||
COPY . ./
|
||||
RUN rm -rf ./node_modules
|
||||
RUN npm install
|
||||
RUN npm install -g nodemon tap
|
|
@ -1,6 +1,9 @@
|
|||
## scratch-analysis
|
||||
#### Analysis tool for summarizing the structure, composition, and complexity of [Scratch](https://scratch.mit.edu) programs.
|
||||
|
||||
[![Build Status](https://travis-ci.org/LLK/scratch-analysis.svg?branch=develop)](https://travis-ci.org/LLK/scratch-analysis)
|
||||
[![Greenkeeper badge](https://badges.greenkeeper.io/LLK/scratch-analysis.svg)](https://greenkeeper.io/)
|
||||
|
||||
## Getting Started
|
||||
```bash
|
||||
npm install scratch-analysis
|
||||
|
@ -23,7 +26,8 @@ The `scratch-analysis` module will return an object containing high-level summar
|
|||
| `scripts` | `count` |
|
||||
| `blocks` | `count`, `unique`, `list`, `frequency` |
|
||||
| `sprites` | `count` |
|
||||
| `variables` | `count` |
|
||||
| `variables` | `count`, `id` |
|
||||
| `cloud` | `count`, `id` |
|
||||
| `lists` | `count` |
|
||||
| `costumes` | `count`, `list`, `hash` |
|
||||
| `sounds` | `count`, `list`, `hash` |
|
||||
|
|
1
TRADEMARK
Normal file
1
TRADEMARK
Normal file
|
@ -0,0 +1 @@
|
|||
The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission.
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
|
@ -0,0 +1,30 @@
|
|||
version: '3.4'
|
||||
volumes:
|
||||
npm_data:
|
||||
runtime_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: scratchapi_scratch_network
|
||||
|
||||
services:
|
||||
app:
|
||||
container_name: scratch-analysis-lib
|
||||
hostname: scratch-analysis
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
image: scratch-analysis:latest
|
||||
command: node -e "require('http').createServer((req, res) => { res.end('OK'); }).listen(8080, () => {console.log('Listening on 8080'); } );"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./
|
||||
target: /var/app/current
|
||||
consistency: cached
|
||||
volume:
|
||||
nocopy: true
|
||||
- npm_data:/var/app/current/node_modules
|
||||
- runtime_data:/runtime
|
||||
ports:
|
||||
- "9999:8080"
|
67
lib/sb2.js
67
lib/sb2.js
|
@ -77,7 +77,7 @@ const sprites = function (input) {
|
|||
let result = 0;
|
||||
|
||||
for (let i in input.children) {
|
||||
if (input.children[i].hasOwnProperty('spriteInfo')) result++;
|
||||
if (Object.prototype.hasOwnProperty.call(input.children[i], 'spriteInfo')) result++;
|
||||
}
|
||||
|
||||
return {count: result};
|
||||
|
@ -92,6 +92,24 @@ const blocks = function (project) {
|
|||
// Storage objects
|
||||
const result = [];
|
||||
|
||||
/**
|
||||
* Determine if a argument is the name of a known cloud variable.
|
||||
* @param {string} arg Argument (variable name)
|
||||
* @return {boolean} Is cloud variable?
|
||||
*/
|
||||
const isArgCloudVar = function (arg) {
|
||||
// Validate argument
|
||||
// @note "Hacked" inputs here could be objects (arrays)
|
||||
if (typeof arg !== 'string') return false;
|
||||
|
||||
// Iterate over global variables and check to see if arg matches
|
||||
for (let i in project.variables) {
|
||||
const variable = project.variables[i];
|
||||
if (variable.name === arg && variable.isPersistent) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk scripts array(s) and build block list.
|
||||
* @param {array} stack Stack of blocks
|
||||
|
@ -108,11 +126,20 @@ const blocks = function (project) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Get opcode and check variable manipulation for the presence of
|
||||
// cloud variables
|
||||
let opcode = stack[i][0];
|
||||
if (opcode === 'setVar:to:' || opcode === 'changeVar:by:') {
|
||||
if (isArgCloudVar(stack[i][1])) {
|
||||
opcode += 'cloud:';
|
||||
}
|
||||
}
|
||||
|
||||
// Add to block list
|
||||
result.push(stack[i][0]);
|
||||
result.push(opcode);
|
||||
|
||||
// Don't pull in params from procedures
|
||||
if (stack[i][0] === 'procDef') continue;
|
||||
if (opcode === 'procDef') continue;
|
||||
|
||||
// Move to next item and walk
|
||||
walk(stack[i].slice(1));
|
||||
|
@ -154,6 +181,35 @@ const extensions = function (project) {
|
|||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts cloud variable information.
|
||||
* @param {object} project Project object (SB2 format)
|
||||
* @param {array} names Names of all variables in project
|
||||
* @return {object} Cloud variable information
|
||||
*/
|
||||
const cloud = function (project, names) {
|
||||
const obj = [];
|
||||
|
||||
// Extract "isPersistent" parameter from all variables in project
|
||||
const cloudyness = extract(project, 'variables', 'isPersistent').id;
|
||||
|
||||
// Ensure that variable names and isPersistent parameter list are the same
|
||||
// length
|
||||
if (names.length !== cloudyness.length) return -1;
|
||||
|
||||
// Iterate over isPersistent values, and extract names of any that are true
|
||||
for (let i in cloudyness) {
|
||||
if (cloudyness[i]) {
|
||||
obj.push(names[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
count: obj.length,
|
||||
id: obj
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyzes a project and returns summary information about the project.
|
||||
* @param {object} project Project object (SB2 format)
|
||||
|
@ -171,6 +227,8 @@ module.exports = function (project, callback) {
|
|||
costumes: extract(project, 'costumes', 'costumeName', 'baseLayerMD5')
|
||||
};
|
||||
|
||||
meta.cloud = cloud(project, meta.variables.id);
|
||||
|
||||
// Sprites
|
||||
meta.sprites = sprites(project);
|
||||
|
||||
|
@ -180,6 +238,9 @@ module.exports = function (project, callback) {
|
|||
// Extensions
|
||||
meta.extensions = extensions(project);
|
||||
|
||||
// Metadata is only in sb3s so just fill in an empty object.
|
||||
meta.meta = {};
|
||||
|
||||
// Return all metadata
|
||||
return callback(null, meta);
|
||||
};
|
||||
|
|
50
lib/sb3.js
50
lib/sb3.js
|
@ -21,9 +21,14 @@ const variables = function (targets, attribute) {
|
|||
let occurrences = 0;
|
||||
let idList = [];
|
||||
|
||||
// Cloud variables are a type of variable
|
||||
const isCloud = (attribute === 'cloud');
|
||||
if (isCloud) attribute = 'variables';
|
||||
|
||||
for (let t in targets) {
|
||||
for (let a in targets[t][attribute]) {
|
||||
const variable = targets[t][attribute][a];
|
||||
if (isCloud && (variable.length !== 3 || !variable[2])) continue;
|
||||
occurrences++;
|
||||
idList.push(variable[0]);
|
||||
}
|
||||
|
@ -67,10 +72,37 @@ const blocks = function (targets) {
|
|||
// Storage object
|
||||
let result = [];
|
||||
|
||||
/**
|
||||
* Determine if a argument is the name of a known cloud variable.
|
||||
* @param {string} arg Argument (variable name)
|
||||
* @return {boolean} Is cloud variable?
|
||||
*/
|
||||
const isArgCloudVar = function (arg) {
|
||||
// Validate argument
|
||||
if (typeof arg !== 'string') return false;
|
||||
|
||||
// Check first target (stage) to determine if arg is a cloud variable id
|
||||
const stage = targets[0];
|
||||
if (typeof stage.variables[arg] !== 'undefined') {
|
||||
return stage.variables[arg].length === 3 && stage.variables[arg][2];
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over all targets and push block opcodes to storage object
|
||||
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);
|
||||
|
||||
// Get opcode and check variable manipulation for the presence of
|
||||
// cloud variables
|
||||
let opcode = block.opcode;
|
||||
if (opcode === 'data_setvariableto' || opcode === 'data_changevariableby') {
|
||||
if (isArgCloudVar(block.fields.VARIABLE[1])) {
|
||||
opcode += '_cloud';
|
||||
}
|
||||
}
|
||||
|
||||
if (!block.shadow) result.push(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,22 +120,32 @@ const blocks = function (targets) {
|
|||
|
||||
const extensions = function (list) {
|
||||
return {
|
||||
count: list.length,
|
||||
id: list
|
||||
count: (typeof list === 'object' ? list.length : 0),
|
||||
id: (typeof list === 'object' ? list : [])
|
||||
};
|
||||
};
|
||||
|
||||
const metadata = function (meta) {
|
||||
let obj = {};
|
||||
if (meta.origin) {
|
||||
obj.origin = meta.origin;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
module.exports = function (project, callback) {
|
||||
const meta = {
|
||||
scripts: scripts(project.targets),
|
||||
variables: variables(project.targets, 'variables'),
|
||||
cloud: variables(project.targets, 'cloud'),
|
||||
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)
|
||||
extensions: extensions(project.extensions),
|
||||
meta: metadata(project.meta)
|
||||
};
|
||||
|
||||
callback(null, meta);
|
||||
|
|
18
package.json
18
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "scratch-analysis",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Analysis tool for summarizing the structure, composition, and complexity of Scratch programs.",
|
||||
"main": "lib/index.js",
|
||||
"directories": {
|
||||
|
@ -8,21 +8,19 @@
|
|||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "npm run test:lint && npm run test:unit && npm run test:integration",
|
||||
"test": "npm run test:lint && npm run test:unit",
|
||||
"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"
|
||||
"test:unit": "tap --reporter nyan test/unit/*.js --statements=97 --branches=97"
|
||||
},
|
||||
"author": "Scratch Foundation",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"scratch-parser": "4.3.2"
|
||||
"scratch-parser": "5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.1",
|
||||
"eslint": "^5.10.0",
|
||||
"eslint-config-scratch": "^5.0.0",
|
||||
"tap": "^12.1.1"
|
||||
"@babel/eslint-parser": "^7.5.4",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-config-scratch": "^7.0.0",
|
||||
"tap": "^16.2.0"
|
||||
}
|
||||
}
|
||||
|
|
BIN
test/fixtures/sb2/cloud.sb2
vendored
Normal file
BIN
test/fixtures/sb2/cloud.sb2
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/sb2/cloud_complex.sb2
vendored
Normal file
BIN
test/fixtures/sb2/cloud_complex.sb2
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/sb2/cloud_opcodes.sb2
vendored
Normal file
BIN
test/fixtures/sb2/cloud_opcodes.sb2
vendored
Normal file
Binary file not shown.
103
test/fixtures/sb3/badExtensions.json
vendored
Normal file
103
test/fixtures/sb3/badExtensions.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": [],
|
||||
"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",
|
||||
"origin": "test.scratch.mit.edu"
|
||||
}
|
||||
}
|
BIN
test/fixtures/sb3/cloud.sb3
vendored
Normal file
BIN
test/fixtures/sb3/cloud.sb3
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/sb3/cloud_complex.sb3
vendored
Normal file
BIN
test/fixtures/sb3/cloud_complex.sb3
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/sb3/cloud_opcodes.sb3
vendored
Normal file
BIN
test/fixtures/sb3/cloud_opcodes.sb3
vendored
Normal file
Binary file not shown.
3
test/fixtures/sb3/default.json
vendored
3
test/fixtures/sb3/default.json
vendored
|
@ -98,6 +98,7 @@
|
|||
"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"
|
||||
"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",
|
||||
"origin": "test.scratch.mit.edu"
|
||||
}
|
||||
}
|
||||
|
|
BIN
test/fixtures/sb3/extensions.sb3
vendored
Normal file
BIN
test/fixtures/sb3/extensions.sb3
vendored
Normal file
Binary file not shown.
79
test/unit/cloud.js
Normal file
79
test/unit/cloud.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const test = require('tap').test;
|
||||
const analysis = require('../../lib/index');
|
||||
|
||||
const sb2 = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb2/cloud.sb2')
|
||||
);
|
||||
const sb3 = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb3/cloud.sb3')
|
||||
);
|
||||
const sb2Complex = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb2/cloud_complex.sb2')
|
||||
);
|
||||
const sb3Complex = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb3/cloud_complex.sb3')
|
||||
);
|
||||
|
||||
test('sb2', t => {
|
||||
analysis(sb2, (err, result) => {
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
t.type(result.cloud, 'object');
|
||||
t.equal(result.cloud.count, 1);
|
||||
t.same(result.cloud.id, ['☁ baz']);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('sb3', t => {
|
||||
analysis(sb3, (err, result) => {
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
t.type(result.cloud, 'object');
|
||||
t.equal(result.cloud.count, 1);
|
||||
t.same(result.cloud.id, ['☁ baz']);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('sb2 complex', t => {
|
||||
analysis(sb2Complex, (err, result) => {
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
t.type(result.cloud, 'object');
|
||||
t.equal(result.cloud.count, 8);
|
||||
t.same(result.cloud.id, [
|
||||
'☁ Player_1',
|
||||
'☁ Player_2',
|
||||
'☁ Player_3',
|
||||
'☁ Player_4',
|
||||
'☁ Player_5',
|
||||
'☁ GameData',
|
||||
'☁ Player_6',
|
||||
'☁ SAVE_DATA2'
|
||||
]);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('sb3 complex', t => {
|
||||
analysis(sb3Complex, (err, result) => {
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
t.type(result.cloud, 'object');
|
||||
t.equal(result.cloud.count, 8);
|
||||
t.same(result.cloud.id, [
|
||||
'☁ Player_1',
|
||||
'☁ Player_2',
|
||||
'☁ Player_3',
|
||||
'☁ Player_4',
|
||||
'☁ Player_5',
|
||||
'☁ GameData',
|
||||
'☁ Player_6',
|
||||
'☁ SAVE_DATA2'
|
||||
]);
|
||||
t.end();
|
||||
});
|
||||
});
|
59
test/unit/cloud_opcodes.js
Normal file
59
test/unit/cloud_opcodes.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const test = require('tap').test;
|
||||
const analysis = require('../../lib/index');
|
||||
|
||||
const sb2 = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb2/cloud_opcodes.sb2')
|
||||
);
|
||||
const sb3 = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb3/cloud_opcodes.sb3')
|
||||
);
|
||||
|
||||
test('sb2', t => {
|
||||
analysis(sb2, (err, result) => {
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
t.type(result.blocks, 'object');
|
||||
t.type(result.blocks.id, 'object');
|
||||
t.same(result.blocks.id, [
|
||||
'whenGreenFlag',
|
||||
'doForever',
|
||||
'setVar:to:',
|
||||
'randomFrom:to:',
|
||||
'changeVar:by:',
|
||||
'setVar:to:',
|
||||
'randomFrom:to:',
|
||||
'changeVar:by:',
|
||||
'setVar:to:cloud:',
|
||||
'randomFrom:to:',
|
||||
'changeVar:by:cloud:',
|
||||
'wait:elapsed:from:'
|
||||
]);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('sb3', t => {
|
||||
analysis(sb3, (err, result) => {
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
t.type(result.blocks, 'object');
|
||||
t.type(result.blocks.id, 'object');
|
||||
t.same(result.blocks.id, [
|
||||
'event_whenflagclicked',
|
||||
'control_forever',
|
||||
'control_wait',
|
||||
'data_setvariableto',
|
||||
'data_setvariableto',
|
||||
'data_setvariableto_cloud',
|
||||
'operator_random',
|
||||
'operator_random',
|
||||
'operator_random',
|
||||
'data_changevariableby',
|
||||
'data_changevariableby',
|
||||
'data_changevariableby_cloud'
|
||||
]);
|
||||
t.end();
|
||||
});
|
||||
});
|
|
@ -13,9 +13,9 @@ const complexBinary = fs.readFileSync(
|
|||
path.resolve(__dirname, '../fixtures/sb2/complex.sb2')
|
||||
);
|
||||
|
||||
test('defalt (object)', t => {
|
||||
test('default (object)', t => {
|
||||
analysis(defaultObject, (err, result) => {
|
||||
t.true(typeof err === 'undefined' || err === null);
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
|
||||
t.type(result.scripts, 'object');
|
||||
|
@ -23,34 +23,34 @@ test('defalt (object)', t => {
|
|||
|
||||
t.type(result.variables, 'object');
|
||||
t.equal(result.variables.count, 0);
|
||||
t.deepEqual(result.variables.id, []);
|
||||
t.same(result.variables.id, []);
|
||||
|
||||
t.type(result.lists, 'object');
|
||||
t.equal(result.lists.count, 0);
|
||||
t.deepEqual(result.lists.id, []);
|
||||
t.same(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, [
|
||||
t.same(result.sounds.id, [
|
||||
'pop',
|
||||
'meow'
|
||||
]);
|
||||
t.deepEqual(result.sounds.hash, [
|
||||
t.same(result.sounds.hash, [
|
||||
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||
]);
|
||||
|
||||
t.type(result.costumes, 'object');
|
||||
t.equal(result.costumes.count, 3);
|
||||
t.deepEqual(result.costumes.id, [
|
||||
t.same(result.costumes.id, [
|
||||
'backdrop1',
|
||||
'costume1',
|
||||
'costume2'
|
||||
]);
|
||||
t.deepEqual(result.costumes.hash, [
|
||||
t.same(result.costumes.hash, [
|
||||
'739b5e2a2435f6e1ec2993791b423146.png',
|
||||
'09dc888b0b7df19f70d81588ae73420e.svg',
|
||||
'3696356a03a8d938318876a593572843.svg'
|
||||
|
@ -62,20 +62,23 @@ test('defalt (object)', t => {
|
|||
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.same(result.blocks.id, []);
|
||||
t.same(result.blocks.frequency, {});
|
||||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 0);
|
||||
t.deepEqual(result.extensions.id, []);
|
||||
t.same(result.extensions.id, []);
|
||||
|
||||
t.type(result.meta, 'object');
|
||||
t.same(result.meta, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('defalt (binary)', t => {
|
||||
test('default (binary)', t => {
|
||||
analysis(defaultBinary, (err, result) => {
|
||||
t.true(typeof err === 'undefined' || err === null);
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
|
||||
t.type(result.scripts, 'object');
|
||||
|
@ -83,34 +86,34 @@ test('defalt (binary)', t => {
|
|||
|
||||
t.type(result.variables, 'object');
|
||||
t.equal(result.variables.count, 0);
|
||||
t.deepEqual(result.variables.id, []);
|
||||
t.same(result.variables.id, []);
|
||||
|
||||
t.type(result.lists, 'object');
|
||||
t.equal(result.lists.count, 0);
|
||||
t.deepEqual(result.lists.id, []);
|
||||
t.same(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, [
|
||||
t.same(result.sounds.id, [
|
||||
'pop',
|
||||
'meow'
|
||||
]);
|
||||
t.deepEqual(result.sounds.hash, [
|
||||
t.same(result.sounds.hash, [
|
||||
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||
]);
|
||||
|
||||
t.type(result.costumes, 'object');
|
||||
t.equal(result.costumes.count, 3);
|
||||
t.deepEqual(result.costumes.id, [
|
||||
t.same(result.costumes.id, [
|
||||
'backdrop1',
|
||||
'costume1',
|
||||
'costume2'
|
||||
]);
|
||||
t.deepEqual(result.costumes.hash, [
|
||||
t.same(result.costumes.hash, [
|
||||
'739b5e2a2435f6e1ec2993791b423146.png',
|
||||
'f9a1c175dbe2e5dee472858dd30d16bb.svg',
|
||||
'6e8bd9ae68fdb02b7e1e3df656a75635.svg'
|
||||
|
@ -122,12 +125,12 @@ test('defalt (binary)', t => {
|
|||
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.same(result.blocks.id, []);
|
||||
t.same(result.blocks.frequency, {});
|
||||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 0);
|
||||
t.deepEqual(result.extensions.id, []);
|
||||
t.same(result.extensions.id, []);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
@ -135,7 +138,7 @@ test('defalt (binary)', t => {
|
|||
|
||||
test('complex (binary)', t => {
|
||||
analysis(complexBinary, (err, result) => {
|
||||
t.true(typeof err === 'undefined' || err === null);
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
|
||||
t.type(result.scripts, 'object');
|
||||
|
@ -143,14 +146,14 @@ test('complex (binary)', t => {
|
|||
|
||||
t.type(result.variables, 'object');
|
||||
t.equal(result.variables.count, 2);
|
||||
t.deepEqual(result.variables.id, [
|
||||
t.same(result.variables.id, [
|
||||
'global',
|
||||
'local'
|
||||
]);
|
||||
|
||||
t.type(result.lists, 'object');
|
||||
t.equal(result.lists.count, 2);
|
||||
t.deepEqual(result.lists.id, [
|
||||
t.same(result.lists.id, [
|
||||
'globallist',
|
||||
'locallist'
|
||||
]);
|
||||
|
@ -160,23 +163,23 @@ test('complex (binary)', t => {
|
|||
|
||||
t.type(result.sounds, 'object');
|
||||
t.equal(result.sounds.count, 2);
|
||||
t.deepEqual(result.sounds.id, [
|
||||
t.same(result.sounds.id, [
|
||||
'pop',
|
||||
'meow'
|
||||
]);
|
||||
t.deepEqual(result.sounds.hash, [
|
||||
t.same(result.sounds.hash, [
|
||||
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||
]);
|
||||
|
||||
t.type(result.costumes, 'object');
|
||||
t.equal(result.costumes.count, 3);
|
||||
t.deepEqual(result.costumes.id, [
|
||||
t.same(result.costumes.id, [
|
||||
'backdrop1',
|
||||
'costume1',
|
||||
'costume2'
|
||||
]);
|
||||
t.deepEqual(result.costumes.hash, [
|
||||
t.same(result.costumes.hash, [
|
||||
'5b465b3b07d39019109d8dc6d6ee6593.svg',
|
||||
'f9a1c175dbe2e5dee472858dd30d16bb.svg',
|
||||
'6e8bd9ae68fdb02b7e1e3df656a75635.svg'
|
||||
|
@ -188,7 +191,7 @@ test('complex (binary)', t => {
|
|||
t.type(result.blocks, 'object');
|
||||
t.equal(result.blocks.count, 34);
|
||||
t.equal(result.blocks.unique, 18);
|
||||
t.deepEqual(result.blocks.id, [
|
||||
t.same(result.blocks.id, [
|
||||
'whenGreenFlag',
|
||||
'doForever',
|
||||
'changeGraphicEffect:by:',
|
||||
|
@ -224,7 +227,7 @@ test('complex (binary)', t => {
|
|||
'LEGO WeDo 2.0\u001FsetLED',
|
||||
'randomFrom:to:'
|
||||
]);
|
||||
t.deepEqual(result.blocks.frequency, {
|
||||
t.same(result.blocks.frequency, {
|
||||
'LEGO WeDo 2.0\u001FsetLED': 1,
|
||||
'LEGO WeDo 2.0\u001FwhenTilted': 1,
|
||||
'bounceOffEdge': 1,
|
||||
|
@ -247,7 +250,7 @@ test('complex (binary)', t => {
|
|||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 1);
|
||||
t.deepEqual(result.extensions.id, [
|
||||
t.same(result.extensions.id, [
|
||||
'LEGO WeDo 2.0'
|
||||
]);
|
||||
|
||||
|
|
157
test/unit/sb3.js
157
test/unit/sb3.js
|
@ -13,9 +13,17 @@ const complexBinary = fs.readFileSync(
|
|||
path.resolve(__dirname, '../fixtures/sb3/complex.sb3')
|
||||
);
|
||||
|
||||
test('defalt (object)', t => {
|
||||
const extensionsBinary = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb3/extensions.sb3')
|
||||
);
|
||||
|
||||
const badExtensions = fs.readFileSync(
|
||||
path.resolve(__dirname, '../fixtures/sb3/badExtensions.json')
|
||||
);
|
||||
|
||||
test('default (object)', t => {
|
||||
analysis(defaultObject, (err, result) => {
|
||||
t.true(typeof err === 'undefined' || err === null);
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
|
||||
t.type(result.scripts, 'object');
|
||||
|
@ -23,36 +31,36 @@ test('defalt (object)', t => {
|
|||
|
||||
t.type(result.variables, 'object');
|
||||
t.equal(result.variables.count, 1);
|
||||
t.deepEqual(result.variables.id, [
|
||||
t.same(result.variables.id, [
|
||||
'my variable'
|
||||
]);
|
||||
|
||||
t.type(result.lists, 'object');
|
||||
t.equal(result.lists.count, 0);
|
||||
t.deepEqual(result.lists.id, []);
|
||||
t.same(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, [
|
||||
t.same(result.sounds.id, [
|
||||
'pop',
|
||||
'Meow'
|
||||
]);
|
||||
t.deepEqual(result.sounds.hash, [
|
||||
t.same(result.sounds.hash, [
|
||||
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||
]);
|
||||
|
||||
t.type(result.costumes, 'object');
|
||||
t.equal(result.costumes.count, 3);
|
||||
t.deepEqual(result.costumes.id, [
|
||||
t.same(result.costumes.id, [
|
||||
'backdrop1',
|
||||
'costume1',
|
||||
'costume2'
|
||||
]);
|
||||
t.deepEqual(result.costumes.hash, [
|
||||
t.same(result.costumes.hash, [
|
||||
'cd21514d0531fdffb22204e0ec5ed84a.svg',
|
||||
'b7853f557e4426412e64bb3da6531a99.svg',
|
||||
'e6ddc55a6ddd9cc9d84fe0b4c21e016f.svg'
|
||||
|
@ -64,20 +72,22 @@ test('defalt (object)', t => {
|
|||
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.same(result.blocks.id, []);
|
||||
t.same(result.blocks.frequency, {});
|
||||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 0);
|
||||
t.deepEqual(result.extensions.id, []);
|
||||
t.same(result.extensions.id, []);
|
||||
|
||||
t.type(result.meta, 'object');
|
||||
t.equal(result.meta.origin, 'test.scratch.mit.edu');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('defalt (binary)', t => {
|
||||
test('default (binary)', t => {
|
||||
analysis(defaultBinary, (err, result) => {
|
||||
t.true(typeof err === 'undefined' || err === null);
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
|
||||
t.type(result.scripts, 'object');
|
||||
|
@ -85,36 +95,36 @@ test('defalt (binary)', t => {
|
|||
|
||||
t.type(result.variables, 'object');
|
||||
t.equal(result.variables.count, 1);
|
||||
t.deepEqual(result.variables.id, [
|
||||
t.same(result.variables.id, [
|
||||
'my variable'
|
||||
]);
|
||||
|
||||
t.type(result.lists, 'object');
|
||||
t.equal(result.lists.count, 0);
|
||||
t.deepEqual(result.lists.id, []);
|
||||
t.same(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, [
|
||||
t.same(result.sounds.id, [
|
||||
'pop',
|
||||
'Meow'
|
||||
]);
|
||||
t.deepEqual(result.sounds.hash, [
|
||||
t.same(result.sounds.hash, [
|
||||
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||
]);
|
||||
|
||||
t.type(result.costumes, 'object');
|
||||
t.equal(result.costumes.count, 3);
|
||||
t.deepEqual(result.costumes.id, [
|
||||
t.same(result.costumes.id, [
|
||||
'backdrop1',
|
||||
'costume1',
|
||||
'costume2'
|
||||
]);
|
||||
t.deepEqual(result.costumes.hash, [
|
||||
t.same(result.costumes.hash, [
|
||||
'cd21514d0531fdffb22204e0ec5ed84a.svg',
|
||||
'b7853f557e4426412e64bb3da6531a99.svg',
|
||||
'e6ddc55a6ddd9cc9d84fe0b4c21e016f.svg'
|
||||
|
@ -126,12 +136,15 @@ test('defalt (binary)', t => {
|
|||
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.same(result.blocks.id, []);
|
||||
t.same(result.blocks.frequency, {});
|
||||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 0);
|
||||
t.deepEqual(result.extensions.id, []);
|
||||
t.same(result.extensions.id, []);
|
||||
|
||||
t.type(result.meta, 'object');
|
||||
t.same({}, result.meta);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
@ -139,7 +152,7 @@ test('defalt (binary)', t => {
|
|||
|
||||
test('complex (binary)', t => {
|
||||
analysis(complexBinary, (err, result) => {
|
||||
t.true(typeof err === 'undefined' || err === null);
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
|
||||
t.type(result.scripts, 'object');
|
||||
|
@ -147,14 +160,14 @@ test('complex (binary)', t => {
|
|||
|
||||
t.type(result.variables, 'object');
|
||||
t.equal(result.variables.count, 2);
|
||||
t.deepEqual(result.variables.id, [
|
||||
t.same(result.variables.id, [
|
||||
'global',
|
||||
'local'
|
||||
]);
|
||||
|
||||
t.type(result.lists, 'object');
|
||||
t.equal(result.lists.count, 2);
|
||||
t.deepEqual(result.lists.id, [
|
||||
t.same(result.lists.id, [
|
||||
'globallist',
|
||||
'locallist'
|
||||
]);
|
||||
|
@ -164,23 +177,23 @@ test('complex (binary)', t => {
|
|||
|
||||
t.type(result.sounds, 'object');
|
||||
t.equal(result.sounds.count, 2);
|
||||
t.deepEqual(result.sounds.id, [
|
||||
t.same(result.sounds.id, [
|
||||
'pop',
|
||||
'meow'
|
||||
]);
|
||||
t.deepEqual(result.sounds.hash, [
|
||||
t.same(result.sounds.hash, [
|
||||
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||
]);
|
||||
|
||||
t.type(result.costumes, 'object');
|
||||
t.equal(result.costumes.count, 3);
|
||||
t.deepEqual(result.costumes.id, [
|
||||
t.same(result.costumes.id, [
|
||||
'backdrop1',
|
||||
'costume1',
|
||||
'costume2'
|
||||
]);
|
||||
t.deepEqual(result.costumes.hash, [
|
||||
t.same(result.costumes.hash, [
|
||||
'7633d36de03d1df75808f581bbccc742.svg',
|
||||
'e6bcb4046c157f60c9f5c3bb5f299fce.svg',
|
||||
'64208764c777be25d34d813dc0b743c7.svg'
|
||||
|
@ -192,7 +205,7 @@ test('complex (binary)', t => {
|
|||
t.type(result.blocks, 'object');
|
||||
t.equal(result.blocks.count, 34);
|
||||
t.equal(result.blocks.unique, 18);
|
||||
t.deepEqual(result.blocks.id, [
|
||||
t.same(result.blocks.id, [
|
||||
'event_whenflagclicked',
|
||||
'control_forever',
|
||||
'looks_changeeffectby',
|
||||
|
@ -228,7 +241,7 @@ test('complex (binary)', t => {
|
|||
'wedo2_setLightHue',
|
||||
'operator_random'
|
||||
]);
|
||||
t.deepEqual(result.blocks.frequency, {
|
||||
t.same(result.blocks.frequency, {
|
||||
argument_reporter_string_number: 4,
|
||||
control_forever: 4,
|
||||
data_addtolist: 2,
|
||||
|
@ -251,10 +264,90 @@ test('complex (binary)', t => {
|
|||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 1);
|
||||
t.deepEqual(result.extensions.id, [
|
||||
t.same(result.extensions.id, [
|
||||
'wedo2'
|
||||
]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('extensions', t => {
|
||||
analysis(extensionsBinary, (err, result) => {
|
||||
t.ok(typeof err === 'undefined' || err === null);
|
||||
t.type(result, 'object');
|
||||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 2);
|
||||
t.same(result.extensions.id, [
|
||||
'translate',
|
||||
'text2speech'
|
||||
]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('regression test IBE-198, a bad list does not break library', t => {
|
||||
analysis(badExtensions, (err, result) => {
|
||||
t.ok(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.same(result.variables.id, [
|
||||
'my variable'
|
||||
]);
|
||||
|
||||
t.type(result.lists, 'object');
|
||||
t.equal(result.lists.count, 0);
|
||||
t.same(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.same(result.sounds.id, [
|
||||
'pop',
|
||||
'Meow'
|
||||
]);
|
||||
t.same(result.sounds.hash, [
|
||||
'83a9787d4cb6f3b7632b4ddfebf74367.wav',
|
||||
'83c36d806dc92327b9e7049a565c6bff.wav'
|
||||
]);
|
||||
|
||||
t.type(result.costumes, 'object');
|
||||
t.equal(result.costumes.count, 3);
|
||||
t.same(result.costumes.id, [
|
||||
'backdrop1',
|
||||
'costume1',
|
||||
'costume2'
|
||||
]);
|
||||
t.same(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.same(result.blocks.id, []);
|
||||
t.same(result.blocks.frequency, {});
|
||||
|
||||
t.type(result.extensions, 'object');
|
||||
t.equal(result.extensions.count, 0);
|
||||
t.same(result.extensions.id, []);
|
||||
|
||||
t.type(result.meta, 'object');
|
||||
t.equal(result.meta.origin, 'test.scratch.mit.edu');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ test('spec', t => {
|
|||
test('frequency', t => {
|
||||
const input = ['foo', 'foo', 'foo', 'bar', 'bar', 'baz'];
|
||||
const result = utility.frequency(input);
|
||||
t.deepEqual(result, {
|
||||
t.same(result, {
|
||||
foo: 3,
|
||||
bar: 2,
|
||||
baz: 1
|
||||
|
|
Loading…
Reference in a new issue