UNPKG

rubricjs

Version:

An es6 component, entity, system, framework with a minimal core.

405 lines (326 loc) 19.3 kB
/* eslint-env mocha, es6 */ const EntityManager = require(`../dist/entity-manager.js`); const expect = require('chai').expect; const auid = require('alphastring'); function createComponentClass() { let componentName = auid(); let propertyName = auid(); /* eslint-disable max-len, no-new-func */ return Function(`return function ${componentName}(){this.${propertyName} = 'foo'; this.propertyName = '${propertyName}';}`)(); } describe('EntityManager Class', function() { let em; let idsSymbol; let registrySymbol; let ComponentClass1; let component1; let ComponentClass2; let component2; beforeEach(function() { em = new EntityManager(); ComponentClass1 = createComponentClass(); component1 = new ComponentClass1(); ComponentClass2 = createComponentClass(); component2 = new ComponentClass2(); /* eslint-disable no-undef */ let keys = Reflect.ownKeys(em); /* eslint-enable no-undef */ keys.forEach((key) => { let prop = em[key]; if (typeof key === 'symbol') { if (prop.constructor.name === 'Array') { idsSymbol = key; } else if (prop.constructor.name === 'Map') { registrySymbol = key; } } return false; }); if (!idsSymbol || !registrySymbol) { throw new Error('Cannot reflect keys from EntityManager class'); } }); /* ██████ ██████ ███████ █████ ████████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██████ █████ ███████ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██ ███████ */ describe('One the subject of creating an instance, it ', function() { it('should result private lists', function() { expect(em.entityIds).to.be.undefined; expect(em.componentRegistry).to.be.undefined; }); it('should result in empty lists', function() { expect(em[idsSymbol]).to.have.length(0); expect(em[registrySymbol]).to.have.property('size').equals(0); }); }); /* ███████ ███ ██ ████████ ██ ████████ ██ ███████ ███████ ██ ████ ██ ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ █████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ███████ ███████ */ /* ██████ ██████ ███████ █████ ████████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██████ █████ ███████ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██ ███████ */ describe('On the subject of creating entities, it, ', function() { it('should result in expected when incrementing ids', function() { expect(em.createEntity()).to.equal(10); expect(em.createEntity()).to.equal(11); }); it('should search for free ids if max reached', function() { em.lowestFreeId = em.maxId; expect(em.createEntity()).to.equal(0); expect(em.createEntity()).to.equal(1); }); it('should throw if no ids available', function() { em.maxId = 0; expect(() => { em.createEntity(); }).to.throw(RangeError, /unsafe/i); }); }); /* ██████ ██████ ███ ███ ██████ ██████ ███ ██ ███████ ███ ██ ████████ ███████ ██ ██ ██ ████ ████ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██████ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██████ ██████ ██ ██ ██ ██████ ██ ████ ███████ ██ ████ ██ ███████ */ /* █████ ██████ ██████ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██████ ██████ */ describe('On the subject of adding components, it, ', function() { it('should result in expected structure', function() { let entity1 = em.createEntity(); em.createEntity(entity1); em.addComponent(component1, entity1); // let mockRegistry = new Map(); // mockRegistry.set(component1.constructor.name, new Map()); // mockRegistry.get(component1.constructor.name).set(entity1, component1); expect(em[registrySymbol]).to.have.property('size').equals(1); expect(em[registrySymbol].get(component1.constructor.name)).to.have.property('size').equals(1); expect(em[registrySymbol].get(component1.constructor.name).get(entity1)).to.have.property(component1.propertyName).equals('foo'); }); it('should not cause intersections between components', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); em.addComponent(component2, entity1); // let mockRegistry = new Map(); // mockRegistry.set(component1.constructor.name, new Map()); // mockRegistry.get(component1.constructor.name).set(entity1, component1); // // mockRegistry.set(component2.constructor.name, new Map()); // mockRegistry.get(component2.constructor.name).set(entity1, component2); // expect(em[registrySymbol]).to.eql(mockRegistry); expect(em[registrySymbol]).to.have.property('size').equal(2); expect(em[registrySymbol].get(component1.constructor.name)).to.have.property('size').equal(1); expect(em[registrySymbol].get(component2.constructor.name)).to.have.property('size').equal(1); expect(em[registrySymbol].get(component1.constructor.name).get(entity1)).to.equal(component1); expect(em[registrySymbol].get(component2.constructor.name).get(entity1)).to.equal(component2); }); it('should throw if non-object is passed for component', function() { let entity1 = em.createEntity(); expect(() => { em.addComponent(1, entity1); }).to.throw(TypeError, /component/i); }); }); /* ██████ ███████ ██ ███████ ████████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ █████ ██ █████ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██████ ███████ ███████ ███████ ██ ███████ */ describe('On the subject of deleting entities, it, ', function() { it('should result in empty list', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); em.deleteEntity(); expect(em[idsSymbol]).to.have.length(0); expect(em[registrySymbol]).to.have.property('size').equal(1); expect(em[registrySymbol].get(component1.constructor.name).constructor.name).to.equal('Map'); expect(em[registrySymbol].get(component1.constructor.name)).to.have.property('size').equal(1); }); it('should clear all', function() { em.createEntity(); em.createEntity(); em.createEntity(); em.deleteAllEntities(); expect(em[idsSymbol]).to.have.length(0); expect(em[registrySymbol]).to.have.property('size').equal(0); }); }); /* ██████ ███████ ████████ ██████ ██ ███████ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██████ █████ ██ ██████ ██ █████ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ███████ ████ ███████ */ describe('On the subject of creating entities, it, ', function() { it('should return proper entities', function() { let entity1 = em.createEntity(); let entity2 = em.createEntity(); em.addComponent(component1, entity1); em.addComponent(component2, entity2); let retrieved = em.getEntitiesWithComponent(component1.constructor.name); expect(retrieved).eql([entity1]); retrieved = em.getEntitiesWithComponent(component2.constructor.name); expect(retrieved).eql([entity2]); }); it('should return empty array if no entities found', function() { let retrieved = em.getEntitiesWithComponent('foo'); expect(retrieved).to.have.length(0); }); // it('should return entities from intersection of component list', function() { // let entity1 = em.createEntity(); // let entity2 = em.createEntity(); // em.addComponent(component1, entity1); // em.addComponent(component2, entity1); // // em.addComponent(component2, entity2); // // let retrieved = em.getEntitiesWithComponents([component1.constructor.name, component2.constructor.name]); // expect(retrieved).eql([entity1]); // retrieved = em.getEntitiesWithComponents([component2.constructor.name]); // expect(retrieved).to.have.length(2); // expect(retrieved[0]).not.equal(retrieved[1]); // }); it('should return entities from mixed list of constructor names and instances', function() { let entity1 = em.createEntity(); let entity2 = em.createEntity(); em.addComponent(component1, entity1); em.addComponent(component2, entity1); em.addComponent(component2, entity2); let retrieved = em.getEntitiesWithComponents([component1, component2.constructor.name]); expect(retrieved).eql([entity1]); retrieved = em.getEntitiesWithComponents([component2]); expect(retrieved).to.have.length(2); expect(retrieved[0]).not.equal(retrieved[1]); }); it('should throw when a non array is presented to getEntitiesWithComponents', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); expect(() => { em.getEntitiesWithComponents(component1); }).to.throw(TypeError, /array/i); }); it('should return empty array if no entities exist', function() { let retrieved = em.getEntitiesWithComponents(['bar']); expect(retrieved).to.have.length(0); }); it('should return empty array if no entities found', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); let retrieved = em.getEntitiesWithComponents([component1, component2]); expect(retrieved).to.have.length(0); }); it('should retrieve all entities', function() { em.createEntity(); em.createEntity(); em.createEntity(); let retrieved = em.getAllEntities(); expect(retrieved).to.have.length(3); expect(retrieved[0]).not.equal(retrieved[1]); expect(retrieved[0]).not.equal(retrieved[2]); expect(retrieved[1]).not.equal(retrieved[2]); }); }); /* ██████ ███████ ███ ███ ██████ ██ ██ ███████ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██████ █████ ██ ████ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██████ ████ ███████ */ describe('On the subject of removing components, it, ', function() { it('should return false when no component exists', function() { let entity1 = em.createEntity(); expect(em.removeComponent(component1.constructor.name, entity1)).to.equal(false); }); it('should return the removed component', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); expect(em.removeComponent(component1.constructor.name, entity1)).to.equal(component1); }); it('should work with constructor name or instance', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); em.removeComponent(component1.constructor.name, entity1); em.addComponent(component1, entity1); em.removeComponent(component1, entity1); // let mockRegistry = new Map(); // mockRegistry.set(component1.constructor.name, new Map()); // mockRegistry.get(component1.constructor.name).set(entity1, component1); // mockRegistry.get(component1.constructor.name).delete(entity1); expect(em[registrySymbol]).to.have.property('size').equal(1); expect(em[registrySymbol].get(component1.constructor.name)).to.have.property('size').equal(0); expect(em[registrySymbol].get(component1.constructor.name).has(entity1)).equal(false); // expect(em[registrySymbol]).eql(mockRegistry); }); it('should generate expected structure', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); em.addComponent(component2, entity1); em.removeComponent(component1, entity1); // let mockRegistry = new Map(); // mockRegistry.set(component1.constructor.name, new Map()); // mockRegistry.get(component1.constructor.name).set(entity1, component1); // // mockRegistry.set(component2.constructor.name, new Map()); // mockRegistry.get(component2.constructor.name).set(entity1, component2); // // mockRegistry.get(component1.constructor.name).delete(entity1); // // expect(em[registrySymbol]).eql(mockRegistry); expect(em[registrySymbol]).to.have.property('size').equal(2); expect(em[registrySymbol].get(component1.constructor.name)).to.have.property('size').equal(0); expect(em[registrySymbol].get(component2.constructor.name)).to.have.property('size').equal(1); expect(em[registrySymbol].get(component2.constructor.name).get(entity1)).to.equal(component2); }); it('should return false if no component was removed', function() { let entity1 = em.createEntity(); let entity2 = em.createEntity(); em.addComponent(component1, entity1); expect(em.removeComponent(component1)).to.not.equal(entity2); expect(em.removeComponent(component2)).to.not.equal(entity2); }); }); /* ██████ ███████ ████████ ██████ ██ ███████ ██ ██ ███████ ███████ ███ ██ ████████ ██ ████████ ██ ███████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██████ █████ ██ ██████ ██ █████ ██ ██ █████ █████ ██ ██ ██ ██ ██ ██ ██ █████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ███████ ████ ███████ ███████ ██ ████ ██ ██ ██ ██ ███████ ███████ */ describe('On the subject of retrieving entities, it, ', function() { it('should return proper instance', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); let retrieved = em.getComponent(component1.constructor.name, entity1); expect(retrieved).equal(component1); }); it('should work with constructor name or instance', function() { let entity1 = em.createEntity(); em.addComponent(component1, entity1); let retrieved = em.getComponent(component1.constructor.name, entity1); expect(retrieved).equal(component1); retrieved = em.getComponent(component1, entity1); expect(retrieved).equal(component1); }); }); });