]> git.r.bdr.sh - rbdr/serpentity/blame_incremental - test/integration.js
Make node components event emitters
[rbdr/serpentity] / test / integration.js
... / ...
CommitLineData
1import { describe, it, beforeEach, mock } from 'node:test';
2import assert from 'node:assert';
3import Serpentity, { Component, Entity, Node, System } from '../lib/serpentity.js';
4
5const internals = {
6 context: {},
7 system: class TestSystem extends System {
8 added(engine) {
9
10 this.testNodes = engine.getNodes(internals.node);
11 this.addedCalled = true;
12 this.addedEngine = engine;
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
36 super.added(); // not needed, but takes care of coverage :P
37 }
38
39 removed(engine) {
40
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
52 this.testNodes = null;
53 this.removedCalled = true;
54 this.removedEngine = engine;
55 this.emittedEvents = null;
56 this.beforeCall = null;
57 this.afterCall = null;
58 super.removed(); // not needed, but takes care of coverage :P
59 }
60
61 update(dt) {
62
63 this.updateCalled = Date.now();
64 this.updateDt = dt;
65
66 for (const node of this.testNodes) {
67 this.beforeCall = this.beforeCall === null ? node.test.called : this.beforeCall;
68 node.test.called = true;
69 this.afterCall = this.afterCall === null ? node.test.called : this.afterCall;
70 node.entity.called = true;
71 }
72
73 while (Date.now() === this.updateCalled) { /* pass some time */ }
74 super.update(); // not needed, but takes care of coverage :P
75 }
76 },
77 component: class TestComponent extends Component {
78 constructor(config) {
79
80 super(config);
81
82 this.called = false;
83 }
84 },
85 node: class TestNode extends Node {},
86 delta: 10
87};
88
89// adds a component to the node
90internals.node.types = {
91 test: internals.component
92};
93
94describe('Loading', () => {
95
96 it('Should export the main class', () => {
97
98 assert(typeof Serpentity === 'function');
99 });
100
101 it('Should export the Entity class', () => {
102
103 assert(typeof Entity === 'function');
104 });
105
106 it('Should export the Component class', () => {
107
108 assert(typeof Component === 'function');
109 });
110
111 it('Should export the System class', () => {
112
113 assert(typeof System === 'function');
114 });
115
116 it('Should export the Node class', () => {
117
118 assert(typeof Node === 'function');
119 });
120});
121
122describe('Engine', () => {
123
124 beforeEach(() => {
125
126 internals.context.engine = new Serpentity();
127
128 internals.context.regularSystem = new internals.system();
129 internals.context.highPrioritySystem = new internals.system();
130 internals.context.lowPrioritySystem = new internals.system();
131
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();
137
138 // Add entity before the systems
139 internals.context.engine.addEntity(internals.context.firstEntity);
140
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);
144
145 // Add entity after the systems
146 internals.context.engine.addEntity(internals.context.secondEntity);
147 internals.context.engine.addEntity(internals.context.emptyEntity);
148 });
149
150 it('should call added callback on added systems', () => {
151
152 // Ensure the added callback is being called
153 assert(internals.context.regularSystem.addedCalled);
154 assert(internals.context.highPrioritySystem.addedCalled);
155 assert(internals.context.lowPrioritySystem.addedCalled);
156 });
157
158 it('should send the engine instance in added callback', () => {
159
160 // Ensure the added callback is sending the engine
161 assert(internals.context.regularSystem.addedEngine instanceof Serpentity);
162 assert(internals.context.highPrioritySystem.addedEngine instanceof Serpentity);
163 assert(internals.context.lowPrioritySystem.addedEngine instanceof Serpentity);
164 });
165
166 it('should not add duplicate systems', () => {
167
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;
171
172 // Ensure we don't add the same system twice
173 assert(!added);
174 assert.deepEqual(newSystemsLength, originalSystemsLength);
175 });
176
177 it('should call update callback on added systems', () => {
178
179 internals.context.engine.update(internals.delta);
180
181 // Ensure update function called
182 assert(!!internals.context.regularSystem.updateCalled);
183 assert(!!internals.context.highPrioritySystem.updateCalled);
184 assert(!!internals.context.lowPrioritySystem.updateCalled);
185 });
186
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
206 it('should call update callback in the order of priorities', () => {
207
208 internals.context.engine.update(internals.delta);
209
210 // Ensure order of priorities
211 assert(internals.context.regularSystem.updateCalled < internals.context.lowPrioritySystem.updateCalled);
212 assert(internals.context.regularSystem.updateCalled > internals.context.highPrioritySystem.updateCalled);
213 });
214
215 it('should send the delta in the update callback', () => {
216
217 internals.context.engine.update(internals.delta);
218
219 // Ensure delta is being sent
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);
223 });
224
225 it('should no longer call removed systems', () => {
226
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);
233
234 // Check for return value
235 assert(originalRemoved);
236 assert(!finalRemoved);
237
238 // Confirm that only removed if found by checking length of systems
239 // array
240 assert(originalSystemLength > intermediateSystemLength);
241 assert.deepEqual(finalSystemLength, intermediateSystemLength);
242
243 // Ensure callback is sent
244 assert(!internals.context.regularSystem.removedCalled);
245 assert(!internals.context.highPrioritySystem.removedCalled);
246 assert(!!internals.context.lowPrioritySystem.removedCalled);
247
248 // Ensure update is no longer sent
249 assert(!!internals.context.regularSystem.updateCalled);
250 assert(!!internals.context.highPrioritySystem.updateCalled);
251 assert(!internals.context.lowPrioritySystem.updateCalled);
252 });
253
254 it('should only call nodes in selected node collections', () => {
255
256 internals.context.engine.update(internals.delta);
257
258 // Ensure component is called for each entity
259 assert(!!internals.context.firstEntity._components[0].called);
260 assert(!!internals.context.secondEntity._components[0].called);
261
262 // Ensure entity not in node collection not called
263 assert(!!internals.context.firstEntity.called);
264 assert(!!internals.context.secondEntity.called);
265 assert(!internals.context.emptyEntity.called);
266 });
267
268 it('should stop showing removed entities', () => {
269
270 internals.context.engine.removeEntity(internals.context.secondEntity);
271 internals.context.engine.update(internals.delta);
272
273 assert(!!internals.context.firstEntity._components[0].called);
274 assert(!internals.context.secondEntity._components[0].called);
275
276 assert(!!internals.context.firstEntity.called);
277 assert(!internals.context.secondEntity.called);
278 assert(!internals.context.emptyEntity.called);
279 });
280
281 it('should not add duplicate components to entities', () => {
282
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;
286
287 assert(!result);
288 assert.deepEqual(newComponentsLength, originalComponentsLength);
289 });
290
291 it('should allow access to components by class', () => {
292
293 const firstComponent = internals.context.firstEntity.getComponent(internals.component);
294 const emptyComponent = internals.context.emptyEntity.getComponent(internals.component);
295
296 assert(firstComponent instanceof internals.component);
297 assert.deepEqual(emptyComponent, undefined);
298 });
299
300 it('should not add duplicate entities', () => {
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;
305
306 assert(!added);
307
308 assert.deepEqual(finalEntitiesLength, originalEntitiesLength);
309 });
310
311 it('should remove entities', () => {
312
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);
319
320 // Check for return value
321 assert(originalRemoved);
322 assert(!finalRemoved);
323
324 // Confirm that only removed if found by checking length of systems
325 // array
326 assert(originalEntityLength > intermediateEntityLength);
327 assert.deepEqual(finalEntityLength, intermediateEntityLength);
328
329 // Ensure callback is sent
330 assert(!internals.context.firstEntity.called);
331 assert(!!internals.context.secondEntity.called);
332 });
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 });
344});