-'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');
- }
+import { describe, it, beforeEach } from 'node:test';
+import assert from 'node:assert';
+import Serpentity, { Component, Entity, Node, System } from '../lib/serpentity.js';
- removed (engine) {
- this.testNodes = null;
- console.log('Engine is serpentity: ' + (engine instanceof Serpentity ? 'OK' : 'FAIL'));
- console.log('System removed callback: EXEC OK');
- }
+const internals = {
+ context: {},
+ system: class TestSystem extends 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;
+ super.added(); // not needed, but takes care of coverage :P
}
- 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;
+ super.removed(); // not needed, but takes care of coverage :P
}
- 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 */ }
+ super.update(); // not needed, but takes care of coverage :P
+ }
+ },
+ component: class TestComponent extends 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 Node {},
+ delta: 10
+};
+
+// adds a component to the node
+internals.node.types = {
+ test: internals.component
+};
- let TestNode = class TestNode extends Serpentity.Node {};
- TestNode.types = {
- test : TestComponent
- };
- console.log('TestNode: CREATE OK');
+describe('Loading', () => {
- console.log('\n## Adding system to the engine');
+ it('Should export the main class', () => {
+
+ assert(typeof Serpentity === 'function');
+ });
- let engine = new Serpentity();
- console.log('engine: CREATE OK');
+ it('Should export the Entity class', () => {
- engine.addSystem(testSystem, 0);
+ assert(typeof Entity === 'function');
+ });
- console.log('\n## Running update without any entities');
- engine.update(10);
+ it('Should export the Component class', () => {
- 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);
+ assert(typeof Component === 'function');
+ });
- console.log('\n## Adding Low Priority System');
- engine.addSystem(lowProTestSystem, 10);
- engine.update(10);
+ it('Should export the System class', () => {
- console.log('\n## Adding Mid Priority System');
- engine.addSystem(midProTestSystem, 5);
- engine.update(10);
+ assert(typeof System === 'function');
+ });
- console.log('\n## Removing the system and readding');
- engine.removeSystem(testSystem);
- engine.update(10);
- engine.addSystem(testSystem, 0);
- engine.update(10);
+ it('Should export the Node class', () => {
- console.log('\n## Adding a second entity');
- entity = new Serpentity.Entity();
- entity.addComponent(new TestComponent());
- engine.addEntity(entity);
- engine.update(10);
+ assert(typeof Node === 'function');
+ });
+});
- console.log('\n## Removing entity');
- engine.removeEntity(entity);
- engine.update(10);
+describe('Engine', () => {
- console.log('\n## Removing system');
- engine.removeSystem(testSystem);
- engine.update(10);
+ 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 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);
-if (typeof require === 'function') {
- let Serpentity = require('serpentity');
- test(Serpentity);
-} else {
- window.addEventListener('load', function () {
- test(window.Serpentity);
+ // Ensure callback is sent
+ assert(!internals.context.firstEntity.called);
+ assert(!!internals.context.secondEntity.called);
});
-}
+});