diff --git a/.gitignore b/.gitignore index 813b9840..e8fa5d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ matter-doc-theme build/matter-dev.js build/matter-dev.min.js demo/js/lib/matter-dev.js -test/browser/diffs \ No newline at end of file +test/browser/diffs +test/node/diffs \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 60e8c21c..749f70f3 100644 --- a/.jshintrc +++ b/.jshintrc @@ -31,8 +31,9 @@ "undef": true, "-W079": true, // Silence redefinition errors (they are false positives). "predef": [ - "Matter", "window", "document", "Element", "MatterTools", "PIXI", "phantom", - "$", "Image", "navigator", "setTimeout", "decomp", "HTMLElement", "require", + "Matter", "window", "document", "Element", "MatterTools", + "phantom", "process", "HTMLElement", "require", "PIXI", + "$", "Image", "navigator", "setTimeout", "decomp", "module", "Body", "Composite", "World", "Contact", "Detector", "Grid", "Pairs", "Pair", "Resolver", "SAT", "Constraint", "MouseConstraint", "Common", "Engine", "Mouse", "Sleeping", "Bodies", "Composites", diff --git a/Gruntfile.js b/Gruntfile.js index 4fccedc1..3c9c18e6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -57,7 +57,7 @@ module.exports = function(grunt) { options: { jshintrc: '.jshintrc' }, - all: ['src/**/*.js', 'demo/js/*.js', 'test/browser/TestDemo.js', '!src/module/*'] + all: ['src/**/*.js', 'demo/js/*.js', 'test/browser/TestDemo.js', 'test/node/TestDemo.js', '!src/module/*'] }, connect: { watch: { @@ -114,7 +114,7 @@ module.exports = function(grunt) { } }, shell: { - testDemo: { + testDemoBrowser: { command: function(arg) { arg = arg ? ' --' + arg : ''; return 'phantomjs test/browser/TestDemo.js' + arg; @@ -124,6 +124,17 @@ module.exports = function(grunt) { timeout: 1000 * 60 } } + }, + testDemoNode: { + command: function(arg) { + arg = arg ? ' --' + arg : ''; + return 'node test/node/TestDemo.js' + arg; + }, + options: { + execOptions: { + timeout: 1000 * 60 + } + } } } }); @@ -139,7 +150,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-shell'); grunt.registerTask('default', ['test', 'build']); - grunt.registerTask('test', ['build:dev', 'connect:serve', 'jshint', 'test:demo']); + grunt.registerTask('test', ['build:dev', 'connect:serve', 'jshint', 'test:demo', 'test:demoNode']); grunt.registerTask('dev', ['build:dev', 'connect:watch', 'watch']); grunt.registerTask('test:demo', function() { @@ -147,11 +158,24 @@ module.exports = function(grunt) { diff = grunt.option('diff'); if (updateAll) { - grunt.task.run('shell:testDemo:updateAll'); + grunt.task.run('shell:testDemoBrowser:updateAll'); + } else if (diff) { + grunt.task.run('shell:testDemoBrowser:diff'); + } else { + grunt.task.run('shell:testDemoBrowser'); + } + }); + + grunt.registerTask('test:demoNode', function() { + var updateAll = grunt.option('updateAll'), + diff = grunt.option('diff'); + + if (updateAll) { + grunt.task.run('shell:testDemoNode:updateAll'); } else if (diff) { - grunt.task.run('shell:testDemo:diff'); + grunt.task.run('shell:testDemoNode:diff'); } else { - grunt.task.run('shell:testDemo'); + grunt.task.run('shell:testDemoNode'); } }); diff --git a/package.json b/package.json index 20b9a7b6..eefe1356 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "rigid body physics" ], "devDependencies": { + "cheerio": "^0.19.0", "fast-json-patch": "^0.5.4", "grunt": "~0.4.2", "grunt-contrib-concat": "~0.3.0", @@ -30,7 +31,9 @@ "grunt-contrib-watch": "~0.5.3", "grunt-contrib-yuidoc": "~0.5.1", "grunt-preprocess": "^4.1.0", - "grunt-shell": "^1.1.2" + "grunt-shell": "^1.1.2", + "mkdirp": "^0.5.1", + "rimraf": "^2.4.2" }, "scripts": { "dev": "npm install && grunt dev", diff --git a/test/node/TestDemo.js b/test/node/TestDemo.js new file mode 100644 index 00000000..04a5eb85 --- /dev/null +++ b/test/node/TestDemo.js @@ -0,0 +1,177 @@ +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.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(status) { + 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'; + + Matter.Demo.init(); + + var engine = Matter.Demo._engine, + runner = Matter.Runner.create(); + + if (!(demo in Matter.Demo)) { + throw '\'' + demo + '\' is not defined in Matter.Demo'; + } + + Matter.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' + ]; + + $('#demo-select option', fs.readFileSync('demo/dev.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(); \ No newline at end of file