]>
git.r.bdr.sh - rbdr/serpentity/blob - test/integration.js
1 import { describe
, it
, beforeEach
, mock
} from 'node:test';
2 import assert
from 'node:assert';
3 import Serpentity
, { Component
, Entity
, Node
, System
} from '../lib/serpentity.js';
7 system: class TestSystem
extends System
{
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;
17 this.changeObserver
= (event
) => {
18 this.emittedEvents
.push(event
)
21 this.nodeAddedObserver
= ({ node
}) => {
22 node
.test
.addEventListener('change', this.changeObserver
);
25 this.nodeRemovedObserver
= ({ node
}) => {
26 node
.test
.removeEventListener('change', this.changeObserver
);
29 for (const node
of this.testNodes
) {
30 node
.test
.addEventListener('change', this.changeObserver
);
33 this.testNodes
.addEventListener('nodeAdded', this.nodeAddedObserver
);
34 this.testNodes
.addEventListener('nodeRemoved', this.nodeRemovedObserver
);
36 super.added(); // not needed, but takes care of coverage :P
41 this.testNodes
.removeEventListener('nodeAdded', this.nodeAddedObserver
);
42 this.nodeAddedObserver
= null;
44 this.testNodes
.removeEventListener('nodeRemoved', this.nodeRemovedObserver
);
45 this.nodeRemovedObserver
= null;
47 for (const node
of this.testNodes
) {
48 node
.test
.removeEventListener('change', this.changeObserver
);
50 this.changeObserver
= null;
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
63 this.updateCalled
= Date
.now();
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;
73 while (Date
.now() === this.updateCalled
) { /* pass some time */ }
74 super.update(); // not needed, but takes care of coverage :P
77 component: class TestComponent
extends Component
{
85 node: class TestNode
extends Node
{},
89 // adds a component to the node
90 internals
.node
.types
= {
91 test: internals
.component
94 describe('Loading', () => {
96 it('Should export the main class', () => {
98 assert(typeof Serpentity
=== 'function');
101 it('Should export the Entity class', () => {
103 assert(typeof Entity
=== 'function');
106 it('Should export the Component class', () => {
108 assert(typeof Component
=== 'function');
111 it('Should export the System class', () => {
113 assert(typeof System
=== 'function');
116 it('Should export the Node class', () => {
118 assert(typeof Node
=== 'function');
122 describe('Engine', () => {
126 internals
.context
.engine
= new Serpentity();
128 internals
.context
.regularSystem
= new internals
.system();
129 internals
.context
.highPrioritySystem
= new internals
.system();
130 internals
.context
.lowPrioritySystem
= new internals
.system();
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();
138 // Add entity before the systems
139 internals
.context
.engine
.addEntity(internals
.context
.firstEntity
);
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);
145 // Add entity after the systems
146 internals
.context
.engine
.addEntity(internals
.context
.secondEntity
);
147 internals
.context
.engine
.addEntity(internals
.context
.emptyEntity
);
150 it('should call added callback on added systems', () => {
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
);
158 it('should send the engine instance in added callback', () => {
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
);
166 it('should not add duplicate systems', () => {
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
;
172 // Ensure we don't add the same system twice
174 assert
.deepEqual(newSystemsLength
, originalSystemsLength
);
177 it('should call update callback on added systems', () => {
179 internals
.context
.engine
.update(internals
.delta
);
181 // Ensure update function called
182 assert(!!internals
.context
.regularSystem
.updateCalled
);
183 assert(!!internals
.context
.highPrioritySystem
.updateCalled
);
184 assert(!!internals
.context
.lowPrioritySystem
.updateCalled
);
187 it('should keep proxied object behavior as expected', () => {
189 internals
.context
.engine
.update(internals
.delta
);
191 assert(internals
.context
.highPrioritySystem
.beforeCall
=== false);
192 assert(internals
.context
.highPrioritySystem
.afterCall
=== true);
195 it('should emit an event for every changed property', () => {
197 internals
.context
.engine
.update(internals
.delta
);
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);
206 it('should call update callback in the order of priorities', () => {
208 internals
.context
.engine
.update(internals
.delta
);
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
);
215 it('should send the delta in the update callback', () => {
217 internals
.context
.engine
.update(internals
.delta
);
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
);
225 it('should no longer call removed systems', () => {
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
);
234 // Check for return value
235 assert(originalRemoved
);
236 assert(!finalRemoved
);
238 // Confirm that only removed if found by checking length of systems
240 assert(originalSystemLength
> intermediateSystemLength
);
241 assert
.deepEqual(finalSystemLength
, intermediateSystemLength
);
243 // Ensure callback is sent
244 assert(!internals
.context
.regularSystem
.removedCalled
);
245 assert(!internals
.context
.highPrioritySystem
.removedCalled
);
246 assert(!!internals
.context
.lowPrioritySystem
.removedCalled
);
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
);
254 it('should only call nodes in selected node collections', () => {
256 internals
.context
.engine
.update(internals
.delta
);
258 // Ensure component is called for each entity
259 assert(!!internals
.context
.firstEntity
._components
[0].called
);
260 assert(!!internals
.context
.secondEntity
._components
[0].called
);
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
);
268 it('should stop showing removed entities', () => {
270 internals
.context
.engine
.removeEntity(internals
.context
.secondEntity
);
271 internals
.context
.engine
.update(internals
.delta
);
273 assert(!!internals
.context
.firstEntity
._components
[0].called
);
274 assert(!internals
.context
.secondEntity
._components
[0].called
);
276 assert(!!internals
.context
.firstEntity
.called
);
277 assert(!internals
.context
.secondEntity
.called
);
278 assert(!internals
.context
.emptyEntity
.called
);
281 it('should not add duplicate components to entities', () => {
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
;
288 assert
.deepEqual(newComponentsLength
, originalComponentsLength
);
291 it('should allow access to components by class', () => {
293 const firstComponent
= internals
.context
.firstEntity
.getComponent(internals
.component
);
294 const emptyComponent
= internals
.context
.emptyEntity
.getComponent(internals
.component
);
296 assert(firstComponent
instanceof internals
.component
);
297 assert
.deepEqual(emptyComponent
, undefined);
300 it('should not add duplicate entities', () => {
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
;
308 assert
.deepEqual(finalEntitiesLength
, originalEntitiesLength
);
311 it('should remove entities', () => {
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
);
320 // Check for return value
321 assert(originalRemoved
);
322 assert(!finalRemoved
);
324 // Confirm that only removed if found by checking length of systems
326 assert(originalEntityLength
> intermediateEntityLength
);
327 assert
.deepEqual(finalEntityLength
, intermediateEntityLength
);
329 // Ensure callback is sent
330 assert(!internals
.context
.firstEntity
.called
);
331 assert(!!internals
.context
.secondEntity
.called
);
334 it('should not add duplicate entities', () => {
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
;
342 assert
.deepEqual(finalEntitiesLength
, originalEntitiesLength
);