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