]> git.r.bdr.sh - rbdr/serpentity/blame - test/integration.js
Make node components event emitters
[rbdr/serpentity] / test / integration.js
CommitLineData
6b973876 1import { describe, it, beforeEach, mock } from 'node:test';
3d7e8509 2import assert from 'node:assert';
4d421669 3import Serpentity, { Component, Entity, Node, System } from '../lib/serpentity.js';
3db7d198 4
b3b840f8 5const internals = {
4d421669
BB
6 context: {},
7 system: class TestSystem extends System {
b3b840f8 8 added(engine) {
3db7d198 9
b3b840f8
RBR
10 this.testNodes = engine.getNodes(internals.node);
11 this.addedCalled = true;
12 this.addedEngine = engine;
6b973876
RBR
13 this.emittedEvents = [];
14 this.beforeCall = null;
15 this.afterCall = null;
16
17 this.changeObserver = (event) => {
18 this.emittedEvents.push(event)
19 };
20
21 this.nodeAddedObserver = ({ node }) => {
22 node.test.addEventListener('change', this.changeObserver);
23 };
24
25 this.nodeRemovedObserver = ({ node }) => {
26 node.test.removeEventListener('change', this.changeObserver);
27 };
28
29 for (const node of this.testNodes) {
30 node.test.addEventListener('change', this.changeObserver);
31 }
32
33 this.testNodes.addEventListener('nodeAdded', this.nodeAddedObserver);
34 this.testNodes.addEventListener('nodeRemoved', this.nodeRemovedObserver);
35
3d7e8509 36 super.added(); // not needed, but takes care of coverage :P
3db7d198
BB
37 }
38
b3b840f8
RBR
39 removed(engine) {
40
6b973876
RBR
41 this.testNodes.removeEventListener('nodeAdded', this.nodeAddedObserver);
42 this.nodeAddedObserver = null;
43
44 this.testNodes.removeEventListener('nodeRemoved', this.nodeRemovedObserver);
45 this.nodeRemovedObserver = null;
46
47 for (const node of this.testNodes) {
48 node.test.removeEventListener('change', this.changeObserver);
49 }
50 this.changeObserver = null;
51
3db7d198 52 this.testNodes = null;
b3b840f8
RBR
53 this.removedCalled = true;
54 this.removedEngine = engine;
6b973876
RBR
55 this.emittedEvents = null;
56 this.beforeCall = null;
57 this.afterCall = null;
3d7e8509 58 super.removed(); // not needed, but takes care of coverage :P
3db7d198
BB
59 }
60
b3b840f8 61 update(dt) {
3db7d198 62
b3b840f8
RBR
63 this.updateCalled = Date.now();
64 this.updateDt = dt;
3db7d198 65
b3b840f8 66 for (const node of this.testNodes) {
6b973876 67 this.beforeCall = this.beforeCall === null ? node.test.called : this.beforeCall;
b3b840f8 68 node.test.called = true;
6b973876 69 this.afterCall = this.afterCall === null ? node.test.called : this.afterCall;
b3b840f8
RBR
70 node.entity.called = true;
71 }
3db7d198 72
b3b840f8 73 while (Date.now() === this.updateCalled) { /* pass some time */ }
3d7e8509 74 super.update(); // not needed, but takes care of coverage :P
b3b840f8
RBR
75 }
76 },
4d421669 77 component: class TestComponent extends Component {
b3b840f8 78 constructor(config) {
3db7d198 79
3db7d198
BB
80 super(config);
81
b3b840f8 82 this.called = false;
3db7d198 83 }
b3b840f8 84 },
4d421669 85 node: class TestNode extends Node {},
b3b840f8
RBR
86 delta: 10
87};
3db7d198 88
b3b840f8
RBR
89// adds a component to the node
90internals.node.types = {
91 test: internals.component
92};
3db7d198 93
3d7e8509 94describe('Loading', () => {
3db7d198 95
3d7e8509 96 it('Should export the main class', () => {
3db7d198 97
3d7e8509 98 assert(typeof Serpentity === 'function');
b3b840f8 99 });
3db7d198 100
3d7e8509 101 it('Should export the Entity class', () => {
3db7d198 102
3d7e8509 103 assert(typeof Entity === 'function');
b3b840f8 104 });
3db7d198 105
3d7e8509 106 it('Should export the Component class', () => {
3db7d198 107
3d7e8509 108 assert(typeof Component === 'function');
b3b840f8 109 });
3db7d198 110
3d7e8509 111 it('Should export the System class', () => {
3db7d198 112
3d7e8509 113 assert(typeof System === 'function');
b3b840f8 114 });
3db7d198 115
3d7e8509 116 it('Should export the Node class', () => {
3db7d198 117
3d7e8509 118 assert(typeof Node === 'function');
b3b840f8
RBR
119 });
120});
121
3d7e8509 122describe('Engine', () => {
b3b840f8 123
4d421669 124 beforeEach(() => {
b3b840f8 125
4d421669 126 internals.context.engine = new Serpentity();
b3b840f8 127
4d421669
BB
128 internals.context.regularSystem = new internals.system();
129 internals.context.highPrioritySystem = new internals.system();
130 internals.context.lowPrioritySystem = new internals.system();
b3b840f8 131
4d421669
BB
132 internals.context.firstEntity = new Entity();
133 internals.context.firstEntity.addComponent(new internals.component());
134 internals.context.secondEntity = new Entity();
135 internals.context.secondEntity.addComponent(new internals.component());
136 internals.context.emptyEntity = new Entity();
b3b840f8
RBR
137
138 // Add entity before the systems
4d421669 139 internals.context.engine.addEntity(internals.context.firstEntity);
b3b840f8 140
4d421669
BB
141 internals.context.engine.addSystem(internals.context.regularSystem, 100);
142 internals.context.engine.addSystem(internals.context.highPrioritySystem, 0);
143 internals.context.engine.addSystem(internals.context.lowPrioritySystem, 1000);
b3b840f8
RBR
144
145 // Add entity after the systems
4d421669
BB
146 internals.context.engine.addEntity(internals.context.secondEntity);
147 internals.context.engine.addEntity(internals.context.emptyEntity);
b3b840f8
RBR
148 });
149
3d7e8509 150 it('should call added callback on added systems', () => {
b3b840f8
RBR
151
152 // Ensure the added callback is being called
3d7e8509
RBR
153 assert(internals.context.regularSystem.addedCalled);
154 assert(internals.context.highPrioritySystem.addedCalled);
155 assert(internals.context.lowPrioritySystem.addedCalled);
b3b840f8
RBR
156 });
157
3d7e8509 158 it('should send the engine instance in added callback', () => {
b3b840f8
RBR
159
160 // Ensure the added callback is sending the engine
3d7e8509
RBR
161 assert(internals.context.regularSystem.addedEngine instanceof Serpentity);
162 assert(internals.context.highPrioritySystem.addedEngine instanceof Serpentity);
163 assert(internals.context.lowPrioritySystem.addedEngine instanceof Serpentity);
b3b840f8
RBR
164 });
165
3d7e8509 166 it('should not add duplicate systems', () => {
b3b840f8 167
4d421669
BB
168 const originalSystemsLength = internals.context.engine.systems.length;
169 const added = internals.context.engine.addSystem(internals.context.regularSystem, 0);
170 const newSystemsLength = internals.context.engine.systems.length;
b3b840f8
RBR
171
172 // Ensure we don't add the same system twice
3d7e8509
RBR
173 assert(!added);
174 assert.deepEqual(newSystemsLength, originalSystemsLength);
b3b840f8
RBR
175 });
176
3d7e8509 177 it('should call update callback on added systems', () => {
b3b840f8 178
4d421669 179 internals.context.engine.update(internals.delta);
b3b840f8
RBR
180
181 // Ensure update function called
3d7e8509
RBR
182 assert(!!internals.context.regularSystem.updateCalled);
183 assert(!!internals.context.highPrioritySystem.updateCalled);
184 assert(!!internals.context.lowPrioritySystem.updateCalled);
b3b840f8
RBR
185 });
186
6b973876
RBR
187 it('should keep proxied object behavior as expected', () => {
188
189 internals.context.engine.update(internals.delta);
190
191 assert(internals.context.highPrioritySystem.beforeCall === false);
192 assert(internals.context.highPrioritySystem.afterCall === true);
193 });
194
195 it('should emit an event for every changed property', () => {
196
197 internals.context.engine.update(internals.delta);
198
199 assert(internals.context.regularSystem.emittedEvents[0].property === 'called');
200 assert(internals.context.regularSystem.emittedEvents[0].from === false);
201 assert(internals.context.regularSystem.emittedEvents[0].to === true);
202 // 3 systems x 2 entities.
203 assert(internals.context.regularSystem.emittedEvents.length === 6);
204 });
205
3d7e8509 206 it('should call update callback in the order of priorities', () => {
b3b840f8 207
4d421669 208 internals.context.engine.update(internals.delta);
b3b840f8
RBR
209
210 // Ensure order of priorities
3d7e8509
RBR
211 assert(internals.context.regularSystem.updateCalled < internals.context.lowPrioritySystem.updateCalled);
212 assert(internals.context.regularSystem.updateCalled > internals.context.highPrioritySystem.updateCalled);
b3b840f8
RBR
213 });
214
3d7e8509 215 it('should send the delta in the update callback', () => {
b3b840f8 216
4d421669 217 internals.context.engine.update(internals.delta);
b3b840f8
RBR
218
219 // Ensure delta is being sent
3d7e8509
RBR
220 assert.deepEqual(internals.context.regularSystem.updateDt, internals.delta);
221 assert.deepEqual(internals.context.highPrioritySystem.updateDt, internals.delta);
222 assert.deepEqual(internals.context.lowPrioritySystem.updateDt, internals.delta);
b3b840f8
RBR
223 });
224
3d7e8509 225 it('should no longer call removed systems', () => {
b3b840f8 226
4d421669
BB
227 const originalSystemLength = internals.context.engine.systems.length;
228 const originalRemoved = internals.context.engine.removeSystem(internals.context.lowPrioritySystem);
229 const intermediateSystemLength = internals.context.engine.systems.length;
230 const finalRemoved = internals.context.engine.removeSystem(internals.context.lowPrioritySystem);
231 const finalSystemLength = internals.context.engine.systems.length;
232 internals.context.engine.update(internals.delta);
b3b840f8 233
4d421669 234 // Check for return value
3d7e8509
RBR
235 assert(originalRemoved);
236 assert(!finalRemoved);
b3b840f8
RBR
237
238 // Confirm that only removed if found by checking length of systems
239 // array
3d7e8509
RBR
240 assert(originalSystemLength > intermediateSystemLength);
241 assert.deepEqual(finalSystemLength, intermediateSystemLength);
b3b840f8
RBR
242
243 // Ensure callback is sent
3d7e8509
RBR
244 assert(!internals.context.regularSystem.removedCalled);
245 assert(!internals.context.highPrioritySystem.removedCalled);
246 assert(!!internals.context.lowPrioritySystem.removedCalled);
b3b840f8
RBR
247
248 // Ensure update is no longer sent
3d7e8509
RBR
249 assert(!!internals.context.regularSystem.updateCalled);
250 assert(!!internals.context.highPrioritySystem.updateCalled);
251 assert(!internals.context.lowPrioritySystem.updateCalled);
b3b840f8
RBR
252 });
253
3d7e8509 254 it('should only call nodes in selected node collections', () => {
b3b840f8 255
4d421669 256 internals.context.engine.update(internals.delta);
b3b840f8
RBR
257
258 // Ensure component is called for each entity
3d7e8509
RBR
259 assert(!!internals.context.firstEntity._components[0].called);
260 assert(!!internals.context.secondEntity._components[0].called);
b3b840f8
RBR
261
262 // Ensure entity not in node collection not called
3d7e8509
RBR
263 assert(!!internals.context.firstEntity.called);
264 assert(!!internals.context.secondEntity.called);
265 assert(!internals.context.emptyEntity.called);
b3b840f8
RBR
266 });
267
3d7e8509 268 it('should stop showing removed entities', () => {
b3b840f8 269
4d421669
BB
270 internals.context.engine.removeEntity(internals.context.secondEntity);
271 internals.context.engine.update(internals.delta);
b3b840f8 272
3d7e8509
RBR
273 assert(!!internals.context.firstEntity._components[0].called);
274 assert(!internals.context.secondEntity._components[0].called);
b3b840f8 275
3d7e8509
RBR
276 assert(!!internals.context.firstEntity.called);
277 assert(!internals.context.secondEntity.called);
278 assert(!internals.context.emptyEntity.called);
b3b840f8
RBR
279 });
280
3d7e8509 281 it('should not add duplicate components to entities', () => {
b3b840f8 282
4d421669
BB
283 const originalComponentsLength = internals.context.secondEntity._components.length;
284 const result = internals.context.secondEntity.addComponent(new internals.component());
285 const newComponentsLength = internals.context.secondEntity._components.length;
b3b840f8 286
3d7e8509
RBR
287 assert(!result);
288 assert.deepEqual(newComponentsLength, originalComponentsLength);
b3b840f8
RBR
289 });
290
3d7e8509 291 it('should allow access to components by class', () => {
b3b840f8 292
4d421669
BB
293 const firstComponent = internals.context.firstEntity.getComponent(internals.component);
294 const emptyComponent = internals.context.emptyEntity.getComponent(internals.component);
b3b840f8 295
3d7e8509
RBR
296 assert(firstComponent instanceof internals.component);
297 assert.deepEqual(emptyComponent, undefined);
b3b840f8
RBR
298 });
299
3d7e8509 300 it('should not add duplicate entities', () => {
4d421669
BB
301
302 const originalEntitiesLength = internals.context.engine.entities.length;
303 const added = internals.context.engine.addEntity(internals.context.firstEntity);
304 const finalEntitiesLength = internals.context.engine.entities.length;
b3b840f8 305
3d7e8509 306 assert(!added);
b3b840f8 307
3d7e8509 308 assert.deepEqual(finalEntitiesLength, originalEntitiesLength);
b3b840f8
RBR
309 });
310
3d7e8509 311 it('should remove entities', () => {
b3b840f8 312
4d421669
BB
313 const originalEntityLength = internals.context.engine.entities.length;
314 const originalRemoved = internals.context.engine.removeEntity(internals.context.firstEntity);
315 const intermediateEntityLength = internals.context.engine.entities.length;
316 const finalRemoved = internals.context.engine.removeEntity(internals.context.firstEntity);
317 const finalEntityLength = internals.context.engine.entities.length;
318 internals.context.engine.update(internals.delta);
b3b840f8 319
4d421669 320 // Check for return value
3d7e8509
RBR
321 assert(originalRemoved);
322 assert(!finalRemoved);
b3b840f8
RBR
323
324 // Confirm that only removed if found by checking length of systems
325 // array
3d7e8509
RBR
326 assert(originalEntityLength > intermediateEntityLength);
327 assert.deepEqual(finalEntityLength, intermediateEntityLength);
b3b840f8
RBR
328
329 // Ensure callback is sent
3d7e8509
RBR
330 assert(!internals.context.firstEntity.called);
331 assert(!!internals.context.secondEntity.called);
3db7d198 332 });
6b973876
RBR
333
334 it('should not add duplicate entities', () => {
335
336 const originalEntitiesLength = internals.context.engine.entities.length;
337 const added = internals.context.engine.addEntity(internals.context.firstEntity);
338 const finalEntitiesLength = internals.context.engine.entities.length;
339
340 assert(!added);
341
342 assert.deepEqual(finalEntitiesLength, originalEntitiesLength);
343 });
b3b840f8 344});