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