UNPKG

networked-aframe

Version:

A web framework for building multi-user virtual reality experiences.

450 lines (357 loc) 13.7 kB
/* global NAF, assert, setup, suite, test, teardown, sinon, THREE */ var sinonTest = require("sinon-test"); sinon.test = sinonTest(sinon); require('aframe'); var helpers = require('./helpers'); var naf = require('../../src/NafIndex'); var NetworkEntities = require('../../src/NetworkEntities'); suite('NetworkEntities', function() { var scene; var entities; var entityData; var firstUpdateData; function initScene(done) { var opts = { assets: [ '<template id="template1"><a-entity></a-entity></template>', '<template id="template2"><a-box></a-box></template>', '<template id="template3"><a-sphere></a-sphere></template>', '<template id="template4"><a-sphere><a-entity class="test-child"></a-entity></a-sphere></template>' ] }; scene = helpers.sceneFactory(opts); NAF.schemas.add({ template: '#template1', components: [ 'position', 'rotation' ] }); NAF.schemas.add({ template: '#template2', components: [ 'position', 'rotation' ] }); NAF.schemas.add({ template: '#template3', components: [ 'position', 'rotation' ] }); NAF.schemas.add({ template: '#template4', components: [ 'position', 'rotation' ] }); naf.utils.whenEntityLoaded(scene, done); } setup(function(done) { naf.options.useLerp = false; naf.schemas.clear(); entities = new NetworkEntities(); firstUpdateData = { networkId: 'test1', owner: 'abcdefg', parent: null, template: '#template1', components: { 0: '1 2 3', 1: '4 3.5 2' }, isFirstSync: true }; entityData = { networkId: 'test1', owner: 'abcdefg', parent: null, template: '#template1', components: { 0: '1 2 3', 1: '4 3.5 2' }, isFirstSync: false }; initScene(done); naf.connection.isMineAndConnected = sinon.stub(); }); teardown(function() { scene.parentElement.removeChild(scene); }); suite('registerEntity', function() { test('adds entity to list', function() { var entity = 'i-am-entity'; var networkId = 'nid1'; entities.registerEntity(networkId, entity); var result = entities.getEntity('nid1'); assert.equal(result, 'i-am-entity'); }); }); suite('createRemoteEntity', function() { test('returns entity', function() { var entity = entities.createRemoteEntity(entityData); assert.isOk(entity); }); }); suite('setInitialComponents', function() { test('entity components set immediately', function(done) { var entity = entities.createRemoteEntity(entityData); scene.appendChild(entity); naf.utils.whenEntityLoaded(entity, function() { var position = entity.getAttribute('position'); var rotation = entity.getAttribute('rotation'); assert.deepEqual(position, new THREE.Vector3(1,2,3)); assert.deepEqual(rotation, {x: 4, y: 3.5, z: 2}); done(); }); }); test('entity sets correct first update data', function(done) { var entity = entities.createRemoteEntity(firstUpdateData); assert.equal(entity.firstUpdateData, firstUpdateData); scene.appendChild(entity); naf.utils.whenEntityLoaded(entity, function() { // entity.firstUpdateData was freed assert.equal(entity.firstUpdateData, undefined); done(); }); }); test('entity sets correct first update data with updated data', function(done) { var entity = entities.createRemoteEntity(firstUpdateData); assert.equal(entity.firstUpdateData, firstUpdateData); scene.appendChild(entity); var entityDataUpdate = { // same as firstUpdateData with components changes and isFirstSync: false networkId: 'test1', owner: 'abcdefg', parent: null, template: '#template1', components: { 0: '1 2 4', // changed from "1 2 3" to "1 2 4" // 1: '4 3.5 2' }, isFirstSync: false }; // simulate receiving a network update before the networked component is initialized entities.updateEntity(entityDataUpdate.owner, 'u', entityDataUpdate, undefined); var mergedData = {...firstUpdateData, components: {...firstUpdateData.components, ...entityDataUpdate.components}}; assert.deepEqual(entity.firstUpdateData, mergedData); naf.utils.whenEntityLoaded(entity, function() { assert.equal(entity.firstUpdateData, undefined); done(); }); }); test('entity sets correct networked component', function(done) { var entity = entities.createRemoteEntity(entityData); scene.appendChild(entity); naf.utils.whenEntityLoaded(entity, function() { var componentData = entity.components.networked.data; assert.equal(componentData.template, '#template1', 'template'); assert.equal(componentData.networkId, 'test1', 'networkId'); assert.equal(componentData.owner, 'abcdefg', 'owner'); done(); }); }); }) suite('updateEntity', function() { teardown(function() { NAF.options.firstSyncSource = null; }); test('first update creates new entity', sinon.test(function() { var mockEl = document.createElement('a-entity'); this.stub(entities, 'createRemoteEntity').returns(mockEl); entities.updateEntity('client', 'u', firstUpdateData); assert.isTrue(entities.createRemoteEntity.calledWith(firstUpdateData)); })); test('second update updates entity', sinon.test(function() { var entity = entities.createRemoteEntity(entityData); var networkUpdate = this.stub(entity.components.networked, "networkUpdate"); entities.registerEntity(entityData.networkId, entity); entities.updateEntity('client', 'u', entityData); // updates entity assert(networkUpdate.calledWith(entityData)); })); test('entity with parent that has not been created is not created yet', sinon.test(function() { var mockEl = document.createElement('a-entity'); this.stub(entities, 'createRemoteEntity').returns(mockEl); entityData.parent = 'non-existent-parent'; entities.updateEntity('client', 'u', entityData); assert.isFalse(entities.createRemoteEntity.calledWith(entityData)); })); test('first update ignored from disallowed source', sinon.test(function() { var mockEl = document.createElement('a-entity'); this.stub(entities, 'createRemoteEntity').returns(mockEl); NAF.options.firstSyncSource = 'allowed-source'; entities.updateEntity('client', 'u', entityData, 'disallowed-source'); assert.isFalse(entities.createRemoteEntity.calledWith(entityData)); })); test('child entities created after parent', sinon.test(function() { var entityDataParent = firstUpdateData; var entityDataChild1 = { networkId: 'test-child-1', owner: 'abcdefg', parent: 'test1', template: '#template1', components: { 0: '1 2 3', 1: '4 3.5 2' }, isFirstSync: true }; var entityDataChild2 = { networkId: 'test-child-2', owner: 'abcdefg', parent: 'test1', template: '#template1', components: { 0: '1 2 3', 1: '4 3.5 2' }, isFirstSync: true }; var child1 = document.createElement('a-entity'); var child2 = document.createElement('a-entity'); var parent = document.createElement('a-entity'); var stub = this.stub(entities, 'createRemoteEntity'); stub.onCall(0).returns(parent); stub.onCall(1).returns(child1); stub.onCall(2).returns(child2); entities.updateEntity('client', 'u', entityDataChild1); entities.updateEntity('client', 'u', entityDataChild2); assert.isFalse(entities.createRemoteEntity.calledWith(entityDataChild1), 'does not create child 1'); assert.isFalse(entities.createRemoteEntity.calledWith(entityDataChild2), 'does not create child 2'); entities.updateEntity('client', 'u', entityDataParent); entities.registerEntity('test1', parent); assert.equal(entities.createRemoteEntity.callCount, 3); assert.isTrue(entities.createRemoteEntity.calledWith(entityDataParent), 'creates parent'); assert.isTrue(entities.createRemoteEntity.calledWith(entityDataChild1), 'creates child 1 after parent'); assert.isTrue(entities.createRemoteEntity.calledWith(entityDataChild2), 'creates child 2 after parent'); })); }); suite('completeSync', function() { test('no network entities', function() { entities.completeSync(); }); // These tests broke when when we moved from syncAll as an event to a direct function call. // A correct test would spy on that method, but I could not figure out how to spy on that method in this context. // test('emits sync on 3 entities', function() { // var entityList = []; // for (let i = 0; i < 3; i++) { // entityData.networkId = i; // var entity = document.createElement('a-entity'); // entities.registerEntity(entityData.networkId, entity); // entityList.push(entity); // sinon.spy(entity, 'emit'); // } // entities.completeSync(); // for (let i = 0; i < 3; i++) { // assert.isTrue(entityList[i].emit.calledWith('syncAll')) // } // }); // test('emits sync on many entities', function() { // var entityList = []; // for (let i = 0; i < 20; i++) { // entityData.networkId = i; // var entity = document.createElement('a-entity'); // entities.registerEntity(entityData.networkId, entity); // entityList.push(entity); // sinon.spy(entity, 'emit'); // } // entities.completeSync(); // for (let i = 0; i < 20; i++) { // assert.isTrue(entityList[i].emit.calledWith('syncAll')) // } // }); test('does not emit sync on removed entity', function() { var entity = document.createElement('a-entity'); entities.registerEntity(entityData.networkId, entity); scene.appendChild(entity); sinon.spy(entity, 'emit'); entities.removeEntity(entityData.networkId); entities.completeSync(); assert.isFalse(entity.emit.calledWith('syncAll')); }); }); suite('removeEntity', function() { test('correct id', function() { var entity = document.createElement('a-entity'); entities.registerEntity(entityData.networkId, entity); scene.appendChild(entity); var removedEntity = entities.removeEntity(entityData.networkId); assert.equal(removedEntity, entity); }); test('wrong id', function() { var entity = document.createElement('a-entity'); entities.registerEntity(entityData.networkId, entity); scene.appendChild(entity); var result = entities.removeEntity('wrong'); assert.isNull(result); }); test('no entities', function() { var result = entities.removeEntity('wrong'); assert.isNull(result); }); }); suite('removeRemoteEntity', function() { test('calls removeEntity with id', function() { var data = { networkId: 'testId' }; entities.removeEntity = sinon.stub(); entities.removeRemoteEntity('client1', 'type1', data); assert.isTrue(entities.removeEntity.calledWith('testId')); }); }); suite('removeEntitiesOfClient', function() { test('removing many entities', sinon.test(function() { var entityList = []; for (var i = 0; i < 3; i++) { var el = document.createElement('a-entity'); entities.registerEntity(i, el); scene.appendChild(el); entityList.push(el); } this.stub(naf.utils, 'getCreator').returns(entityData.owner); var removedEntities = entities.removeEntitiesOfClient(entityData.owner); assert.equal(removedEntities.length, 3); })); test('other entities', sinon.test(function() { var el = document.createElement('a-entity'); entities.registerEntity(entityData.networkId, el); this.stub(naf.utils, 'getNetworkOwner').returns('a'); var removedEntities = entities.removeEntitiesOfClient('b'); assert.equal(removedEntities.length, 0); })); test('no entities', function() { var removedEntities = entities.removeEntitiesOfClient(entityData.owner); assert.equal(removedEntities.length, 0); }); }); suite('getEntity', function() { test('normal', function() { var testEntity = { test: true }; entities.entities[entityData.networkId] = testEntity; var result = entities.getEntity(entityData.networkId); assert.equal(result, testEntity); }); test('incorrect id', function() { var testEntity = { test: true }; entities.entities[entityData.networkId] = testEntity; var result = entities.getEntity('wrong'); assert.equal(result, null); }); }); suite('hasEntity', function() { test('normal', function() { var testEntity = { test: true }; entities.entities[entityData.networkId] = testEntity; var result = entities.hasEntity(entityData.networkId); assert.isTrue(result); }); test('incorrect id', function() { var testEntity = { test: true }; entities.entities[entityData.networkId] = testEntity; var result = entities.hasEntity('wrong'); assert.isFalse(result); }); }); });