]>
Commit | Line | Data |
---|---|---|
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 | }); |