X-Git-Url: https://git.r.bdr.sh/rbdr/serpentity/blobdiff_plain/3db7d19815d24241bbe60c9afbb8b3f60d000550..564951b9b728e9e2a4c46a571867faa28fd396a8:/test/integration.js diff --git a/test/integration.js b/test/integration.js index 0ca7e26..5b6e595 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,163 +1,310 @@ 'use strict'; -let test = function test (Serpentity) { - - /* eslint no-console: 0 */ - - ///////////////// - // Load the stuff - ///////////////// - console.log('\n## Loading'); - console.log('Serpentity: ' + (typeof Serpentity !== 'undefined' ? 'LOAD OK' : 'FAIL')); - console.log('Serpentity.Entity: ' + (typeof Serpentity !== 'undefined' && Serpentity.Entity ? 'LOAD OK' : 'FAIL')); - console.log('Serpentity.Component: ' + (typeof Serpentity !== 'undefined' && Serpentity.Component ? 'LOAD OK' : 'FAIL')); - console.log('Serpentity.System: ' + (typeof Serpentity !== 'undefined' && Serpentity.System ? 'LOAD OK' : 'FAIL')); - console.log('Serpentity.Node: ' + (typeof Serpentity !== 'undefined' && Serpentity.Node ? 'LOAD OK' : 'FAIL')); - console.log('Serpentity.NodeCollection: ' + (typeof Serpentity !== 'undefined' && Serpentity.NodeCollection ? 'LOAD OK' : 'FAIL')); - - ////////////////////// - // Create test classes - ////////////////////// - console.log('\n## Creating Test Classes'); - let TestSystem = class TestSystem extends Serpentity.System { - added (engine) { - this.testNodes = engine.getNodes(TestNode); - console.log('Engine is serpentity: ' + (engine instanceof Serpentity ? 'OK' : 'FAIL')); - console.log('System added callback: EXEC OK'); - } +const Code = require('code'); // assertion library +const Lab = require('lab'); +const Serpentity = require('..'); - removed (engine) { - this.testNodes = null; - console.log('Engine is serpentity: ' + (engine instanceof Serpentity ? 'OK' : 'FAIL')); - console.log('System removed callback: EXEC OK'); - } +const internals = { + system: class TestSystem extends Serpentity.System { + added(engine) { - update (dt) { - this.testNodes.forEach(function (node) { - console.log('Running Node: ' + (node.test.testMessage === 'test' ? 'SYSTEM OK' : 'FAIL')); - }); - console.log('dt is number: ' + (typeof dt === 'number' ? 'OK' : 'FAIL')); - console.log('System update callback: EXEC OK'); - } - }; - let testSystem = new TestSystem(); - - let LowProTestSystem = class LowProTestSystem extends Serpentity.System { - added (engine) { - this.testNodes = engine.getNodes(TestNode); - console.log('Engine is serpentity: ' + (engine instanceof Serpentity ? 'OK' : 'FAIL')); - console.log('System added callback: EXEC OK'); + this.testNodes = engine.getNodes(internals.node); + this.addedCalled = true; + this.addedEngine = engine; } - removed (engine) { + removed(engine) { + this.testNodes = null; - console.log('Engine is serpentity: ' + (engine instanceof Serpentity ? 'OK' : 'FAIL')); - console.log('System removed callback: EXEC OK'); + this.removedCalled = true; + this.removedEngine = engine; } - update (dt) { - this.testNodes.forEach(function (node) { - console.log('Running Low Priority Node: ' + (node.test.testMessage === 'test' ? 'SYSTEM OK' : 'FAIL')); - }); - console.log('dt is number: ' + (typeof dt === 'number' ? 'OK' : 'FAIL')); - console.log('System update callback: EXEC OK'); - } - }; - let lowProTestSystem = new LowProTestSystem(); - console.log('LowProTestSystem: CREATE OK'); - - let MidProTestSystem = class MidProTestSystem extends Serpentity.System { - added (engine) { - this.testNodes = engine.getNodes(TestNode); - console.log('Engine is serpentity: ' + (engine instanceof Serpentity ? 'OK' : 'FAIL')); - console.log('System added callback: EXEC OK'); - } + update(dt) { - removed (engine) { - this.testNodes = null; - console.log('Engine is serpentity: ' + (engine instanceof Serpentity ? 'OK' : 'FAIL')); - console.log('System removed callback: EXEC OK'); - } + this.updateCalled = Date.now(); + this.updateDt = dt; - update (dt) { - this.testNodes.forEach(function (node) { - console.log('Running Mid Priority Node: ' + (node.test.testMessage === 'test' ? 'SYSTEM OK' : 'FAIL')); - }); - console.log('dt is number: ' + (typeof dt === 'number' ? 'OK' : 'FAIL')); - console.log('System update callback: EXEC OK'); - } - }; - var midProTestSystem = new MidProTestSystem(); - console.log('MidProTestSystem: CREATE OK'); + for (const node of this.testNodes) { + node.test.called = true; + node.entity.called = true; + } + while (Date.now() === this.updateCalled) { /* pass some time */ } + } + }, + component: class TestComponent extends Serpentity.Component { + constructor(config) { - let TestComponent = class TestComponent extends Serpentity.Component { - constructor (config) { super(config); - this.testMessage = this.testMessage || 'test'; + this.called = false; } - }; - console.log('TestComponent: CREATE OK'); + }, + node: class TestNode extends Serpentity.Node {}, + delta: 10 +}; - let TestNode = class TestNode extends Serpentity.Node {}; - TestNode.types = { - test : TestComponent - }; - console.log('TestNode: CREATE OK'); +// adds a component to the node +internals.node.types = { + test: internals.component +}; - console.log('\n## Adding system to the engine'); +const lab = exports.lab = Lab.script(); - let engine = new Serpentity(); - console.log('engine: CREATE OK'); +lab.experiment('loading', () => { - engine.addSystem(testSystem, 0); + lab.test('Serpentity should be exported', (done) => { - console.log('\n## Running update without any entities'); - engine.update(10); + Code.expect(typeof Serpentity).to.not.be.undefined(); + done(); + }); - console.log('\n## Adding system to the engine and updating'); - let entity = new Serpentity.Entity(); - entity.addComponent(new TestComponent()); - engine.addEntity(entity); - engine.update(10); + lab.test('Serpentity should include the Entity class', (done) => { - console.log('\n## Adding Low Priority System'); - engine.addSystem(lowProTestSystem, 10); - engine.update(10); + Code.expect(typeof Serpentity.Entity).to.not.be.undefined(); + done(); + }); - console.log('\n## Adding Mid Priority System'); - engine.addSystem(midProTestSystem, 5); - engine.update(10); + lab.test('Serpentity should include the Component class', (done) => { - console.log('\n## Removing the system and readding'); - engine.removeSystem(testSystem); - engine.update(10); - engine.addSystem(testSystem, 0); - engine.update(10); + Code.expect(typeof Serpentity.Component).to.not.be.undefined(); + done(); + }); - console.log('\n## Adding a second entity'); - entity = new Serpentity.Entity(); - entity.addComponent(new TestComponent()); - engine.addEntity(entity); - engine.update(10); + lab.test('Serpentity should include the System class', (done) => { - console.log('\n## Removing entity'); - engine.removeEntity(entity); - engine.update(10); + Code.expect(typeof Serpentity.System).to.not.be.undefined(); + done(); + }); - console.log('\n## Removing system'); - engine.removeSystem(testSystem); - engine.update(10); + lab.test('Serpentity should include the Node class', (done) => { -}; + Code.expect(typeof Serpentity.Node).to.not.be.undefined(); + done(); + }); + + lab.test('Serpentity should include the NodeCollection class', (done) => { + + Code.expect(typeof Serpentity.NodeCollection).to.not.be.undefined(); + done(); + }); +}); + +lab.experiment('Engine Tests', () => { + + lab.beforeEach((done) => { + + this.engine = new Serpentity(); + + this.regularSystem = new internals.system(); + this.highPrioritySystem = new internals.system(); + this.lowPrioritySystem = new internals.system(); + + this.firstEntity = new Serpentity.Entity(); + this.firstEntity.addComponent(new internals.component()); + this.secondEntity = new Serpentity.Entity(); + this.secondEntity.addComponent(new internals.component()); + this.emptyEntity = new Serpentity.Entity(); + + // Add entity before the systems + this.engine.addEntity(this.firstEntity); + + this.engine.addSystem(this.regularSystem, 100); + this.engine.addSystem(this.highPrioritySystem, 0); + this.engine.addSystem(this.lowPrioritySystem, 1000); + + // Add entity after the systems + this.engine.addEntity(this.secondEntity); + this.engine.addEntity(this.emptyEntity); + + done(); + }); + + lab.test('Engine should call added callback on added systems', (done) => { + + // Ensure the added callback is being called + Code.expect(this.regularSystem.addedCalled).to.be.true(); + Code.expect(this.highPrioritySystem.addedCalled).to.be.true(); + Code.expect(this.lowPrioritySystem.addedCalled).to.be.true(); + + done(); + }); + + lab.test('Engine should send the engine instance in added callback', (done) => { + + // Ensure the added callback is sending the engine + Code.expect(this.regularSystem.addedEngine instanceof Serpentity).to.be.true(); + Code.expect(this.highPrioritySystem.addedEngine instanceof Serpentity).to.be.true(); + Code.expect(this.lowPrioritySystem.addedEngine instanceof Serpentity).to.be.true(); + + done(); + }); + + lab.test('Engine should not add duplicate systems', (done) => { + + const originalSystemsLength = this.engine.systems.length; + const added = this.engine.addSystem(this.regularSystem, 0); + const newSystemsLength = this.engine.systems.length; + + // Ensure we don't add the same system twice + Code.expect(added).to.be.false(); + Code.expect(originalSystemsLength).to.be.equal(newSystemsLength); + + done(); + }); + + lab.test('Engine should call update callback on added systems', (done) => { + + this.engine.update(internals.delta); + + // Ensure update function called + Code.expect(!!this.regularSystem.updateCalled).to.be.true(); + Code.expect(!!this.highPrioritySystem.updateCalled).to.be.true(); + Code.expect(!!this.lowPrioritySystem.updateCalled).to.be.true(); -if (typeof require === 'function') { - let Serpentity = require('serpentity'); - test(Serpentity); -} else { - window.addEventListener('load', function () { - test(window.Serpentity); + done(); }); -} + lab.test('Engine should call update callback in the order of priorities', (done) => { + + this.engine.update(internals.delta); + + // Ensure order of priorities + Code.expect(this.regularSystem.updateCalled).to.be.lessThan(this.lowPrioritySystem.updateCalled); + Code.expect(this.regularSystem.updateCalled).to.be.greaterThan(this.highPrioritySystem.updateCalled); + + done(); + }); + + lab.test('Engine should send the delta in the update callback', (done) => { + + this.engine.update(internals.delta); + + // Ensure delta is being sent + Code.expect(this.regularSystem.updateDt).to.be.equal(internals.delta); + Code.expect(this.highPrioritySystem.updateDt).to.be.equal(internals.delta); + Code.expect(this.lowPrioritySystem.updateDt).to.be.equal(internals.delta); + + done(); + }); + + lab.test('System remove callback', (done) => { + + const originalSystemLength = this.engine.systems.length; + const originalRemoved = this.engine.removeSystem(this.lowPrioritySystem); + const intermediateSystemLength = this.engine.systems.length; + const finalRemoved = this.engine.removeSystem(this.lowPrioritySystem); + const finalSystemLength = this.engine.systems.length; + this.engine.update(internals.delta); + + // Check for return value + Code.expect(originalRemoved).to.be.true(); + Code.expect(finalRemoved).to.be.false(); + + // Confirm that only removed if found by checking length of systems + // array + Code.expect(originalSystemLength).to.be.above(intermediateSystemLength); + Code.expect(finalSystemLength).to.be.equal(intermediateSystemLength); + + // Ensure callback is sent + Code.expect(!!this.regularSystem.removedCalled).to.be.false(); + Code.expect(!!this.highPrioritySystem.removedCalled).to.be.false(); + Code.expect(!!this.lowPrioritySystem.removedCalled).to.be.true(); + + // Ensure update is no longer sent + Code.expect(!!this.regularSystem.updateCalled).to.be.true(); + Code.expect(!!this.highPrioritySystem.updateCalled).to.be.true(); + Code.expect(!!this.lowPrioritySystem.updateCalled).to.be.false(); + + done(); + }); + + lab.test('Entity node selection', (done) => { + + this.engine.update(internals.delta); + + // Ensure component is called for each entity + Code.expect(!!this.firstEntity._components[0].called).to.be.true(); + Code.expect(!!this.secondEntity._components[0].called).to.be.true(); + + // Ensure entity not in node collection not called + Code.expect(!!this.firstEntity.called).to.be.true(); + Code.expect(!!this.secondEntity.called).to.be.true(); + Code.expect(!!this.emptyEntity.called).to.be.false(); + + done(); + }); + + lab.test('Entity node removal', (done) => { + + this.engine.removeEntity(this.secondEntity); + this.engine.update(internals.delta); + + Code.expect(!!this.firstEntity._components[0].called).to.be.true(); + Code.expect(!!this.secondEntity._components[0].called).to.be.false(); + + Code.expect(!!this.firstEntity.called).to.be.true(); + Code.expect(!!this.secondEntity.called).to.be.false(); + Code.expect(!!this.emptyEntity.called).to.be.false(); + + done(); + }); + + lab.test('Entity should not add duplicate components', (done) => { + const originalComponentsLength = this.secondEntity._components.length; + const result = this.secondEntity.addComponent(new internals.component()); + const newComponentsLength = this.secondEntity._components.length; + + Code.expect(result).to.be.false(); + Code.expect(originalComponentsLength).to.be.equal(newComponentsLength); + + done(); + }); + + lab.test('Entity should allow access to components by class', (done) => { + const firstComponent = this.firstEntity.getComponent(internals.component); + const emptyComponent = this.emptyEntity.getComponent(internals.component); + + Code.expect(firstComponent instanceof internals.component).to.be.true(); + Code.expect(emptyComponent).to.be.equal(undefined); + + done(); + }); + + lab.test('Engine should not add duplicate entities', (done) => { + const originalEntitiesLength = this.engine.entities.length; + const added = this.engine.addEntity(this.firstEntity); + const finalEntitiesLength = this.engine.entities.length; + + Code.expect(added).to.be.false(); + + Code.expect(originalEntitiesLength).to.be.equal(finalEntitiesLength); + done(); + }); + + lab.test('Engine should remove entities', (done) => { + + const originalEntityLength = this.engine.entities.length; + const originalRemoved = this.engine.removeEntity(this.firstEntity); + const intermediateEntityLength = this.engine.entities.length; + const finalRemoved = this.engine.removeEntity(this.firstEntity); + const finalEntityLength = this.engine.entities.length; + this.engine.update(internals.delta); + + // Check for return value + Code.expect(originalRemoved).to.be.true(); + Code.expect(finalRemoved).to.be.false(); + + // Confirm that only removed if found by checking length of systems + // array + Code.expect(originalEntityLength).to.be.above(intermediateEntityLength); + Code.expect(finalEntityLength).to.be.equal(intermediateEntityLength); + + // Ensure callback is sent + Code.expect(!!this.firstEntity.called).to.be.false(); + Code.expect(!!this.secondEntity.called).to.be.true(); + + done(); + }); +});