phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
1,355 lines (1,047 loc) • 41.4 kB
JavaScript
var Animation = require('../../src/animations/Animation');
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function createMockTextureManager ()
{
return {
exists: function () { return false; },
get: function () { return null; },
getFrame: function () { return null; }
};
}
function createMockManager ()
{
return {
textureManager: createMockTextureManager(),
on: vi.fn(),
off: vi.fn(),
remove: vi.fn()
};
}
function createAnimation (config)
{
var manager = createMockManager();
return new Animation(manager, 'test-anim', config || {});
}
function createMockFrame (props)
{
var frame = {
isFirst: false,
isLast: false,
nextFrame: null,
prevFrame: null,
progress: 0,
duration: 0,
index: 1,
toJSON: function ()
{
return { key: 'test', frame: 0, duration: 0 };
},
destroy: vi.fn()
};
if (props)
{
for (var key in props)
{
frame[key] = props[key];
}
}
return frame;
}
/**
* Build `count` linked mock frames (isFirst, isLast, prevFrame, nextFrame, progress
* all set correctly so they behave like a real animation frame sequence).
*/
function createLinkedFrames (count)
{
var frames = [];
var i;
for (i = 0; i < count; i++)
{
frames.push(createMockFrame({ index: i + 1 }));
}
for (i = 0; i < frames.length; i++)
{
frames[i].isFirst = (i === 0);
frames[i].isLast = (i === frames.length - 1);
frames[i].progress = (count > 1) ? i / (count - 1) : 0;
frames[i].prevFrame = frames[(i - 1 + frames.length) % frames.length];
frames[i].nextFrame = frames[(i + 1) % frames.length];
}
return frames;
}
/**
* Build a minimal AnimationState mock that references the supplied animation.
*/
function createMockState (anim, frameOverrides)
{
var frames = createLinkedFrames(3);
var currentFrame = frames[0];
if (frameOverrides)
{
for (var k in frameOverrides)
{
currentFrame[k] = frameOverrides[k];
}
}
return {
currentAnim: anim,
currentFrame: currentFrame,
frameRate: anim.frameRate,
msPerFrame: anim.msPerFrame,
accumulator: 0,
nextTick: 0,
yoyo: false,
repeatCounter: 0,
repeatDelay: 0,
pendingRepeat: false,
inReverse: false,
forward: true,
isPlaying: true,
_pendingStop: 0,
_pendingStopValue: 0,
complete: vi.fn(),
stop: vi.fn(),
setCurrentFrame: vi.fn(),
handleRepeat: vi.fn()
};
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('Animation', function ()
{
// -----------------------------------------------------------------------
// Constructor
// -----------------------------------------------------------------------
describe('constructor', function ()
{
it('should set the key from the second argument', function ()
{
var manager = createMockManager();
var anim = new Animation(manager, 'walk', {});
expect(anim.key).toBe('walk');
});
it('should store a reference to the manager', function ()
{
var manager = createMockManager();
var anim = new Animation(manager, 'walk', {});
expect(anim.manager).toBe(manager);
});
it('should default type to "frame"', function ()
{
var anim = createAnimation();
expect(anim.type).toBe('frame');
});
it('should default frameRate to 24 when neither frameRate nor duration is provided', function ()
{
var anim = createAnimation();
expect(anim.frameRate).toBe(24);
});
it('should default paused to false', function ()
{
var anim = createAnimation();
expect(anim.paused).toBe(false);
});
it('should default delay to 0', function ()
{
var anim = createAnimation({ delay: 0 });
expect(anim.delay).toBe(0);
});
it('should accept a custom delay', function ()
{
var anim = createAnimation({ delay: 500 });
expect(anim.delay).toBe(500);
});
it('should default repeat to 0', function ()
{
var anim = createAnimation();
expect(anim.repeat).toBe(0);
});
it('should accept a custom repeat value', function ()
{
var anim = createAnimation({ repeat: -1 });
expect(anim.repeat).toBe(-1);
});
it('should default repeatDelay to 0', function ()
{
var anim = createAnimation();
expect(anim.repeatDelay).toBe(0);
});
it('should default yoyo to false', function ()
{
var anim = createAnimation();
expect(anim.yoyo).toBe(false);
});
it('should accept yoyo: true', function ()
{
var anim = createAnimation({ yoyo: true });
expect(anim.yoyo).toBe(true);
});
it('should default showBeforeDelay to false', function ()
{
var anim = createAnimation();
expect(anim.showBeforeDelay).toBe(false);
});
it('should default showOnStart to false', function ()
{
var anim = createAnimation();
expect(anim.showOnStart).toBe(false);
});
it('should default hideOnComplete to false', function ()
{
var anim = createAnimation();
expect(anim.hideOnComplete).toBe(false);
});
it('should default randomFrame to false', function ()
{
var anim = createAnimation();
expect(anim.randomFrame).toBe(false);
});
it('should default skipMissedFrames to true', function ()
{
var anim = createAnimation();
expect(anim.skipMissedFrames).toBe(true);
});
it('should default frames to an empty array when no frames config is provided', function ()
{
var anim = createAnimation();
expect(Array.isArray(anim.frames)).toBe(true);
expect(anim.frames.length).toBe(0);
});
it('should register PAUSE_ALL and RESUME_ALL listeners on the manager', function ()
{
var manager = createMockManager();
new Animation(manager, 'walk', {});
expect(manager.on).toHaveBeenCalledTimes(2);
});
it('should not throw when manager has no on() method', function ()
{
var manager = createMockManager();
delete manager.on;
expect(function () { new Animation(manager, 'walk', {}); }).not.toThrow();
});
});
// -----------------------------------------------------------------------
// getTotalFrames
// -----------------------------------------------------------------------
describe('getTotalFrames', function ()
{
it('should return 0 when there are no frames', function ()
{
var anim = createAnimation();
expect(anim.getTotalFrames()).toBe(0);
});
it('should return the correct count after frames are added manually', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(5);
expect(anim.getTotalFrames()).toBe(5);
});
});
// -----------------------------------------------------------------------
// calculateDuration
// -----------------------------------------------------------------------
describe('calculateDuration', function ()
{
it('should use 24 fps default when both duration and frameRate are null', function ()
{
var anim = createAnimation();
var target = {};
anim.calculateDuration(target, 12, null, null);
expect(target.frameRate).toBe(24);
});
it('should derive duration from default 24 fps when both are null', function ()
{
var anim = createAnimation();
var target = {};
anim.calculateDuration(target, 12, null, null);
// 24 / 12 * 1000 = 2000
expect(target.duration).toBeCloseTo(2000);
});
it('should set duration and derive frameRate when only duration is provided', function ()
{
var anim = createAnimation();
var target = {};
// 12 frames over 4000 ms → 3 fps
anim.calculateDuration(target, 12, 4000, null);
expect(target.duration).toBe(4000);
expect(target.frameRate).toBeCloseTo(3);
});
it('should set frameRate and derive duration when only frameRate is provided', function ()
{
var anim = createAnimation();
var target = {};
// 15 frames at 30 fps → 500 ms
anim.calculateDuration(target, 15, null, 30);
expect(target.frameRate).toBe(30);
expect(target.duration).toBeCloseTo(500);
});
it('should prefer frameRate over duration when both are provided', function ()
{
var anim = createAnimation();
var target = {};
// frameRate wins → 10 frames at 5 fps = 2000 ms
anim.calculateDuration(target, 10, 9999, 5);
expect(target.frameRate).toBe(5);
expect(target.duration).toBeCloseTo(2000);
});
it('should calculate msPerFrame correctly', function ()
{
var anim = createAnimation();
var target = {};
anim.calculateDuration(target, 10, null, 10);
expect(target.msPerFrame).toBeCloseTo(100);
});
it('should handle a single frame with default fps', function ()
{
var anim = createAnimation();
var target = {};
anim.calculateDuration(target, 1, null, null);
expect(target.frameRate).toBe(24);
// 24 / 1 * 1000 = 24000
expect(target.duration).toBeCloseTo(24000);
});
});
// -----------------------------------------------------------------------
// checkFrame
// -----------------------------------------------------------------------
describe('checkFrame', function ()
{
var anim;
beforeEach(function ()
{
anim = createAnimation();
anim.frames = createLinkedFrames(5);
});
it('should return true for index 0', function ()
{
expect(anim.checkFrame(0)).toBe(true);
});
it('should return true for the last valid index', function ()
{
expect(anim.checkFrame(4)).toBe(true);
});
it('should return false for a negative index', function ()
{
expect(anim.checkFrame(-1)).toBe(false);
});
it('should return false for an index equal to frames.length', function ()
{
expect(anim.checkFrame(5)).toBe(false);
});
it('should return false for an index beyond frames.length', function ()
{
expect(anim.checkFrame(100)).toBe(false);
});
it('should return false when there are no frames', function ()
{
anim.frames = [];
expect(anim.checkFrame(0)).toBe(false);
});
});
// -----------------------------------------------------------------------
// getFirstTick
// -----------------------------------------------------------------------
describe('getFirstTick', function ()
{
it('should set accumulator to 0', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.accumulator = 999;
anim.getFirstTick(state);
expect(state.accumulator).toBe(0);
});
it('should set nextTick to msPerFrame when frameRates match and frame duration is 0', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
// frame duration is 0 (falsy) → fall back to msPerFrame
state.currentFrame.duration = 0;
anim.getFirstTick(state);
expect(state.nextTick).toBeCloseTo(anim.msPerFrame);
});
it('should use frame.duration when it is set and frameRates match', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.duration = 200;
anim.getFirstTick(state);
expect(state.nextTick).toBe(200);
});
it('should use state.msPerFrame when state frameRate differs from animation frameRate', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.frameRate = anim.frameRate + 10; // deliberate mismatch
state.msPerFrame = 50;
anim.getFirstTick(state);
expect(state.nextTick).toBe(50);
});
});
// -----------------------------------------------------------------------
// getFrameAt
// -----------------------------------------------------------------------
describe('getFrameAt', function ()
{
it('should return the frame at the given index', function ()
{
var anim = createAnimation();
var frames = createLinkedFrames(3);
anim.frames = frames;
expect(anim.getFrameAt(0)).toBe(frames[0]);
expect(anim.getFrameAt(1)).toBe(frames[1]);
expect(anim.getFrameAt(2)).toBe(frames[2]);
});
it('should return undefined for an out-of-range index', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(2);
expect(anim.getFrameAt(99)).toBeUndefined();
});
});
// -----------------------------------------------------------------------
// getFrames
// -----------------------------------------------------------------------
describe('getFrames', function ()
{
it('should return an empty array when frames is an empty array', function ()
{
var anim = createAnimation();
var result = anim.getFrames(createMockTextureManager(), []);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(0);
});
it('should return an empty array when frames is not an array', function ()
{
var anim = createAnimation();
var result = anim.getFrames(createMockTextureManager(), null);
expect(result.length).toBe(0);
});
it('should skip frames whose texture is not found', function ()
{
var anim = createAnimation();
var tm = createMockTextureManager();
// getFrame returns null → frame skipped
tm.getFrame = function () { return null; };
var result = anim.getFrames(tm, [{ key: 'missing', frame: 0 }]);
expect(result.length).toBe(0);
});
it('should skip frames with no key and no defaultTextureKey', function ()
{
var anim = createAnimation();
var tm = createMockTextureManager();
var result = anim.getFrames(tm, [{ frame: 0 }], null);
expect(result.length).toBe(0);
});
it('should return an empty array for a string texture key that does not exist', function ()
{
var anim = createAnimation();
var tm = createMockTextureManager();
// exists returns false
var result = anim.getFrames(tm, 'nonexistent-texture');
expect(result.length).toBe(0);
});
});
// -----------------------------------------------------------------------
// getNextTick
// -----------------------------------------------------------------------
describe('getNextTick', function ()
{
it('should subtract nextTick from accumulator', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.accumulator = 100;
state.nextTick = 40;
state.currentFrame.duration = 0;
anim.getNextTick(state);
expect(state.accumulator).toBeCloseTo(60);
});
it('should set nextTick to msPerFrame when frame duration is 0 and frameRates match', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.duration = 0;
state.nextTick = 0;
anim.getNextTick(state);
expect(state.nextTick).toBeCloseTo(anim.msPerFrame);
});
it('should set nextTick to frame.duration when it is non-zero and frameRates match', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.duration = 250;
anim.getNextTick(state);
expect(state.nextTick).toBe(250);
});
});
// -----------------------------------------------------------------------
// getFrameByProgress
// -----------------------------------------------------------------------
describe('getFrameByProgress', function ()
{
var anim;
beforeEach(function ()
{
anim = createAnimation();
anim.frames = createLinkedFrames(3);
// progress values: 0, 0.5, 1
});
it('should return the first frame for progress 0', function ()
{
expect(anim.getFrameByProgress(0)).toBe(anim.frames[0]);
});
it('should return the last frame for progress 1', function ()
{
expect(anim.getFrameByProgress(1)).toBe(anim.frames[2]);
});
it('should clamp values below 0 to the first frame', function ()
{
expect(anim.getFrameByProgress(-5)).toBe(anim.frames[0]);
});
it('should clamp values above 1 to the last frame', function ()
{
expect(anim.getFrameByProgress(99)).toBe(anim.frames[2]);
});
it('should return the closest frame for a mid-range progress value', function ()
{
var result = anim.getFrameByProgress(0.5);
expect(result).toBe(anim.frames[1]);
});
});
// -----------------------------------------------------------------------
// getLastFrame
// -----------------------------------------------------------------------
describe('getLastFrame', function ()
{
it('should return the last frame in the frames array', function ()
{
var anim = createAnimation();
var frames = createLinkedFrames(4);
anim.frames = frames;
expect(anim.getLastFrame()).toBe(frames[3]);
});
it('should return the only frame when there is exactly one', function ()
{
var anim = createAnimation();
var frames = createLinkedFrames(1);
anim.frames = frames;
expect(anim.getLastFrame()).toBe(frames[0]);
});
});
// -----------------------------------------------------------------------
// nextFrame
// -----------------------------------------------------------------------
describe('nextFrame', function ()
{
it('should call state.complete() when on the last frame with no repeat or yoyo', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.isLast = true;
state.yoyo = false;
state.repeatCounter = 0;
anim.nextFrame(state);
expect(state.complete).toHaveBeenCalledTimes(1);
});
it('should advance to nextFrame when not on the last frame', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.isLast = false;
anim.nextFrame(state);
expect(state.setCurrentFrame).toHaveBeenCalledWith(state.currentFrame.nextFrame);
});
it('should call repeatAnimation when on last frame with repeatCounter > 0 and no yoyo', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.isLast = true;
state.yoyo = false;
state.repeatCounter = 3;
state.inReverse = false;
state.forward = true;
var repeatSpy = vi.spyOn(anim, 'repeatAnimation');
anim.nextFrame(state);
expect(repeatSpy).toHaveBeenCalledWith(state);
});
});
// -----------------------------------------------------------------------
// previousFrame
// -----------------------------------------------------------------------
describe('previousFrame', function ()
{
it('should call state.complete() when on the first frame with no repeat or yoyo', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.isFirst = true;
state.yoyo = false;
state.repeatCounter = 0;
anim.previousFrame(state);
expect(state.complete).toHaveBeenCalledTimes(1);
});
it('should step back to prevFrame when not on the first frame', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.isFirst = false;
anim.previousFrame(state);
expect(state.setCurrentFrame).toHaveBeenCalledWith(state.currentFrame.prevFrame);
});
it('should call repeatAnimation when on first frame with repeatCounter > 0', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.currentFrame.isFirst = true;
state.yoyo = false;
state.repeatCounter = 2;
state.inReverse = true;
state.forward = false;
var repeatSpy = vi.spyOn(anim, 'repeatAnimation');
anim.previousFrame(state);
expect(repeatSpy).toHaveBeenCalledWith(state);
});
});
// -----------------------------------------------------------------------
// addFrame
// -----------------------------------------------------------------------
describe('addFrame', function ()
{
it('should return the animation instance (chaining)', function ()
{
var anim = createAnimation();
vi.spyOn(anim, 'getFrames').mockReturnValue([]);
expect(anim.addFrame([])).toBe(anim);
});
it('should append frames to the end of the frames array', function ()
{
var anim = createAnimation();
var existing = createLinkedFrames(2);
anim.frames = existing.slice();
var newFrame = createMockFrame({ index: 3 });
vi.spyOn(anim, 'getFrames').mockReturnValue([ newFrame ]);
anim.addFrame([]);
expect(anim.frames.length).toBe(3);
expect(anim.frames[2]).toBe(newFrame);
});
});
// -----------------------------------------------------------------------
// addFrameAt
// -----------------------------------------------------------------------
describe('addFrameAt', function ()
{
it('should return the animation instance (chaining)', function ()
{
var anim = createAnimation();
vi.spyOn(anim, 'getFrames').mockReturnValue([]);
expect(anim.addFrameAt(0, [])).toBe(anim);
});
it('should prepend frames when index is 0', function ()
{
var anim = createAnimation();
var existing = createLinkedFrames(2);
anim.frames = existing.slice();
var newFrames = [ createMockFrame({ index: 99 }) ];
vi.spyOn(anim, 'getFrames').mockReturnValue(newFrames);
anim.addFrameAt(0, []);
expect(anim.frames[0]).toBe(newFrames[0]);
expect(anim.frames.length).toBe(3);
});
it('should append frames when index equals frames.length', function ()
{
var anim = createAnimation();
var existing = createLinkedFrames(2);
anim.frames = existing.slice();
var newFrames = [ createMockFrame({ index: 99 }) ];
vi.spyOn(anim, 'getFrames').mockReturnValue(newFrames);
anim.addFrameAt(2, []);
expect(anim.frames[2]).toBe(newFrames[0]);
expect(anim.frames.length).toBe(3);
});
it('should insert frames in the middle at the specified index', function ()
{
var anim = createAnimation();
var existing = createLinkedFrames(4);
anim.frames = existing.slice();
var newFrame = createMockFrame({ index: 99 });
vi.spyOn(anim, 'getFrames').mockReturnValue([ newFrame ]);
anim.addFrameAt(2, []);
expect(anim.frames.length).toBe(5);
expect(anim.frames[2]).toBe(newFrame);
expect(anim.frames[0]).toBe(existing[0]);
expect(anim.frames[1]).toBe(existing[1]);
expect(anim.frames[3]).toBe(existing[2]);
});
it('should not modify frames when getFrames returns an empty array', function ()
{
var anim = createAnimation();
var existing = createLinkedFrames(3);
anim.frames = existing.slice();
vi.spyOn(anim, 'getFrames').mockReturnValue([]);
anim.addFrameAt(1, []);
expect(anim.frames.length).toBe(3);
});
});
// -----------------------------------------------------------------------
// removeFrame
// -----------------------------------------------------------------------
describe('removeFrame', function ()
{
it('should return the animation instance (chaining)', function ()
{
var anim = createAnimation();
expect(anim.removeFrame({})).toBe(anim);
});
it('should remove the specified frame object', function ()
{
var anim = createAnimation();
var frames = createLinkedFrames(3);
anim.frames = frames.slice();
anim.removeFrame(frames[1]);
expect(anim.frames.length).toBe(2);
expect(anim.frames.indexOf(frames[1])).toBe(-1);
});
it('should do nothing when the frame is not in the array', function ()
{
var anim = createAnimation();
var frames = createLinkedFrames(3);
anim.frames = frames.slice();
anim.removeFrame(createMockFrame());
expect(anim.frames.length).toBe(3);
});
});
// -----------------------------------------------------------------------
// removeFrameAt
// -----------------------------------------------------------------------
describe('removeFrameAt', function ()
{
it('should return the animation instance (chaining)', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(2);
expect(anim.removeFrameAt(0)).toBe(anim);
});
it('should remove the frame at the given index', function ()
{
var anim = createAnimation();
var frames = createLinkedFrames(3);
anim.frames = frames.slice();
anim.removeFrameAt(1);
expect(anim.frames.length).toBe(2);
expect(anim.frames.indexOf(frames[1])).toBe(-1);
});
it('should update the frame sequence after removal', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.removeFrameAt(0);
// After removal and updateFrameSequence, the new first frame should be isFirst
expect(anim.frames[0].isFirst).toBe(true);
});
});
// -----------------------------------------------------------------------
// repeatAnimation
// -----------------------------------------------------------------------
describe('repeatAnimation', function ()
{
it('should call state.stop() when _pendingStop is 2 and _pendingStopValue is 0', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state._pendingStop = 2;
state._pendingStopValue = 0;
anim.repeatAnimation(state);
expect(state.stop).toHaveBeenCalledTimes(1);
});
it('should decrement _pendingStopValue when _pendingStop is 2 and value > 0', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state._pendingStop = 2;
state._pendingStopValue = 3;
state.repeatDelay = 0;
state.pendingRepeat = false;
state.repeatCounter = 1;
state.forward = true;
anim.repeatAnimation(state);
expect(state._pendingStopValue).toBe(2);
});
it('should set pendingRepeat and adjust nextTick when repeatDelay > 0', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.repeatDelay = 1000;
state.pendingRepeat = false;
state.accumulator = 50;
state.nextTick = 40;
anim.repeatAnimation(state);
expect(state.pendingRepeat).toBe(true);
// accumulator -= nextTick (50-40=10), nextTick += repeatDelay (40+1000=1040)
expect(state.nextTick).toBeCloseTo(1040);
});
it('should decrement repeatCounter and call setCurrentFrame when no delay', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.repeatDelay = 0;
state.repeatCounter = 3;
state.forward = true;
anim.repeatAnimation(state);
expect(state.repeatCounter).toBe(2);
expect(state.setCurrentFrame).toHaveBeenCalled();
});
it('should call handleRepeat when isPlaying is true and no delay', function ()
{
var anim = createAnimation();
var state = createMockState(anim);
state.repeatDelay = 0;
state.repeatCounter = 2;
state.forward = true;
state.isPlaying = true;
anim.repeatAnimation(state);
expect(state.handleRepeat).toHaveBeenCalledTimes(1);
});
});
// -----------------------------------------------------------------------
// toJSON
// -----------------------------------------------------------------------
describe('toJSON', function ()
{
it('should return an object with the animation key', function ()
{
var manager = createMockManager();
var anim = new Animation(manager, 'run', {});
var json = anim.toJSON();
expect(json.key).toBe('run');
});
it('should include all expected top-level properties', function ()
{
var anim = createAnimation();
var json = anim.toJSON();
expect(json).toHaveProperty('key');
expect(json).toHaveProperty('type');
expect(json).toHaveProperty('frames');
expect(json).toHaveProperty('frameRate');
expect(json).toHaveProperty('duration');
expect(json).toHaveProperty('skipMissedFrames');
expect(json).toHaveProperty('delay');
expect(json).toHaveProperty('repeat');
expect(json).toHaveProperty('repeatDelay');
expect(json).toHaveProperty('yoyo');
expect(json).toHaveProperty('showBeforeDelay');
expect(json).toHaveProperty('showOnStart');
expect(json).toHaveProperty('hideOnComplete');
expect(json).toHaveProperty('randomFrame');
});
it('should return frames as an array', function ()
{
var anim = createAnimation();
var json = anim.toJSON();
expect(Array.isArray(json.frames)).toBe(true);
});
it('should call toJSON on each frame', function ()
{
var anim = createAnimation();
var frame1 = createMockFrame();
var frame2 = createMockFrame();
anim.frames = [ frame1, frame2 ];
vi.spyOn(frame1, 'toJSON').mockReturnValue({ key: 'a' });
vi.spyOn(frame2, 'toJSON').mockReturnValue({ key: 'b' });
var json = anim.toJSON();
expect(json.frames.length).toBe(2);
expect(frame1.toJSON).toHaveBeenCalledTimes(1);
expect(frame2.toJSON).toHaveBeenCalledTimes(1);
});
it('should reflect config values in output', function ()
{
var anim = createAnimation({
delay: 200,
repeat: -1,
yoyo: true,
skipMissedFrames: false,
hideOnComplete: true
});
var json = anim.toJSON();
expect(json.delay).toBe(200);
expect(json.repeat).toBe(-1);
expect(json.yoyo).toBe(true);
expect(json.skipMissedFrames).toBe(false);
expect(json.hideOnComplete).toBe(true);
});
});
// -----------------------------------------------------------------------
// updateFrameSequence
// -----------------------------------------------------------------------
describe('updateFrameSequence', function ()
{
it('should return the animation instance (chaining)', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
expect(anim.updateFrameSequence()).toBe(anim);
});
it('should mark the first frame as isFirst', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[0].isFirst).toBe(true);
});
it('should mark the last frame as isLast', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[2].isLast).toBe(true);
});
it('should set progress to 0 for the first frame', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[0].progress).toBe(0);
});
it('should set progress to 1 for the last frame', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[2].progress).toBeCloseTo(1);
});
it('should set progress to 0.5 for the middle frame of three', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[1].progress).toBeCloseTo(0.5);
});
it('should reassign index values starting at 1', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[0].index).toBe(1);
expect(anim.frames[1].index).toBe(2);
expect(anim.frames[2].index).toBe(3);
});
it('should link last frame nextFrame to first frame', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[2].nextFrame).toBe(anim.frames[0]);
});
it('should link first frame prevFrame to last frame', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.updateFrameSequence();
expect(anim.frames[0].prevFrame).toBe(anim.frames[2]);
});
it('should set isFirst and isLast to true for a single-frame sequence', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(1);
anim.updateFrameSequence();
expect(anim.frames[0].isFirst).toBe(true);
expect(anim.frames[0].isLast).toBe(true);
});
it('should make a single frame point to itself for nextFrame and prevFrame', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(1);
anim.updateFrameSequence();
expect(anim.frames[0].nextFrame).toBe(anim.frames[0]);
expect(anim.frames[0].prevFrame).toBe(anim.frames[0]);
});
});
// -----------------------------------------------------------------------
// pause
// -----------------------------------------------------------------------
describe('pause', function ()
{
it('should set paused to true', function ()
{
var anim = createAnimation();
anim.pause();
expect(anim.paused).toBe(true);
});
it('should return the animation instance (chaining)', function ()
{
var anim = createAnimation();
expect(anim.pause()).toBe(anim);
});
});
// -----------------------------------------------------------------------
// resume
// -----------------------------------------------------------------------
describe('resume', function ()
{
it('should set paused to false', function ()
{
var anim = createAnimation();
anim.paused = true;
anim.resume();
expect(anim.paused).toBe(false);
});
it('should return the animation instance (chaining)', function ()
{
var anim = createAnimation();
expect(anim.resume()).toBe(anim);
});
});
// -----------------------------------------------------------------------
// destroy
// -----------------------------------------------------------------------
describe('destroy', function ()
{
it('should set manager to null', function ()
{
var anim = createAnimation();
anim.destroy();
expect(anim.manager).toBeNull();
});
it('should empty the frames array', function ()
{
var anim = createAnimation();
anim.frames = createLinkedFrames(3);
anim.destroy();
expect(anim.frames.length).toBe(0);
});
it('should call destroy() on each frame', function ()
{
var anim = createAnimation();
var frames = createLinkedFrames(3);
anim.frames = frames.slice();
anim.destroy();
expect(frames[0].destroy).toHaveBeenCalledTimes(1);
expect(frames[1].destroy).toHaveBeenCalledTimes(1);
expect(frames[2].destroy).toHaveBeenCalledTimes(1);
});
it('should call manager.remove() with the animation key', function ()
{
var manager = createMockManager();
var anim = new Animation(manager, 'walk', {});
anim.destroy();
expect(manager.remove).toHaveBeenCalledWith('walk');
});
it('should call manager.off() to remove event listeners', function ()
{
var manager = createMockManager();
var anim = new Animation(manager, 'walk', {});
anim.destroy();
expect(manager.off).toHaveBeenCalledTimes(2);
});
it('should not throw when manager has no off() method', function ()
{
var manager = createMockManager();
delete manager.off;
var anim = new Animation(manager, 'walk', {});
expect(function () { anim.destroy(); }).not.toThrow();
});
});
});