phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
562 lines (444 loc) • 17 kB
JavaScript
var Clock = require('../../src/time/Clock');
var TimerEvent = require('../../src/time/TimerEvent');
function createMockScene ()
{
var eventEmitter = {
once: vi.fn(),
on: vi.fn(),
off: vi.fn()
};
return {
sys: {
events: eventEmitter,
game: {
loop: {
time: 0
}
}
}
};
}
describe('Clock', function ()
{
var clock;
var mockScene;
beforeEach(function ()
{
mockScene = createMockScene();
clock = new Clock(mockScene);
});
describe('constructor', function ()
{
it('should store the scene reference', function ()
{
expect(clock.scene).toBe(mockScene);
});
it('should store the scene systems reference', function ()
{
expect(clock.systems).toBe(mockScene.sys);
});
it('should initialise now to zero', function ()
{
expect(clock.now).toBe(0);
});
it('should initialise startTime to zero', function ()
{
expect(clock.startTime).toBe(0);
});
it('should initialise timeScale to one', function ()
{
expect(clock.timeScale).toBe(1);
});
it('should initialise paused to false', function ()
{
expect(clock.paused).toBe(false);
});
it('should register BOOT listener on scene events', function ()
{
expect(mockScene.sys.events.once).toHaveBeenCalledWith(
expect.any(String),
clock.boot,
clock
);
});
it('should register START listener on scene events', function ()
{
expect(mockScene.sys.events.on).toHaveBeenCalledWith(
expect.any(String),
clock.start,
clock
);
});
});
describe('addEvent', function ()
{
it('should return a TimerEvent when given a config object', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
expect(event).toBeInstanceOf(TimerEvent);
});
it('should add the event to pending insertion', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
expect(clock._pendingInsertion).toContain(event);
});
it('should accept a TimerEvent instance and return it', function ()
{
var event = new TimerEvent({ delay: 500, callback: vi.fn() });
var returned = clock.addEvent(event);
expect(returned).toBe(event);
});
it('should reset hasDispatched when given a TimerEvent instance', function ()
{
var event = new TimerEvent({ delay: 500, callback: vi.fn() });
event.hasDispatched = true;
clock.addEvent(event);
expect(event.hasDispatched).toBe(false);
});
it('should reset elapsed to startAt when given a TimerEvent instance', function ()
{
var event = new TimerEvent({ delay: 500, callback: vi.fn(), startAt: 100 });
event.elapsed = 999;
clock.addEvent(event);
expect(event.elapsed).toBe(100);
});
it('should throw when given a TimerEvent with zero delay and infinite repeat', function ()
{
// Create a valid event first, then mutate to the invalid state
// (TimerEvent also validates in its constructor, so we set properties afterward)
var event = new TimerEvent({ delay: 100, callback: vi.fn() });
event.delay = 0;
event.loop = true;
expect(function ()
{
clock.addEvent(event);
}).toThrow('TimerEvent infinite loop created via zero delay');
});
it('should not add duplicate pending insertions for a re-added TimerEvent', function ()
{
var event = new TimerEvent({ delay: 500, callback: vi.fn() });
clock.addEvent(event);
clock.addEvent(event);
// removeEvent is called in addEvent for instances, so it should only appear once
var count = clock._pendingInsertion.filter(function (e) { return e === event; }).length;
expect(count).toBe(1);
});
});
describe('delayedCall', function ()
{
it('should return a TimerEvent', function ()
{
var event = clock.delayedCall(1000, vi.fn());
expect(event).toBeInstanceOf(TimerEvent);
});
it('should create an event with the given delay', function ()
{
var event = clock.delayedCall(500, vi.fn());
expect(event.delay).toBe(500);
});
it('should create an event with the given callback', function ()
{
var cb = vi.fn();
var event = clock.delayedCall(500, cb);
expect(event.callback).toBe(cb);
});
it('should create an event with the given args', function ()
{
var args = [1, 2, 3];
var event = clock.delayedCall(500, vi.fn(), args);
expect(event.args).toBe(args);
});
it('should create an event with the given callbackScope', function ()
{
var scope = { name: 'test' };
var event = clock.delayedCall(500, vi.fn(), [], scope);
expect(event.callbackScope).toBe(scope);
});
it('should add the event to pending insertion', function ()
{
var event = clock.delayedCall(1000, vi.fn());
expect(clock._pendingInsertion).toContain(event);
});
});
describe('clearPendingEvents', function ()
{
it('should return the Clock instance', function ()
{
expect(clock.clearPendingEvents()).toBe(clock);
});
it('should empty the pending insertion list', function ()
{
clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.addEvent({ delay: 2000, callback: vi.fn() });
clock.clearPendingEvents();
expect(clock._pendingInsertion.length).toBe(0);
});
it('should work when the pending list is already empty', function ()
{
expect(function ()
{
clock.clearPendingEvents();
}).not.toThrow();
expect(clock._pendingInsertion.length).toBe(0);
});
});
describe('removeEvent', function ()
{
it('should return the Clock instance', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
expect(clock.removeEvent(event)).toBe(clock);
});
it('should remove a pending event from pending insertion', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.removeEvent(event);
expect(clock._pendingInsertion).not.toContain(event);
});
it('should remove an active event from the active list', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.preUpdate();
clock.removeEvent(event);
expect(clock._active).not.toContain(event);
});
it('should remove an event from pending removal', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.preUpdate();
clock._pendingRemoval.push(event);
clock.removeEvent(event);
expect(clock._pendingRemoval).not.toContain(event);
});
it('should accept an array of events and remove all of them', function ()
{
var event1 = clock.addEvent({ delay: 1000, callback: vi.fn() });
var event2 = clock.addEvent({ delay: 2000, callback: vi.fn() });
clock.removeEvent([event1, event2]);
expect(clock._pendingInsertion).not.toContain(event1);
expect(clock._pendingInsertion).not.toContain(event2);
});
it('should handle removing an event not in any list without throwing', function ()
{
var event = new TimerEvent({ delay: 1000, callback: vi.fn() });
expect(function ()
{
clock.removeEvent(event);
}).not.toThrow();
});
});
describe('removeAllEvents', function ()
{
it('should return the Clock instance', function ()
{
expect(clock.removeAllEvents()).toBe(clock);
});
it('should move all active events into the pending removal list', function ()
{
var event1 = clock.addEvent({ delay: 1000, callback: vi.fn() });
var event2 = clock.addEvent({ delay: 2000, callback: vi.fn() });
clock.preUpdate();
clock.removeAllEvents();
expect(clock._pendingRemoval).toContain(event1);
expect(clock._pendingRemoval).toContain(event2);
});
it('should not clear the active list immediately', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.preUpdate();
clock.removeAllEvents();
// Active is still populated until next preUpdate
expect(clock._active).toContain(event);
});
it('should remove active events from active list after next preUpdate', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.preUpdate();
clock.removeAllEvents();
clock.preUpdate();
expect(clock._active).not.toContain(event);
});
it('should work when there are no active events', function ()
{
expect(function ()
{
clock.removeAllEvents();
}).not.toThrow();
});
});
describe('preUpdate', function ()
{
it('should move pending insertion events into the active list', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.preUpdate();
expect(clock._active).toContain(event);
expect(clock._pendingInsertion.length).toBe(0);
});
it('should remove events in pending removal from the active list', function ()
{
var event = clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.preUpdate();
clock._pendingRemoval.push(event);
clock.preUpdate();
expect(clock._active).not.toContain(event);
expect(clock._pendingRemoval.length).toBe(0);
});
it('should clear both pending lists after processing', function ()
{
clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.addEvent({ delay: 2000, callback: vi.fn() });
clock.preUpdate();
clock.preUpdate();
expect(clock._pendingInsertion.length).toBe(0);
});
it('should do nothing when both pending lists are empty', function ()
{
expect(function ()
{
clock.preUpdate();
}).not.toThrow();
});
it('should handle multiple insertions in one preUpdate', function ()
{
clock.addEvent({ delay: 1000, callback: vi.fn() });
clock.addEvent({ delay: 2000, callback: vi.fn() });
clock.addEvent({ delay: 3000, callback: vi.fn() });
clock.preUpdate();
expect(clock._active.length).toBe(3);
});
});
describe('update', function ()
{
it('should update the now property to the given time', function ()
{
clock.update(5000, 16);
expect(clock.now).toBe(5000);
});
it('should update now even when paused', function ()
{
clock.paused = true;
clock.update(9999, 16);
expect(clock.now).toBe(9999);
});
it('should not advance events when the clock is paused', function ()
{
var cb = vi.fn();
clock.addEvent({ delay: 100, callback: cb });
clock.preUpdate();
clock.paused = true;
clock.update(1000, 200);
expect(cb).not.toHaveBeenCalled();
});
it('should fire the callback when elapsed reaches the delay', function ()
{
var cb = vi.fn();
clock.addEvent({ delay: 100, callback: cb });
clock.preUpdate();
clock.update(200, 100);
expect(cb).toHaveBeenCalledTimes(1);
});
it('should not fire the callback before the delay has elapsed', function ()
{
var cb = vi.fn();
clock.addEvent({ delay: 100, callback: cb });
clock.preUpdate();
clock.update(50, 50);
expect(cb).not.toHaveBeenCalled();
});
it('should pass args to the callback', function ()
{
var cb = vi.fn();
var args = ['a', 'b'];
clock.addEvent({ delay: 100, callback: cb, args: args });
clock.preUpdate();
clock.update(200, 100);
expect(cb).toHaveBeenCalledWith('a', 'b');
});
it('should call the callback with the correct scope', function ()
{
var scope = { value: 42 };
var capturedThis;
var cb = function ()
{
capturedThis = this;
};
clock.addEvent({ delay: 100, callback: cb, callbackScope: scope });
clock.preUpdate();
clock.update(200, 100);
expect(capturedThis).toBe(scope);
});
it('should apply timeScale to delta', function ()
{
var cb = vi.fn();
clock.timeScale = 2;
clock.addEvent({ delay: 100, callback: cb });
clock.preUpdate();
// delta 25 * timeScale 2 = 50, not enough
clock.update(25, 25);
expect(cb).not.toHaveBeenCalled();
// delta 25 * timeScale 2 = 50, total 100 — fires now
clock.update(50, 25);
expect(cb).toHaveBeenCalledTimes(1);
});
it('should apply event timeScale to elapsed', function ()
{
var cb = vi.fn();
clock.addEvent({ delay: 100, callback: cb, timeScale: 2 });
clock.preUpdate();
// delta 25 * event.timeScale 2 = 50
clock.update(25, 25);
expect(cb).not.toHaveBeenCalled();
// delta 25 * event.timeScale 2 = 50, total 100 — fires
clock.update(50, 25);
expect(cb).toHaveBeenCalledTimes(1);
});
it('should not fire the callback again for a non-repeating event', function ()
{
var cb = vi.fn();
clock.addEvent({ delay: 100, callback: cb });
clock.preUpdate();
clock.update(200, 100);
clock.update(400, 200);
expect(cb).toHaveBeenCalledTimes(1);
});
it('should schedule a non-repeating event for removal after it fires', function ()
{
var cb = vi.fn();
var event = clock.addEvent({ delay: 100, callback: cb });
clock.preUpdate();
clock.update(200, 100);
expect(clock._pendingRemoval).toContain(event);
});
it('should fire a repeating event multiple times', function ()
{
var cb = vi.fn();
clock.addEvent({ delay: 100, callback: cb, repeat: 2 });
clock.preUpdate();
clock.update(100, 100);
clock.update(200, 100);
clock.update(300, 100);
expect(cb.mock.calls.length).toBeGreaterThanOrEqual(2);
});
it('should skip paused events', function ()
{
var cb = vi.fn();
var event = clock.addEvent({ delay: 100, callback: cb });
clock.preUpdate();
event.paused = true;
clock.update(200, 200);
expect(cb).not.toHaveBeenCalled();
});
it('should process multiple active events in one update', function ()
{
var cb1 = vi.fn();
var cb2 = vi.fn();
clock.addEvent({ delay: 100, callback: cb1 });
clock.addEvent({ delay: 100, callback: cb2 });
clock.preUpdate();
clock.update(200, 100);
expect(cb1).toHaveBeenCalledTimes(1);
expect(cb2).toHaveBeenCalledTimes(1);
});
});
});