+describe('Engine', () => {
+
+ beforeEach(() => {
+
+ internals.context.engine = new Serpentity();
+
+ internals.context.regularSystem = new internals.system();
+ internals.context.highPrioritySystem = new internals.system();
+ internals.context.lowPrioritySystem = new internals.system();
+
+ internals.context.firstEntity = new Entity();
+ internals.context.firstEntity.addComponent(new internals.component());
+ internals.context.secondEntity = new Entity();
+ internals.context.secondEntity.addComponent(new internals.component());
+ internals.context.emptyEntity = new Entity();
+
+ // Add entity before the systems
+ internals.context.engine.addEntity(internals.context.firstEntity);
+
+ internals.context.engine.addSystem(internals.context.regularSystem, 100);
+ internals.context.engine.addSystem(internals.context.highPrioritySystem, 0);
+ internals.context.engine.addSystem(internals.context.lowPrioritySystem, 1000);
+
+ // Add entity after the systems
+ internals.context.engine.addEntity(internals.context.secondEntity);
+ internals.context.engine.addEntity(internals.context.emptyEntity);
+ });
+
+ it('should call added callback on added systems', () => {
+
+ // Ensure the added callback is being called
+ assert(internals.context.regularSystem.addedCalled);
+ assert(internals.context.highPrioritySystem.addedCalled);
+ assert(internals.context.lowPrioritySystem.addedCalled);
+ });
+
+ it('should send the engine instance in added callback', () => {
+
+ // Ensure the added callback is sending the engine
+ assert(internals.context.regularSystem.addedEngine instanceof Serpentity);
+ assert(internals.context.highPrioritySystem.addedEngine instanceof Serpentity);
+ assert(internals.context.lowPrioritySystem.addedEngine instanceof Serpentity);
+ });
+
+ it('should not add duplicate systems', () => {
+
+ const originalSystemsLength = internals.context.engine.systems.length;
+ const added = internals.context.engine.addSystem(internals.context.regularSystem, 0);
+ const newSystemsLength = internals.context.engine.systems.length;
+
+ // Ensure we don't add the same system twice
+ assert(!added);
+ assert.deepEqual(newSystemsLength, originalSystemsLength);
+ });
+
+ it('should call update callback on added systems', () => {
+
+ internals.context.engine.update(internals.delta);
+
+ // Ensure update function called
+ assert(!!internals.context.regularSystem.updateCalled);
+ assert(!!internals.context.highPrioritySystem.updateCalled);
+ assert(!!internals.context.lowPrioritySystem.updateCalled);
+ });
+
+ it('should keep proxied object behavior as expected', () => {
+
+ internals.context.engine.update(internals.delta);
+
+ assert(internals.context.highPrioritySystem.beforeCall === false);
+ assert(internals.context.highPrioritySystem.afterCall === true);
+ });
+
+ it('should emit an event for every changed property', () => {
+
+ internals.context.engine.update(internals.delta);
+
+ assert(internals.context.regularSystem.emittedEvents[0].property === 'called');
+ assert(internals.context.regularSystem.emittedEvents[0].from === false);
+ assert(internals.context.regularSystem.emittedEvents[0].to === true);
+ // 3 systems x 2 entities.
+ assert(internals.context.regularSystem.emittedEvents.length === 6);
+ });
+
+ it('should call update callback in the order of priorities', () => {
+
+ internals.context.engine.update(internals.delta);
+
+ // Ensure order of priorities
+ assert(internals.context.regularSystem.updateCalled < internals.context.lowPrioritySystem.updateCalled);
+ assert(internals.context.regularSystem.updateCalled > internals.context.highPrioritySystem.updateCalled);
+ });
+
+ it('should send the delta in the update callback', () => {
+
+ internals.context.engine.update(internals.delta);
+
+ // Ensure delta is being sent
+ assert.deepEqual(internals.context.regularSystem.updateDt, internals.delta);
+ assert.deepEqual(internals.context.highPrioritySystem.updateDt, internals.delta);
+ assert.deepEqual(internals.context.lowPrioritySystem.updateDt, internals.delta);
+ });
+
+ it('should no longer call removed systems', () => {
+
+ const originalSystemLength = internals.context.engine.systems.length;
+ const originalRemoved = internals.context.engine.removeSystem(internals.context.lowPrioritySystem);
+ const intermediateSystemLength = internals.context.engine.systems.length;
+ const finalRemoved = internals.context.engine.removeSystem(internals.context.lowPrioritySystem);
+ const finalSystemLength = internals.context.engine.systems.length;
+ internals.context.engine.update(internals.delta);
+
+ // Check for return value
+ assert(originalRemoved);
+ assert(!finalRemoved);
+
+ // Confirm that only removed if found by checking length of systems
+ // array
+ assert(originalSystemLength > intermediateSystemLength);
+ assert.deepEqual(finalSystemLength, intermediateSystemLength);
+
+ // Ensure callback is sent
+ assert(!internals.context.regularSystem.removedCalled);
+ assert(!internals.context.highPrioritySystem.removedCalled);
+ assert(!!internals.context.lowPrioritySystem.removedCalled);
+
+ // Ensure update is no longer sent
+ assert(!!internals.context.regularSystem.updateCalled);
+ assert(!!internals.context.highPrioritySystem.updateCalled);
+ assert(!internals.context.lowPrioritySystem.updateCalled);
+ });
+
+ it('should only call nodes in selected node collections', () => {
+
+ internals.context.engine.update(internals.delta);
+
+ // Ensure component is called for each entity
+ assert(!!internals.context.firstEntity._components[0].called);
+ assert(!!internals.context.secondEntity._components[0].called);
+
+ // Ensure entity not in node collection not called
+ assert(!!internals.context.firstEntity.called);
+ assert(!!internals.context.secondEntity.called);
+ assert(!internals.context.emptyEntity.called);
+ });
+
+ it('should stop showing removed entities', () => {
+
+ internals.context.engine.removeEntity(internals.context.secondEntity);
+ internals.context.engine.update(internals.delta);
+
+ assert(!!internals.context.firstEntity._components[0].called);
+ assert(!internals.context.secondEntity._components[0].called);
+
+ assert(!!internals.context.firstEntity.called);
+ assert(!internals.context.secondEntity.called);
+ assert(!internals.context.emptyEntity.called);
+ });
+
+ it('should not add duplicate components to entities', () => {
+
+ const originalComponentsLength = internals.context.secondEntity._components.length;
+ const result = internals.context.secondEntity.addComponent(new internals.component());
+ const newComponentsLength = internals.context.secondEntity._components.length;
+
+ assert(!result);
+ assert.deepEqual(newComponentsLength, originalComponentsLength);
+ });
+
+ it('should allow access to components by class', () => {
+
+ const firstComponent = internals.context.firstEntity.getComponent(internals.component);
+ const emptyComponent = internals.context.emptyEntity.getComponent(internals.component);
+
+ assert(firstComponent instanceof internals.component);
+ assert.deepEqual(emptyComponent, undefined);
+ });
+
+ it('should not add duplicate entities', () => {
+
+ const originalEntitiesLength = internals.context.engine.entities.length;
+ const added = internals.context.engine.addEntity(internals.context.firstEntity);
+ const finalEntitiesLength = internals.context.engine.entities.length;
+
+ assert(!added);
+
+ assert.deepEqual(finalEntitiesLength, originalEntitiesLength);
+ });
+
+ it('should remove entities', () => {
+
+ const originalEntityLength = internals.context.engine.entities.length;
+ const originalRemoved = internals.context.engine.removeEntity(internals.context.firstEntity);
+ const intermediateEntityLength = internals.context.engine.entities.length;
+ const finalRemoved = internals.context.engine.removeEntity(internals.context.firstEntity);
+ const finalEntityLength = internals.context.engine.entities.length;
+ internals.context.engine.update(internals.delta);
+
+ // Check for return value
+ assert(originalRemoved);
+ assert(!finalRemoved);
+
+ // Confirm that only removed if found by checking length of systems
+ // array
+ assert(originalEntityLength > intermediateEntityLength);
+ assert.deepEqual(finalEntityLength, intermediateEntityLength);
+
+ // Ensure callback is sent
+ assert(!internals.context.firstEntity.called);
+ assert(!!internals.context.secondEntity.called);
+ });
+
+ it('should not add duplicate entities', () => {
+
+ const originalEntitiesLength = internals.context.engine.entities.length;
+ const added = internals.context.engine.addEntity(internals.context.firstEntity);
+ const finalEntitiesLength = internals.context.engine.entities.length;
+
+ assert(!added);