mirror of
https://github.com/liabru/matter-js.git
synced 2025-02-07 19:06:26 -05:00
changed example tests to use jest
This commit is contained in:
parent
3c32969da2
commit
104d31902c
8 changed files with 225 additions and 936 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ test/browser/diffs
|
||||||
test/browser/refs
|
test/browser/refs
|
||||||
test/node/diffs
|
test/node/diffs
|
||||||
test/node/refs
|
test/node/refs
|
||||||
|
__snapshots__
|
|
@ -81,13 +81,17 @@ gulp.task('release', callback => {
|
||||||
message: 'cannot build release as there are uncomitted changes'
|
message: 'cannot build release as there are uncomitted changes'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
sequence('release:test', 'bump', 'release:build', 'doc', 'changelog', callback);
|
sequence(
|
||||||
|
'release:lint', 'bump', 'release:build', 'release:test',
|
||||||
|
'doc', 'changelog', callback
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('release:test', shell('npm run test'));
|
gulp.task('release:lint', shell('npm run lint'));
|
||||||
gulp.task('release:build', shell('npm run build'));
|
gulp.task('release:build', shell('npm run build'));
|
||||||
|
gulp.task('release:test', shell('TEST_BUILD=true npm run test'));
|
||||||
gulp.task('release:push:git', shell('git push'));
|
gulp.task('release:push:git', shell('git push'));
|
||||||
gulp.task('release:push:npm', shell('npm publish'));
|
gulp.task('release:push:npm', shell('npm publish'));
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
"build-examples": "webpack --config webpack.examples.config.js --mode=production --env.EDGE & webpack --config webpack.examples.config.js --mode=production --env.MINIMIZE --env.EDGE",
|
"build-examples": "webpack --config webpack.examples.config.js --mode=production --env.EDGE & webpack --config webpack.examples.config.js --mode=production --env.MINIMIZE --env.EDGE",
|
||||||
"lint": "eslint 'src/**/*.js' 'demo/js/Demo.js' 'examples/*.js'",
|
"lint": "eslint 'src/**/*.js' 'demo/js/Demo.js' 'examples/*.js'",
|
||||||
"doc": "gulp doc",
|
"doc": "gulp doc",
|
||||||
|
"test": "jest",
|
||||||
|
"test-snapshot": "TEST_SNAPSHOTS=true jest --ci",
|
||||||
|
"test-snapshot-update": "TEST_SNAPSHOTS=true jest -u"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
118
test/Common.js
Normal file
118
test/Common.js
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
const { Composite, Constraint, Vertices } = require('../src/module/main');
|
||||||
|
|
||||||
|
const includeKeys = [
|
||||||
|
// Common
|
||||||
|
'id', 'label',
|
||||||
|
|
||||||
|
// Constraint
|
||||||
|
'angularStiffness', 'bodyA', 'bodyB', 'pointA', 'pointB', 'damping', 'length', 'stiffness',
|
||||||
|
|
||||||
|
// Body
|
||||||
|
'angle', 'anglePrev', 'area', 'axes', 'bounds', 'min', 'max', 'x', 'y', 'collisionFilter', 'category', 'mask',
|
||||||
|
'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor',
|
||||||
|
'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'position', 'positionPrev', 'restitution', 'sleepThreshold', 'slop',
|
||||||
|
'timeScale', 'vertices'
|
||||||
|
];
|
||||||
|
|
||||||
|
const limit = (val, precision=3) => {
|
||||||
|
if (typeof val === 'number') {
|
||||||
|
return parseFloat(val.toPrecision(precision));
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
};
|
||||||
|
|
||||||
|
const engineSnapshot = (engine, extended=false) => {
|
||||||
|
const {
|
||||||
|
positionIterations, velocityIterations,
|
||||||
|
constraintIterations, world
|
||||||
|
} = engine;
|
||||||
|
|
||||||
|
const bodies = Composite.allBodies(world);
|
||||||
|
const constraints = Composite.allConstraints(world);
|
||||||
|
const composites = Composite.allComposites(world);
|
||||||
|
|
||||||
|
return {
|
||||||
|
positionIterations,
|
||||||
|
velocityIterations,
|
||||||
|
constraintIterations,
|
||||||
|
bodyCount: bodies.length,
|
||||||
|
constraintCount: constraints.length,
|
||||||
|
compositeCount: composites.length,
|
||||||
|
averageBodyPosition: Vertices.mean(bodies.map(body => body.position)),
|
||||||
|
averageBodyPositionPrev: Vertices.mean(bodies.map(body => body.positionPrev)),
|
||||||
|
averageBodyAngle: bodies.reduce((angle, body) => angle + body.angle, 0) / bodies.length,
|
||||||
|
averageBodyAnglePrev: bodies.reduce((angle, body) => angle + body.anglePrev, 0) / bodies.length,
|
||||||
|
averageConstraintPosition: Vertices.mean(
|
||||||
|
constraints.reduce((positions, constraint) => {
|
||||||
|
positions.push(
|
||||||
|
Constraint.pointAWorld(constraint),
|
||||||
|
Constraint.pointBWorld(constraint)
|
||||||
|
);
|
||||||
|
return positions;
|
||||||
|
}, []).concat({ x: 0, y: 0 })
|
||||||
|
),
|
||||||
|
world: extended ? worldSnapshotExtended(engine.world) : worldSnapshot(engine.world)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const worldSnapshot = world => ({
|
||||||
|
...Composite.allBodies(world).reduce((bodies, body) => {
|
||||||
|
bodies[`${body.id} ${body.label}`] =
|
||||||
|
`${limit(body.position.x)} ${limit(body.position.y)} ${limit(body.angle)}`
|
||||||
|
+ ` ${limit(body.position.x - body.positionPrev.x)} ${limit(body.position.y - body.positionPrev.y)}`
|
||||||
|
+ ` ${limit(body.angle - body.anglePrev)}`;
|
||||||
|
return bodies;
|
||||||
|
}, {}),
|
||||||
|
...Composite.allConstraints(world).reduce((constraints, constraint) => {
|
||||||
|
const positionA = Constraint.pointAWorld(constraint);
|
||||||
|
const positionB = Constraint.pointBWorld(constraint);
|
||||||
|
|
||||||
|
constraints[`${constraint.id} ${constraint.label}`] =
|
||||||
|
`${limit(positionA.x)} ${limit(positionA.y)} ${limit(positionB.x)} ${limit(positionB.y)}`
|
||||||
|
+ ` ${constraint.bodyA ? constraint.bodyA.id : null} ${constraint.bodyB ? constraint.bodyB.id : null}`;
|
||||||
|
|
||||||
|
return constraints;
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
|
|
||||||
|
const worldSnapshotExtended = world => worldSnapshotExtendedBase({
|
||||||
|
...Composite.allBodies(world).reduce((bodies, body) => {
|
||||||
|
bodies[body.id] = body;
|
||||||
|
return bodies;
|
||||||
|
}, {}),
|
||||||
|
...Composite.allConstraints(world).reduce((constraints, constraint) => {
|
||||||
|
constraints[constraint.id] = constraint;
|
||||||
|
return constraints;
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
|
|
||||||
|
const worldSnapshotExtendedBase = (obj, depth=0) => {
|
||||||
|
if (typeof obj === 'number') {
|
||||||
|
return limit(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map(item => worldSnapshotExtendedBase(item, depth + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj !== 'object') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(obj)
|
||||||
|
.filter(([key]) => depth === 0 || includeKeys.includes(key))
|
||||||
|
.reduce((cleaned, [key, val]) => {
|
||||||
|
if (val && val.id && String(val.id) !== key) {
|
||||||
|
val = val.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
val = `[${val.length}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...cleaned, [key]: worldSnapshotExtendedBase(val, depth + 1) };
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { engineSnapshot };
|
96
test/Examples.spec.js
Normal file
96
test/Examples.spec.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Common = require('./Common');
|
||||||
|
const fs = require('fs');
|
||||||
|
const execSync = require('child_process').execSync;
|
||||||
|
const useSnapshots = process.env.TEST_SNAPSHOTS === 'true';
|
||||||
|
const useBuild = process.env.TEST_BUILD === 'true';
|
||||||
|
|
||||||
|
console.info(`Testing Matter from ${useBuild ? `build '../build/matter'.` : `source '../src/module/main'.`}`);
|
||||||
|
|
||||||
|
// mock modules
|
||||||
|
if (useBuild) {
|
||||||
|
jest.mock('matter-js', () => require('../build/matter'), { virtual: true });
|
||||||
|
} else {
|
||||||
|
jest.mock('matter-js', () => require('../src/module/main'), { virtual: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mock('matter-wrap', () => require('../demo/lib/matter-wrap'), { virtual: true });
|
||||||
|
jest.mock('poly-decomp', () => require('../demo/lib/decomp'), { virtual: true });
|
||||||
|
|
||||||
|
// import mocked Matter and plugins
|
||||||
|
const Matter = global.Matter = require('matter-js');
|
||||||
|
Matter.Plugin.register(require('matter-wrap'));
|
||||||
|
|
||||||
|
// import Examples after Matter
|
||||||
|
const Example = require('../examples/index');
|
||||||
|
|
||||||
|
// stub out browser-only functions
|
||||||
|
const noop = () => ({ collisionFilter: {}, mouse: {} });
|
||||||
|
Matter.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}});
|
||||||
|
Matter.Render.run = Matter.Render.lookAt = noop;
|
||||||
|
Matter.Runner.create = Matter.Runner.run = noop;
|
||||||
|
Matter.MouseConstraint.create = Matter.Mouse.create = noop;
|
||||||
|
Matter.Common.log = Matter.Common.info = Matter.Common.warn = noop;
|
||||||
|
|
||||||
|
// check initial snapshots if enabled (experimental)
|
||||||
|
if (useSnapshots && !fs.existsSync('./test/__snapshots__')) {
|
||||||
|
const gitState = execSync('git log -n 1 --pretty=%d HEAD').toString().trim();
|
||||||
|
const gitIsClean = execSync('git status --porcelain').toString().trim().length === 0;
|
||||||
|
const gitIsMaster = gitState.startsWith('(HEAD -> master, origin/master');
|
||||||
|
|
||||||
|
if (!gitIsMaster || !gitIsClean) {
|
||||||
|
throw `Snapshots are experimental and are not currently committed due to size.
|
||||||
|
Stash changes and switch to HEAD on origin/master.
|
||||||
|
Use 'npm run test-snapshot-update' to generate initial snapshots.
|
||||||
|
Then run 'npm run test-snapshot' to test against these snapshots.
|
||||||
|
Currently on ${gitState}.
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent examples from logging
|
||||||
|
const consoleOriginal = console;
|
||||||
|
beforeEach(() => { global.console = { log: noop }; });
|
||||||
|
afterEach(() => { global.console = consoleOriginal; });
|
||||||
|
|
||||||
|
// list the examples to test
|
||||||
|
const examplesExtended = ['constraints'];
|
||||||
|
const examples = [
|
||||||
|
'airFriction', 'ballPool', 'bridge', 'broadphase', 'car', 'catapult', 'chains', 'circleStack',
|
||||||
|
'cloth', 'collisionFiltering', 'compositeManipulation', 'compound', 'compoundStack', 'concave',
|
||||||
|
'constraints', 'doublePendulum', 'events', 'friction', 'gravity', 'gyro', 'manipulation', 'mixed',
|
||||||
|
'newtonsCradle', 'ragdoll', 'pyramid', 'raycasting', 'restitution', 'rounded', 'sensors', 'sleeping',
|
||||||
|
'slingshot', 'softBody', 'sprites', 'stack', 'staticFriction', 'timescale', 'views', 'wreckingBall'
|
||||||
|
];
|
||||||
|
|
||||||
|
// perform integration tests using listed examples
|
||||||
|
const testName = `Example.%s simulates without throwing${useSnapshots ? ' and matches snapshot' : ''}`;
|
||||||
|
test.each(examples.map(key => [key]))(testName, exampleName => {
|
||||||
|
let engine, startSnapshot, endSnapshot;
|
||||||
|
|
||||||
|
const simulate = () => {
|
||||||
|
const example = Example[exampleName]();
|
||||||
|
const extended = examplesExtended.includes(exampleName);
|
||||||
|
engine = example.engine;
|
||||||
|
startSnapshot = Common.engineSnapshot(engine, extended);
|
||||||
|
|
||||||
|
for (let i = 0; i < 100; i += 1) {
|
||||||
|
Matter.Engine.update(engine, 1000 / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
endSnapshot = Common.engineSnapshot(engine, extended);
|
||||||
|
};
|
||||||
|
|
||||||
|
// simulate and assert nothing is thrown
|
||||||
|
expect(simulate).not.toThrow();
|
||||||
|
|
||||||
|
// assert there has been some change to the world
|
||||||
|
expect(startSnapshot.world).not.toEqual(endSnapshot.world);
|
||||||
|
|
||||||
|
// compare to stored snapshot (experimental)
|
||||||
|
if (useSnapshots) {
|
||||||
|
expect(endSnapshot).toMatchSnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
|
@ -1,206 +0,0 @@
|
||||||
var page = require('webpage').create();
|
|
||||||
var fs = require('fs');
|
|
||||||
var Resurrect = require('../lib/resurrect');
|
|
||||||
var compare = require('fast-json-patch').compare;
|
|
||||||
var system = require('system');
|
|
||||||
|
|
||||||
var demo,
|
|
||||||
frames = 10,
|
|
||||||
testUrl = 'http://localhost:8000/demo/index.html',
|
|
||||||
refsPath = 'test/browser/refs',
|
|
||||||
diffsPath = 'test/browser/diffs';
|
|
||||||
|
|
||||||
var update = arg('--update'),
|
|
||||||
updateAll = typeof arg('--updateAll') !== 'undefined',
|
|
||||||
diff = arg('--diff');
|
|
||||||
|
|
||||||
var resurrect = new Resurrect({ cleanup: true, revive: false }),
|
|
||||||
created = [],
|
|
||||||
changed = [];
|
|
||||||
|
|
||||||
var test = function(status) {
|
|
||||||
if (status === 'fail') {
|
|
||||||
console.log('failed to load', testUrl);
|
|
||||||
console.log('check dev server is running!');
|
|
||||||
console.log('use `grunt dev`');
|
|
||||||
phantom.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var demos = page.evaluate(function() {
|
|
||||||
var demoSelect = document.getElementById('demo-select'),
|
|
||||||
options = Array.prototype.slice.call(demoSelect);
|
|
||||||
return options.map(function(o) { return o.value; });
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.removeTree(diffsPath);
|
|
||||||
|
|
||||||
if (diff) {
|
|
||||||
fs.makeDirectory(diffsPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < demos.length; i += 1) {
|
|
||||||
demo = demos[i];
|
|
||||||
|
|
||||||
var hasChanged = false,
|
|
||||||
hasCreated = false,
|
|
||||||
forceUpdate = update === demo || updateAll,
|
|
||||||
worldStartPath = refsPath + '/' + demo + '/' + demo + '-0.json',
|
|
||||||
worldEndPath = refsPath + '/' + demo + '/' + demo + '-' + frames + '.json',
|
|
||||||
worldStartDiffPath = diffsPath + '/' + demo + '/' + demo + '-0.json',
|
|
||||||
worldEndDiffPath = diffsPath + '/' + demo + '/' + demo + '-' + frames + '.json';
|
|
||||||
|
|
||||||
var worldStart = page.evaluate(function(demo) {
|
|
||||||
if (!(demo in Matter.Example)) {
|
|
||||||
throw '\'' + demo + '\' is not defined in Matter.Demo';
|
|
||||||
}
|
|
||||||
|
|
||||||
var _demo = Matter.Demo.create(),
|
|
||||||
engine = Matter.Example.engine(_demo),
|
|
||||||
runner = Matter.Runner.create();
|
|
||||||
|
|
||||||
Matter.Demo._demo = _demo;
|
|
||||||
_demo.engine = engine;
|
|
||||||
_demo.engine.render = {};
|
|
||||||
_demo.engine.render.options = {};
|
|
||||||
_demo.runner = runner;
|
|
||||||
_demo.render = { options: {} };
|
|
||||||
_demo.mouseConstraint = Matter.MouseConstraint.create(engine);
|
|
||||||
|
|
||||||
Matter.Demo.reset(_demo);
|
|
||||||
Matter.Example[demo](_demo);
|
|
||||||
|
|
||||||
return engine.world;
|
|
||||||
}, demo);
|
|
||||||
|
|
||||||
var worldEnd = page.evaluate(function(demo, frames) {
|
|
||||||
var engine = Matter.Demo._demo.engine,
|
|
||||||
runner = Matter.Demo._demo.runner;
|
|
||||||
|
|
||||||
for (var j = 0; j <= frames; j += 1) {
|
|
||||||
Matter.Runner.tick(runner, engine, j * runner.delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine.world;
|
|
||||||
}, demo, frames);
|
|
||||||
|
|
||||||
worldEnd = JSON.parse(resurrect.stringify(worldEnd, precisionLimiter));
|
|
||||||
worldStart = JSON.parse(resurrect.stringify(worldStart, precisionLimiter));
|
|
||||||
|
|
||||||
if (fs.exists(worldStartPath)) {
|
|
||||||
var worldStartRef = JSON.parse(fs.read(worldStartPath));
|
|
||||||
var worldStartDiff = compare(worldStartRef, worldStart);
|
|
||||||
|
|
||||||
if (worldStartDiff.length !== 0) {
|
|
||||||
if (diff) {
|
|
||||||
fs.write(worldStartDiffPath, JSON.stringify(worldStartDiff, precisionLimiter, 2), 'w');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceUpdate) {
|
|
||||||
hasCreated = true;
|
|
||||||
fs.write(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2), 'w');
|
|
||||||
} else {
|
|
||||||
hasChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasCreated = true;
|
|
||||||
fs.write(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2), 'w');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.exists(worldEndPath)) {
|
|
||||||
var worldEndRef = JSON.parse(fs.read(worldEndPath));
|
|
||||||
var worldEndDiff = compare(worldEndRef, worldEnd);
|
|
||||||
|
|
||||||
if (worldEndDiff.length !== 0) {
|
|
||||||
if (diff) {
|
|
||||||
fs.write(worldEndDiffPath, JSON.stringify(worldEndDiff, precisionLimiter, 2), 'w');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceUpdate) {
|
|
||||||
hasCreated = true;
|
|
||||||
fs.write(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2), 'w');
|
|
||||||
} else {
|
|
||||||
hasChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasCreated = true;
|
|
||||||
fs.write(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2), 'w');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasChanged) {
|
|
||||||
changed.push("'" + demo + "'");
|
|
||||||
system.stdout.write('x');
|
|
||||||
} else if (hasCreated) {
|
|
||||||
created.push("'" + demo + "'");
|
|
||||||
system.stdout.write('+');
|
|
||||||
} else {
|
|
||||||
system.stdout.write('.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (created.length > 0) {
|
|
||||||
console.log('\nupdated', created.join(', '));
|
|
||||||
}
|
|
||||||
|
|
||||||
var isOk = changed.length === 0 ? 1 : 0;
|
|
||||||
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
if (isOk) {
|
|
||||||
console.log('ok');
|
|
||||||
} else {
|
|
||||||
console.log('\nchanges detected on:');
|
|
||||||
console.log(changed.join(', '));
|
|
||||||
console.log('\nreview, then --update [name] or --updateAll');
|
|
||||||
console.log('use --diff for diff log');
|
|
||||||
}
|
|
||||||
|
|
||||||
phantom.exit(!isOk);
|
|
||||||
};
|
|
||||||
|
|
||||||
var precisionLimiter = function(key, value) {
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
return parseFloat(value.toFixed(5));
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
function arg(name) {
|
|
||||||
var index = system.args.indexOf(name);
|
|
||||||
if (index >= 0) {
|
|
||||||
return system.args[index + 1] || true;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
page.onError = function(msg, trace) {
|
|
||||||
setTimeout(function() {
|
|
||||||
var msgStack = ['testing \'' + demo + '\'', msg];
|
|
||||||
|
|
||||||
if (trace && trace.length) {
|
|
||||||
trace.forEach(function(t) {
|
|
||||||
msgStack.push(' at ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (fn: ' + t.function +')' : ''));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(msgStack.join('\n'));
|
|
||||||
phantom.exit(1);
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
page.onResourceReceived = function(res) {
|
|
||||||
setTimeout(function() {
|
|
||||||
if (res.stage === 'end'
|
|
||||||
&& (res.status !== 304 && res.status !== 200 && res.status !== null)) {
|
|
||||||
console.log('error', res.status, res.url);
|
|
||||||
phantom.exit(1);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
phantom.onError = page.onError;
|
|
||||||
|
|
||||||
page.open(testUrl, test);
|
|
|
@ -1,542 +0,0 @@
|
||||||
/**
|
|
||||||
* # ResurrectJS
|
|
||||||
* @version 1.0.3
|
|
||||||
* @license Public Domain
|
|
||||||
*
|
|
||||||
* ResurrectJS preserves object behavior (prototypes) and reference
|
|
||||||
* circularity with a special JSON encoding. Unlike regular JSON,
|
|
||||||
* Date, RegExp, DOM objects, and `undefined` are also properly
|
|
||||||
* preserved.
|
|
||||||
*
|
|
||||||
* ## Examples
|
|
||||||
*
|
|
||||||
* function Foo() {}
|
|
||||||
* Foo.prototype.greet = function() { return "hello"; };
|
|
||||||
*
|
|
||||||
* // Behavior is preserved:
|
|
||||||
* var necromancer = new Resurrect();
|
|
||||||
* var json = necromancer.stringify(new Foo());
|
|
||||||
* var foo = necromancer.resurrect(json);
|
|
||||||
* foo.greet(); // => "hello"
|
|
||||||
*
|
|
||||||
* // References to the same object are preserved:
|
|
||||||
* json = necromancer.stringify([foo, foo]);
|
|
||||||
* var array = necromancer.resurrect(json);
|
|
||||||
* array[0] === array[1]; // => true
|
|
||||||
* array[1].greet(); // => "hello"
|
|
||||||
*
|
|
||||||
* // Dates are restored properly
|
|
||||||
* json = necromancer.stringify(new Date());
|
|
||||||
* var date = necromancer.resurrect(json);
|
|
||||||
* Object.prototype.toString.call(date); // => "[object Date]"
|
|
||||||
*
|
|
||||||
* ## Options
|
|
||||||
*
|
|
||||||
* Options are provided to the constructor as an object with these
|
|
||||||
* properties:
|
|
||||||
*
|
|
||||||
* prefix ('#'): A prefix string used for temporary properties added
|
|
||||||
* to objects during serialization and deserialization. It is
|
|
||||||
* important that you don't use any properties beginning with this
|
|
||||||
* string. This option must be consistent between both
|
|
||||||
* serialization and deserialization.
|
|
||||||
*
|
|
||||||
* cleanup (false): Perform full property cleanup after both
|
|
||||||
* serialization and deserialization using the `delete`
|
|
||||||
* operator. This may cause performance penalties (breaking hidden
|
|
||||||
* classes in V8) on objects that ResurrectJS touches, so enable
|
|
||||||
* with care.
|
|
||||||
*
|
|
||||||
* revive (true): Restore behavior (__proto__) to objects that have
|
|
||||||
* been resurrected. If this is set to false during serialization,
|
|
||||||
* resurrection information will not be encoded. You still get
|
|
||||||
* circularity and Date support.
|
|
||||||
*
|
|
||||||
* resolver (Resurrect.NamespaceResolver(window)): Converts between
|
|
||||||
* a name and a prototype. Create a custom resolver if your
|
|
||||||
* constructors are not stored in global variables. The resolver
|
|
||||||
* has two methods: getName(object) and getPrototype(string).
|
|
||||||
*
|
|
||||||
* For example,
|
|
||||||
*
|
|
||||||
* var necromancer = new Resurrect({
|
|
||||||
* prefix: '__#',
|
|
||||||
* cleanup: true
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* ## Caveats
|
|
||||||
*
|
|
||||||
* * With the default resolver, all constructors must be named and
|
|
||||||
* stored in the global variable under that name. This is required
|
|
||||||
* so that the prototypes can be looked up and reconnected at
|
|
||||||
* resurrection time.
|
|
||||||
*
|
|
||||||
* * The wrapper objects Boolean, String, and Number will be
|
|
||||||
* unwrapped. This means extra properties added to these objects
|
|
||||||
* will not be preserved.
|
|
||||||
*
|
|
||||||
* * Functions cannot ever be serialized. Resurrect will throw an
|
|
||||||
* error if a function is found when traversing a data structure.
|
|
||||||
*
|
|
||||||
* @see http://nullprogram.com/blog/2013/03/28/
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} [options] See options documentation.
|
|
||||||
* @namespace
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Resurrect(options) {
|
|
||||||
this.table = null;
|
|
||||||
this.prefix = '#';
|
|
||||||
this.cleanup = false;
|
|
||||||
this.revive = true;
|
|
||||||
for (var option in options) {
|
|
||||||
if (options.hasOwnProperty(option)) {
|
|
||||||
this[option] = options[option];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.refcode = this.prefix + 'id';
|
|
||||||
this.origcode = this.prefix + 'original';
|
|
||||||
this.buildcode = this.prefix + '.';
|
|
||||||
this.valuecode = this.prefix + 'v';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module)
|
|
||||||
module.exports = Resurrect;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Portable access to the global object (window, global).
|
|
||||||
* Uses indirect eval.
|
|
||||||
* @constant
|
|
||||||
*/
|
|
||||||
Resurrect.GLOBAL = (0, eval)('this');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escape special regular expression characters in a string.
|
|
||||||
* @param {string} string
|
|
||||||
* @returns {string} The string escaped for exact matches.
|
|
||||||
* @see http://stackoverflow.com/a/6969486
|
|
||||||
*/
|
|
||||||
Resurrect.escapeRegExp = function (string) {
|
|
||||||
return string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Helper Objects */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} [message]
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.Error = function ResurrectError(message) {
|
|
||||||
this.message = message || '';
|
|
||||||
this.stack = new Error().stack;
|
|
||||||
};
|
|
||||||
Resurrect.prototype.Error.prototype = Object.create(Error.prototype);
|
|
||||||
Resurrect.prototype.Error.prototype.name = 'ResurrectError';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves prototypes through the properties on an object and
|
|
||||||
* constructor names.
|
|
||||||
* @param {Object} scope
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
Resurrect.NamespaceResolver = function(scope) {
|
|
||||||
this.scope = scope;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the prototype of the given property name from an object. If
|
|
||||||
* not found, it throws an error.
|
|
||||||
* @param {string} name
|
|
||||||
* @returns {Object}
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.NamespaceResolver.prototype.getPrototype = function(name) {
|
|
||||||
var constructor = this.scope[name];
|
|
||||||
if (constructor) {
|
|
||||||
return constructor.prototype;
|
|
||||||
} else {
|
|
||||||
throw new Resurrect.prototype.Error('Unknown constructor: ' + name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the prototype name for an object, to be fetched later with getPrototype.
|
|
||||||
* @param {Object} object
|
|
||||||
* @returns {?string} Null if the constructor is Object.
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.NamespaceResolver.prototype.getName = function(object) {
|
|
||||||
var constructor = object.constructor.name;
|
|
||||||
if (constructor == null) { // IE
|
|
||||||
var funcPattern = /^\s*function\s*([A-Za-z0-9_$]*)/;
|
|
||||||
constructor = funcPattern.exec(object.constructor)[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (constructor === '') {
|
|
||||||
var msg = "Can't serialize objects with anonymous constructors.";
|
|
||||||
throw new Resurrect.prototype.Error(msg);
|
|
||||||
} else if (constructor === 'Object' || constructor === 'Array') {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return constructor;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Set the default resolver searches the global object. */
|
|
||||||
Resurrect.prototype.resolver =
|
|
||||||
new Resurrect.NamespaceResolver(Resurrect.GLOBAL);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a DOM node from HTML source; behaves like a constructor.
|
|
||||||
* @param {string} html
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
Resurrect.Node = function(html) {
|
|
||||||
var div = document.createElement('div');
|
|
||||||
div.innerHTML = html;
|
|
||||||
return div.firstChild;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Type Tests */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} type
|
|
||||||
* @returns {Function} A function that tests for type.
|
|
||||||
*/
|
|
||||||
Resurrect.is = function(type) {
|
|
||||||
var string = '[object ' + type + ']';
|
|
||||||
return function(object) {
|
|
||||||
return Object.prototype.toString.call(object) === string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Resurrect.isArray = Resurrect.is('Array');
|
|
||||||
Resurrect.isString = Resurrect.is('String');
|
|
||||||
Resurrect.isBoolean = Resurrect.is('Boolean');
|
|
||||||
Resurrect.isNumber = Resurrect.is('Number');
|
|
||||||
Resurrect.isFunction = Resurrect.is('Function');
|
|
||||||
Resurrect.isDate = Resurrect.is('Date');
|
|
||||||
Resurrect.isRegExp = Resurrect.is('RegExp');
|
|
||||||
Resurrect.isObject = Resurrect.is('Object');
|
|
||||||
|
|
||||||
Resurrect.isAtom = function(object) {
|
|
||||||
return !Resurrect.isObject(object) && !Resurrect.isArray(object);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {*} object
|
|
||||||
* @returns {boolean} True if object is a primitive or a primitive wrapper.
|
|
||||||
*/
|
|
||||||
Resurrect.isPrimitive = function(object) {
|
|
||||||
return object == null ||
|
|
||||||
Resurrect.isNumber(object) ||
|
|
||||||
Resurrect.isString(object) ||
|
|
||||||
Resurrect.isBoolean(object);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Methods */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a reference (encoding) to an object.
|
|
||||||
* @param {(Object|undefined)} object
|
|
||||||
* @returns {Object}
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.ref = function(object) {
|
|
||||||
var ref = {};
|
|
||||||
if (object === undefined) {
|
|
||||||
ref[this.prefix] = -1;
|
|
||||||
} else {
|
|
||||||
ref[this.prefix] = object[this.refcode];
|
|
||||||
}
|
|
||||||
return ref;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup an object in the table by reference object.
|
|
||||||
* @param {Object} ref
|
|
||||||
* @returns {(Object|undefined)}
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.deref = function(ref) {
|
|
||||||
return this.table[ref[this.prefix]];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put a temporary identifier on an object and store it in the table.
|
|
||||||
* @param {Object} object
|
|
||||||
* @returns {number} The unique identifier number.
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.tag = function(object) {
|
|
||||||
if (this.revive) {
|
|
||||||
var constructor = this.resolver.getName(object);
|
|
||||||
if (constructor) {
|
|
||||||
var proto = Object.getPrototypeOf(object);
|
|
||||||
if (this.resolver.getPrototype(constructor) !== proto) {
|
|
||||||
throw new this.Error('Constructor mismatch!');
|
|
||||||
} else {
|
|
||||||
object[this.prefix] = constructor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
object[this.refcode] = this.table.length;
|
|
||||||
this.table.push(object);
|
|
||||||
return object[this.refcode];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a builder object (encoding) for serialization.
|
|
||||||
* @param {string} name The name of the constructor.
|
|
||||||
* @param value The value to pass to the constructor.
|
|
||||||
* @returns {Object}
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.builder = function(name, value) {
|
|
||||||
var builder = {};
|
|
||||||
builder[this.buildcode] = name;
|
|
||||||
builder[this.valuecode] = value;
|
|
||||||
return builder;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a value from a deserialized builder.
|
|
||||||
* @param {Object} ref
|
|
||||||
* @returns {Object}
|
|
||||||
* @method
|
|
||||||
* @see http://stackoverflow.com/a/14378462
|
|
||||||
* @see http://nullprogram.com/blog/2013/03/24/
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.build = function(ref) {
|
|
||||||
var type = ref[this.buildcode].split(/\./).reduce(function(object, name) {
|
|
||||||
return object[name];
|
|
||||||
}, Resurrect.GLOBAL);
|
|
||||||
/* Brilliant hack by kybernetikos: */
|
|
||||||
var args = [null].concat(ref[this.valuecode]);
|
|
||||||
var factory = type.bind.apply(type, args);
|
|
||||||
var result = new factory();
|
|
||||||
if (Resurrect.isPrimitive(result)) {
|
|
||||||
return result.valueOf(); // unwrap
|
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dereference or build an object or value from an encoding.
|
|
||||||
* @param {Object} ref
|
|
||||||
* @returns {(Object|undefined)}
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.decode = function(ref) {
|
|
||||||
if (this.prefix in ref) {
|
|
||||||
return this.deref(ref);
|
|
||||||
} else if (this.buildcode in ref) {
|
|
||||||
return this.build(ref);
|
|
||||||
} else {
|
|
||||||
throw new this.Error('Unknown encoding.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} object
|
|
||||||
* @returns {boolean} True if the provided object is tagged for serialization.
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.isTagged = function(object) {
|
|
||||||
return (this.refcode in object) && (object[this.refcode] != null);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visit root and all its ancestors, visiting atoms with f.
|
|
||||||
* @param {*} root
|
|
||||||
* @param {Function} f
|
|
||||||
* @param {Function} replacer
|
|
||||||
* @returns {*} A fresh copy of root to be serialized.
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.visit = function(root, f, replacer) {
|
|
||||||
if (Resurrect.isAtom(root)) {
|
|
||||||
return f(root);
|
|
||||||
} else if (!this.isTagged(root)) {
|
|
||||||
var copy = null;
|
|
||||||
if (Resurrect.isArray(root)) {
|
|
||||||
copy = [];
|
|
||||||
root[this.refcode] = this.tag(copy);
|
|
||||||
for (var i = 0; i < root.length; i++) {
|
|
||||||
copy.push(this.visit(root[i], f, replacer));
|
|
||||||
}
|
|
||||||
} else { /* Object */
|
|
||||||
copy = Object.create(Object.getPrototypeOf(root));
|
|
||||||
root[this.refcode] = this.tag(copy);
|
|
||||||
for (var key in root) {
|
|
||||||
var value = root[key];
|
|
||||||
if (root.hasOwnProperty(key)) {
|
|
||||||
if (replacer && value !== undefined) {
|
|
||||||
// Call replacer like JSON.stringify's replacer
|
|
||||||
value = replacer.call(root, key, root[key]);
|
|
||||||
if (value === undefined) {
|
|
||||||
continue; // Omit from result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
copy[key] = this.visit(value, f, replacer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
copy[this.origcode] = root;
|
|
||||||
return this.ref(copy);
|
|
||||||
} else {
|
|
||||||
return this.ref(root);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manage special atom values, possibly returning an encoding.
|
|
||||||
* @param {*} atom
|
|
||||||
* @returns {*}
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.handleAtom = function(atom) {
|
|
||||||
var Node = Resurrect.GLOBAL.Node || function() {};
|
|
||||||
if (Resurrect.isFunction(atom)) {
|
|
||||||
throw new this.Error("Can't serialize functions.");
|
|
||||||
} else if (atom instanceof Node) {
|
|
||||||
var xmls = new XMLSerializer();
|
|
||||||
return this.builder('Resurrect.Node', [xmls.serializeToString(atom)]);
|
|
||||||
} else if (Resurrect.isDate(atom)) {
|
|
||||||
return this.builder('Date', [atom.toISOString()]);
|
|
||||||
} else if (Resurrect.isRegExp(atom)) {
|
|
||||||
var args = atom.toString().match(/\/(.+)\/([gimy]*)/).slice(1);
|
|
||||||
return this.builder('RegExp', args);
|
|
||||||
} else if (atom === undefined) {
|
|
||||||
return this.ref(undefined);
|
|
||||||
} else if (Resurrect.isNumber(atom) && (isNaN(atom) || !isFinite(atom))) {
|
|
||||||
return this.builder('Number', [atom.toString()]);
|
|
||||||
} else {
|
|
||||||
return atom;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides intrusive keys from a user-supplied replacer.
|
|
||||||
* @param {Function} replacer function of two arguments (key, value)
|
|
||||||
* @returns {Function} A function that skips the replacer for intrusive keys.
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.replacerWrapper = function(replacer) {
|
|
||||||
var skip = new RegExp('^' + Resurrect.escapeRegExp(this.prefix));
|
|
||||||
return function(k, v) {
|
|
||||||
if (skip.test(k)) {
|
|
||||||
return v;
|
|
||||||
} else {
|
|
||||||
return replacer(k, v);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize an arbitrary JavaScript object, carefully preserving it.
|
|
||||||
* @param {*} object
|
|
||||||
* @param {(Function|Array)} replacer
|
|
||||||
* @param {string} space
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.stringify = function(object, replacer, space) {
|
|
||||||
if (Resurrect.isFunction(replacer)) {
|
|
||||||
replacer = this.replacerWrapper(replacer);
|
|
||||||
} else if (Resurrect.isArray(replacer)) {
|
|
||||||
var acceptKeys = replacer;
|
|
||||||
replacer = function(k, v) {
|
|
||||||
return acceptKeys.indexOf(k) >= 0 ? v : undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (Resurrect.isAtom(object)) {
|
|
||||||
return JSON.stringify(this.handleAtom(object), replacer, space);
|
|
||||||
} else {
|
|
||||||
this.table = [];
|
|
||||||
this.visit(object, this.handleAtom.bind(this), replacer);
|
|
||||||
for (var i = 0; i < this.table.length; i++) {
|
|
||||||
if (this.cleanup) {
|
|
||||||
delete this.table[i][this.origcode][this.refcode];
|
|
||||||
} else {
|
|
||||||
this.table[i][this.origcode][this.refcode] = null;
|
|
||||||
}
|
|
||||||
delete this.table[i][this.refcode];
|
|
||||||
delete this.table[i][this.origcode];
|
|
||||||
}
|
|
||||||
var table = this.table;
|
|
||||||
this.table = null;
|
|
||||||
return JSON.stringify(table, null, space);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore the __proto__ of the given object to the proper value.
|
|
||||||
* @param {Object} object
|
|
||||||
* @returns {Object} Its argument, or a copy, with the prototype restored.
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.fixPrototype = function(object) {
|
|
||||||
if (this.prefix in object) {
|
|
||||||
var name = object[this.prefix];
|
|
||||||
var prototype = this.resolver.getPrototype(name);
|
|
||||||
if ('__proto__' in object) {
|
|
||||||
object.__proto__ = prototype;
|
|
||||||
if (this.cleanup) {
|
|
||||||
delete object[this.prefix];
|
|
||||||
}
|
|
||||||
return object;
|
|
||||||
} else { // IE
|
|
||||||
var copy = Object.create(prototype);
|
|
||||||
for (var key in object) {
|
|
||||||
if (object.hasOwnProperty(key) && key !== this.prefix) {
|
|
||||||
copy[key] = object[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deserialize an encoded object, restoring circularity and behavior.
|
|
||||||
* @param {string} string
|
|
||||||
* @returns {*} The decoded object or value.
|
|
||||||
* @method
|
|
||||||
*/
|
|
||||||
Resurrect.prototype.resurrect = function(string) {
|
|
||||||
var result = null;
|
|
||||||
var data = JSON.parse(string);
|
|
||||||
if (Resurrect.isArray(data)) {
|
|
||||||
this.table = data;
|
|
||||||
/* Restore __proto__. */
|
|
||||||
if (this.revive) {
|
|
||||||
for (var i = 0; i < this.table.length; i++) {
|
|
||||||
this.table[i] = this.fixPrototype(this.table[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Re-establish object references and construct atoms. */
|
|
||||||
for (i = 0; i < this.table.length; i++) {
|
|
||||||
var object = this.table[i];
|
|
||||||
for (var key in object) {
|
|
||||||
if (object.hasOwnProperty(key)) {
|
|
||||||
if (!(Resurrect.isAtom(object[key]))) {
|
|
||||||
object[key] = this.decode(object[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = this.table[0];
|
|
||||||
} else if (Resurrect.isObject(data)) {
|
|
||||||
this.table = [];
|
|
||||||
result = this.decode(data);
|
|
||||||
} else {
|
|
||||||
result = data;
|
|
||||||
}
|
|
||||||
this.table = null;
|
|
||||||
return result;
|
|
||||||
};
|
|
|
@ -1,185 +0,0 @@
|
||||||
var fs = require('fs');
|
|
||||||
var mkdirp = require('mkdirp').sync;
|
|
||||||
var removeDir = require('rimraf').sync;
|
|
||||||
var Resurrect = require('../lib/resurrect');
|
|
||||||
var compare = require('fast-json-patch').compare;
|
|
||||||
var path = require('path');
|
|
||||||
var $ = require('cheerio');
|
|
||||||
var Matter = require('../../build/matter-dev.js');
|
|
||||||
Matter.Example = require('../../demo/js/Examples.js');
|
|
||||||
Matter.Demo = require('../../demo/js/Demo.js');
|
|
||||||
|
|
||||||
var demo,
|
|
||||||
frames = 10,
|
|
||||||
refsPath = 'test/node/refs',
|
|
||||||
diffsPath = 'test/node/diffs';
|
|
||||||
|
|
||||||
var update = arg('--update'),
|
|
||||||
updateAll = typeof arg('--updateAll') !== 'undefined',
|
|
||||||
diff = arg('--diff');
|
|
||||||
|
|
||||||
var resurrect = new Resurrect({ cleanup: true, revive: false }),
|
|
||||||
created = [],
|
|
||||||
changed = [];
|
|
||||||
|
|
||||||
var test = function() {
|
|
||||||
var demos = getDemoNames();
|
|
||||||
|
|
||||||
removeDir(diffsPath);
|
|
||||||
|
|
||||||
if (diff) {
|
|
||||||
mkdirp(diffsPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < demos.length; i += 1) {
|
|
||||||
demo = demos[i];
|
|
||||||
|
|
||||||
var hasChanged = false,
|
|
||||||
hasCreated = false,
|
|
||||||
forceUpdate = update === demo || updateAll,
|
|
||||||
worldStartPath = refsPath + '/' + demo + '/' + demo + '-0.json',
|
|
||||||
worldEndPath = refsPath + '/' + demo + '/' + demo + '-' + frames + '.json',
|
|
||||||
worldStartDiffPath = diffsPath + '/' + demo + '/' + demo + '-0.json',
|
|
||||||
worldEndDiffPath = diffsPath + '/' + demo + '/' + demo + '-' + frames + '.json';
|
|
||||||
|
|
||||||
var _demo = Matter.Demo.create(),
|
|
||||||
engine = Matter.Example.engine(_demo),
|
|
||||||
runner = Matter.Runner.create();
|
|
||||||
|
|
||||||
_demo.engine = engine;
|
|
||||||
_demo.engine.render = {};
|
|
||||||
_demo.engine.render.options = {};
|
|
||||||
_demo.runner = runner;
|
|
||||||
_demo.render = { options: {} };
|
|
||||||
|
|
||||||
if (!(demo in Matter.Example)) {
|
|
||||||
throw '\'' + demo + '\' is not defined in Matter.Example';
|
|
||||||
}
|
|
||||||
|
|
||||||
Matter.Demo.reset(_demo);
|
|
||||||
Matter.Example[demo](_demo);
|
|
||||||
|
|
||||||
var worldStart = JSON.parse(resurrect.stringify(engine.world, precisionLimiter));
|
|
||||||
|
|
||||||
for (var j = 0; j <= frames; j += 1) {
|
|
||||||
Matter.Runner.tick(runner, engine, j * runner.delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
var worldEnd = JSON.parse(resurrect.stringify(engine.world, precisionLimiter));
|
|
||||||
|
|
||||||
if (fs.existsSync(worldStartPath)) {
|
|
||||||
var worldStartRef = JSON.parse(fs.readFileSync(worldStartPath));
|
|
||||||
var worldStartDiff = compare(worldStartRef, worldStart);
|
|
||||||
|
|
||||||
if (worldStartDiff.length !== 0) {
|
|
||||||
if (diff) {
|
|
||||||
writeFile(worldStartDiffPath, JSON.stringify(worldStartDiff, precisionLimiter, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceUpdate) {
|
|
||||||
hasCreated = true;
|
|
||||||
writeFile(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2));
|
|
||||||
} else {
|
|
||||||
hasChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasCreated = true;
|
|
||||||
writeFile(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(worldEndPath)) {
|
|
||||||
var worldEndRef = JSON.parse(fs.readFileSync(worldEndPath));
|
|
||||||
var worldEndDiff = compare(worldEndRef, worldEnd);
|
|
||||||
|
|
||||||
if (worldEndDiff.length !== 0) {
|
|
||||||
if (diff) {
|
|
||||||
writeFile(worldEndDiffPath, JSON.stringify(worldEndDiff, precisionLimiter, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceUpdate) {
|
|
||||||
hasCreated = true;
|
|
||||||
writeFile(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2));
|
|
||||||
} else {
|
|
||||||
hasChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasCreated = true;
|
|
||||||
writeFile(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasChanged) {
|
|
||||||
changed.push("'" + demo + "'");
|
|
||||||
process.stdout.write('x');
|
|
||||||
} else if (hasCreated) {
|
|
||||||
created.push("'" + demo + "'");
|
|
||||||
process.stdout.write('+');
|
|
||||||
} else {
|
|
||||||
process.stdout.write('.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (created.length > 0) {
|
|
||||||
console.log('\nupdated', created.join(', '));
|
|
||||||
}
|
|
||||||
|
|
||||||
var isOk = changed.length === 0 ? 1 : 0;
|
|
||||||
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
if (isOk) {
|
|
||||||
console.log('ok');
|
|
||||||
} else {
|
|
||||||
console.log('\nchanges detected on:');
|
|
||||||
console.log(changed.join(', '));
|
|
||||||
console.log('\nreview, then --update [name] or --updateAll');
|
|
||||||
console.log('use --diff for diff log');
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
process.exit(!isOk);
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
var precisionLimiter = function(key, value) {
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
return parseFloat(value.toFixed(5));
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
function arg(name) {
|
|
||||||
var index = process.argv.indexOf(name);
|
|
||||||
if (index >= 0) {
|
|
||||||
return process.argv[index + 1] || true;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
var getDemoNames = function() {
|
|
||||||
var demos = [],
|
|
||||||
skip = [
|
|
||||||
'terrain', 'svg', 'concave',
|
|
||||||
'slingshot', 'views', 'raycasting',
|
|
||||||
'events', 'collisionFiltering', 'sleeping',
|
|
||||||
'attractors'
|
|
||||||
];
|
|
||||||
|
|
||||||
$('#demo-select option', fs.readFileSync('demo/index.html').toString())
|
|
||||||
.each(function() {
|
|
||||||
var name = $(this).val();
|
|
||||||
if (skip.indexOf(name) === -1) {
|
|
||||||
demos.push(name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return demos;
|
|
||||||
};
|
|
||||||
|
|
||||||
var writeFile = function(filePath, string) {
|
|
||||||
mkdirp(path.dirname(filePath));
|
|
||||||
fs.writeFileSync(filePath, string);
|
|
||||||
};
|
|
||||||
|
|
||||||
test();
|
|
Loading…
Add table
Reference in a new issue