]> git.r.bdr.sh - rbdr/serpentity/blob - test/integration.js
872c54b674696d0970339764a96ba2bae6a85944
[rbdr/serpentity] / test / integration.js
1 import { describe, it, beforeEach } from 'node:test';
2 import assert from 'node:assert';
3 import Serpentity, { Component, Entity, Node, System } from '../lib/serpentity.js';
4
5 const 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 super.added(); // not needed, but takes care of coverage :P
14 }
15
16 removed(engine) {
17
18 this.testNodes = null;
19 this.removedCalled = true;
20 this.removedEngine = engine;
21 super.removed(); // not needed, but takes care of coverage :P
22 }
23
24 update(dt) {
25
26 this.updateCalled = Date.now();
27 this.updateDt = dt;
28
29 for (const node of this.testNodes) {
30 node.test.called = true;
31 node.entity.called = true;
32 }
33
34 while (Date.now() === this.updateCalled) { /* pass some time */ }
35 super.update(); // not needed, but takes care of coverage :P
36 }
37 },
38 component: class TestComponent extends Component {
39 constructor(config) {
40
41 super(config);
42
43 this.called = false;
44 }
45 },
46 node: class TestNode extends Node {},
47 delta: 10
48 };
49
50 // adds a component to the node
51 internals.node.types = {
52 test: internals.component
53 };
54
55 describe('Loading', () => {
56
57 it('Should export the main class', () => {
58
59 assert(typeof Serpentity === 'function');
60 });
61
62 it('Should export the Entity class', () => {
63
64 assert(typeof Entity === 'function');
65 });
66
67 it('Should export the Component class', () => {
68
69 assert(typeof Component === 'function');
70 });
71
72 it('Should export the System class', () => {
73
74 assert(typeof System === 'function');
75 });
76
77 it('Should export the Node class', () => {
78
79 assert(typeof Node === 'function');
80 });
81 });
82
83 describe('Engine', () => {
84
85 beforeEach(() => {
86
87 internals.context.engine = new Serpentity();
88
89 internals.context.regularSystem = new internals.system();
90 internals.context.highPrioritySystem = new internals.system();
91 internals.context.lowPrioritySystem = new internals.system();
92
93 internals.context.firstEntity = new Entity();
94 internals.context.firstEntity.addComponent(new internals.component());
95 internals.context.secondEntity = new Entity();
96 internals.context.secondEntity.addComponent(new internals.component());
97 internals.context.emptyEntity = new Entity();
98
99 // Add entity before the systems
100 internals.context.engine.addEntity(internals.context.firstEntity);
101
102 internals.context.engine.addSystem(internals.context.regularSystem, 100);
103 internals.context.engine.addSystem(internals.context.highPrioritySystem, 0);
104 internals.context.engine.addSystem(internals.context.lowPrioritySystem, 1000);
105
106 // Add entity after the systems
107 internals.context.engine.addEntity(internals.context.secondEntity);
108 internals.context.engine.addEntity(internals.context.emptyEntity);
109 });
110
111 it('should call added callback on added systems', () => {
112
113 // Ensure the added callback is being called
114 assert(internals.context.regularSystem.addedCalled);
115 assert(internals.context.highPrioritySystem.addedCalled);
116 assert(internals.context.lowPrioritySystem.addedCalled);
117 });
118
119 it('should send the engine instance in added callback', () => {
120
121 // Ensure the added callback is sending the engine
122 assert(internals.context.regularSystem.addedEngine instanceof Serpentity);
123 assert(internals.context.highPrioritySystem.addedEngine instanceof Serpentity);
124 assert(internals.context.lowPrioritySystem.addedEngine instanceof Serpentity);
125 });
126
127 it('should not add duplicate systems', () => {
128
129 const originalSystemsLength = internals.context.engine.systems.length;
130 const added = internals.context.engine.addSystem(internals.context.regularSystem, 0);
131 const newSystemsLength = internals.context.engine.systems.length;
132
133 // Ensure we don't add the same system twice
134 assert(!added);
135 assert.deepEqual(newSystemsLength, originalSystemsLength);
136 });
137
138 it('should call update callback on added systems', () => {
139
140 internals.context.engine.update(internals.delta);
141
142 // Ensure update function called
143 assert(!!internals.context.regularSystem.updateCalled);
144 assert(!!internals.context.highPrioritySystem.updateCalled);
145 assert(!!internals.context.lowPrioritySystem.updateCalled);
146 });
147
148 it('should call update callback in the order of priorities', () => {
149
150 internals.context.engine.update(internals.delta);
151
152 // Ensure order of priorities
153 assert(internals.context.regularSystem.updateCalled < internals.context.lowPrioritySystem.updateCalled);
154 assert(internals.context.regularSystem.updateCalled > internals.context.highPrioritySystem.updateCalled);
155 });
156
157 it('should send the delta in the update callback', () => {
158
159 internals.context.engine.update(internals.delta);
160
161 // Ensure delta is being sent
162 assert.deepEqual(internals.context.regularSystem.updateDt, internals.delta);
163 assert.deepEqual(internals.context.highPrioritySystem.updateDt, internals.delta);
164 assert.deepEqual(internals.context.lowPrioritySystem.updateDt, internals.delta);
165 });
166
167 it('should no longer call removed systems', () => {
168
169 const originalSystemLength = internals.context.engine.systems.length;
170 const originalRemoved = internals.context.engine.removeSystem(internals.context.lowPrioritySystem);
171 const intermediateSystemLength = internals.context.engine.systems.length;
172 const finalRemoved = internals.context.engine.removeSystem(internals.context.lowPrioritySystem);
173 const finalSystemLength = internals.context.engine.systems.length;
174 internals.context.engine.update(internals.delta);
175
176 // Check for return value
177 assert(originalRemoved);
178 assert(!finalRemoved);
179
180 // Confirm that only removed if found by checking length of systems
181 // array
182 assert(originalSystemLength > intermediateSystemLength);
183 assert.deepEqual(finalSystemLength, intermediateSystemLength);
184
185 // Ensure callback is sent
186 assert(!internals.context.regularSystem.removedCalled);
187 assert(!internals.context.highPrioritySystem.removedCalled);
188 assert(!!internals.context.lowPrioritySystem.removedCalled);
189
190 // Ensure update is no longer sent
191 assert(!!internals.context.regularSystem.updateCalled);
192 assert(!!internals.context.highPrioritySystem.updateCalled);
193 assert(!internals.context.lowPrioritySystem.updateCalled);
194 });
195
196 it('should only call nodes in selected node collections', () => {
197
198 internals.context.engine.update(internals.delta);
199
200 // Ensure component is called for each entity
201 assert(!!internals.context.firstEntity._components[0].called);
202 assert(!!internals.context.secondEntity._components[0].called);
203
204 // Ensure entity not in node collection not called
205 assert(!!internals.context.firstEntity.called);
206 assert(!!internals.context.secondEntity.called);
207 assert(!internals.context.emptyEntity.called);
208 });
209
210 it('should stop showing removed entities', () => {
211
212 internals.context.engine.removeEntity(internals.context.secondEntity);
213 internals.context.engine.update(internals.delta);
214
215 assert(!!internals.context.firstEntity._components[0].called);
216 assert(!internals.context.secondEntity._components[0].called);
217
218 assert(!!internals.context.firstEntity.called);
219 assert(!internals.context.secondEntity.called);
220 assert(!internals.context.emptyEntity.called);
221 });
222
223 it('should not add duplicate components to entities', () => {
224
225 const originalComponentsLength = internals.context.secondEntity._components.length;
226 const result = internals.context.secondEntity.addComponent(new internals.component());
227 const newComponentsLength = internals.context.secondEntity._components.length;
228
229 assert(!result);
230 assert.deepEqual(newComponentsLength, originalComponentsLength);
231 });
232
233 it('should allow access to components by class', () => {
234
235 const firstComponent = internals.context.firstEntity.getComponent(internals.component);
236 const emptyComponent = internals.context.emptyEntity.getComponent(internals.component);
237
238 assert(firstComponent instanceof internals.component);
239 assert.deepEqual(emptyComponent, undefined);
240 });
241
242 it('should not add duplicate entities', () => {
243
244 const originalEntitiesLength = internals.context.engine.entities.length;
245 const added = internals.context.engine.addEntity(internals.context.firstEntity);
246 const finalEntitiesLength = internals.context.engine.entities.length;
247
248 assert(!added);
249
250 assert.deepEqual(finalEntitiesLength, originalEntitiesLength);
251 });
252
253 it('should remove entities', () => {
254
255 const originalEntityLength = internals.context.engine.entities.length;
256 const originalRemoved = internals.context.engine.removeEntity(internals.context.firstEntity);
257 const intermediateEntityLength = internals.context.engine.entities.length;
258 const finalRemoved = internals.context.engine.removeEntity(internals.context.firstEntity);
259 const finalEntityLength = internals.context.engine.entities.length;
260 internals.context.engine.update(internals.delta);
261
262 // Check for return value
263 assert(originalRemoved);
264 assert(!finalRemoved);
265
266 // Confirm that only removed if found by checking length of systems
267 // array
268 assert(originalEntityLength > intermediateEntityLength);
269 assert.deepEqual(finalEntityLength, intermediateEntityLength);
270
271 // Ensure callback is sent
272 assert(!internals.context.firstEntity.called);
273 assert(!!internals.context.secondEntity.called);
274 });
275 });