]>
Commit | Line | Data |
---|---|---|
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'; | |
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 | 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 | |
90 | internals.node.types = { | |
91 | test: internals.component | |
92 | }; | |
93 | ||
94 | describe('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 | ||
122 | describe('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 | }); |