phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
793 lines (629 loc) • 24.2 kB
JavaScript
vi.mock('../../../src/renderer/canvas/utils/GetBlendModes', function ()
{
return function ()
{
var output = [];
for (var i = 0; i < 32; i++) { output[i] = 'source-over'; }
output[1] = 'lighter';
return output;
};
});
vi.mock('../../../src/renderer/snapshot/CanvasSnapshot', function ()
{
return function () {};
});
var CanvasRenderer = require('../../../src/renderer/canvas/CanvasRenderer');
var Events = require('../../../src/renderer/events');
function createMockContext ()
{
return {
globalAlpha: 1,
globalCompositeOperation: 'source-over',
fillStyle: '',
imageSmoothingEnabled: true,
setTransform: vi.fn(),
clearRect: vi.fn(),
fillRect: vi.fn(),
save: vi.fn(),
restore: vi.fn(),
beginPath: vi.fn(),
rect: vi.fn(),
clip: vi.fn(),
drawImage: vi.fn()
};
}
function createMockGame (overrides)
{
var mockContext = createMockContext();
var mockCanvas = {
width: 800,
height: 600,
getContext: function () { return mockContext; }
};
var base = {
canvas: mockCanvas,
config: {
clearBeforeRender: true,
backgroundColor: { rgba: 'rgba(0,0,0,1)' },
antialias: true,
roundPixels: false,
transparent: false,
context: null,
desynchronized: false
},
events: {
once: vi.fn(),
on: vi.fn(),
emit: vi.fn()
},
textures: {
once: vi.fn()
},
scale: {
on: vi.fn(),
baseSize: { width: 800, height: 600 }
},
scene: {
customViewports: false
}
};
if (overrides)
{
Object.assign(base, overrides);
}
return base;
}
function createRenderer (gameOverrides)
{
var game = createMockGame(gameOverrides);
return new CanvasRenderer(game);
}
describe('CanvasRenderer', function ()
{
describe('constructor', function ()
{
it('should set type to CANVAS constant', function ()
{
var renderer = createRenderer();
var CONST = require('../../../src/const');
expect(renderer.type).toBe(CONST.CANVAS);
});
it('should initialise drawCount to zero', function ()
{
var renderer = createRenderer();
expect(renderer.drawCount).toBe(0);
});
it('should initialise width to zero', function ()
{
var renderer = createRenderer();
expect(renderer.width).toBe(0);
});
it('should initialise height to zero', function ()
{
var renderer = createRenderer();
expect(renderer.height).toBe(0);
});
it('should set isBooted to false', function ()
{
var renderer = createRenderer();
expect(renderer.isBooted).toBe(false);
});
it('should store reference to game', function ()
{
var game = createMockGame();
var renderer = new CanvasRenderer(game);
expect(renderer.game).toBe(game);
});
it('should store reference to game canvas', function ()
{
var game = createMockGame();
var renderer = new CanvasRenderer(game);
expect(renderer.gameCanvas).toBe(game.canvas);
});
it('should copy config values from game config', function ()
{
var renderer = createRenderer();
expect(renderer.config.clearBeforeRender).toBe(true);
expect(renderer.config.antialias).toBe(true);
expect(renderer.config.roundPixels).toBe(false);
expect(renderer.config.transparent).toBe(false);
});
it('should set antialias from game config', function ()
{
var renderer = createRenderer();
expect(renderer.antialias).toBe(true);
});
it('should populate blendModes array', function ()
{
var renderer = createRenderer();
expect(Array.isArray(renderer.blendModes)).toBe(true);
expect(renderer.blendModes.length).toBeGreaterThan(0);
});
it('should initialise snapshotState with default values', function ()
{
var renderer = createRenderer();
var state = renderer.snapshotState;
expect(state.x).toBe(0);
expect(state.y).toBe(0);
expect(state.width).toBe(1);
expect(state.height).toBe(1);
expect(state.getPixel).toBe(false);
expect(state.callback).toBeNull();
expect(state.type).toBe('image/png');
expect(state.encoder).toBeCloseTo(0.92);
});
it('should use provided context from config instead of calling getContext', function ()
{
var customCtx = createMockContext();
var game = createMockGame();
game.config.context = customCtx;
var renderer = new CanvasRenderer(game);
expect(renderer.gameContext).toBe(customCtx);
});
it('should set currentContext to gameContext', function ()
{
var game = createMockGame();
var renderer = new CanvasRenderer(game);
expect(renderer.currentContext).toBe(renderer.gameContext);
});
it('should register BOOT event listener on game events', function ()
{
var game = createMockGame();
var renderer = new CanvasRenderer(game);
expect(game.events.once).toHaveBeenCalled();
});
it('should register READY event listener on game textures', function ()
{
var game = createMockGame();
var renderer = new CanvasRenderer(game);
expect(game.textures.once).toHaveBeenCalled();
});
});
describe('onResize', function ()
{
it('should call resize when base size differs from current width', function ()
{
var renderer = createRenderer();
renderer.width = 800;
renderer.height = 600;
renderer.resize = vi.fn();
var gameSize = { width: 800, height: 600 };
var baseSize = { width: 1024, height: 768 };
renderer.onResize(gameSize, baseSize);
expect(renderer.resize).toHaveBeenCalledWith(1024, 768);
});
it('should call resize when base size differs from current height', function ()
{
var renderer = createRenderer();
renderer.width = 800;
renderer.height = 600;
renderer.resize = vi.fn();
var gameSize = { width: 800, height: 600 };
var baseSize = { width: 800, height: 900 };
renderer.onResize(gameSize, baseSize);
expect(renderer.resize).toHaveBeenCalledWith(800, 900);
});
it('should not call resize when base size matches current dimensions', function ()
{
var renderer = createRenderer();
renderer.width = 800;
renderer.height = 600;
renderer.resize = vi.fn();
var gameSize = { width: 800, height: 600 };
var baseSize = { width: 800, height: 600 };
renderer.onResize(gameSize, baseSize);
expect(renderer.resize).not.toHaveBeenCalled();
});
});
describe('resize', function ()
{
it('should update width and height', function ()
{
var renderer = createRenderer();
renderer.resize(1280, 720);
expect(renderer.width).toBe(1280);
expect(renderer.height).toBe(720);
});
it('should emit the RESIZE event with width and height', function ()
{
var renderer = createRenderer();
var spy = vi.fn();
renderer.on(Events.RESIZE, spy);
renderer.resize(640, 480);
expect(spy).toHaveBeenCalledWith(640, 480);
});
it('should accept zero values', function ()
{
var renderer = createRenderer();
renderer.resize(0, 0);
expect(renderer.width).toBe(0);
expect(renderer.height).toBe(0);
});
});
describe('resetTransform', function ()
{
it('should call setTransform with identity matrix on current context', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.currentContext = mockCtx;
renderer.resetTransform();
expect(mockCtx.setTransform).toHaveBeenCalledWith(1, 0, 0, 1, 0, 0);
});
});
describe('setBlendMode', function ()
{
it('should set globalCompositeOperation on current context', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.currentContext = mockCtx;
renderer.setBlendMode('multiply');
expect(mockCtx.globalCompositeOperation).toBe('multiply');
});
it('should return this for chaining', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.currentContext = mockCtx;
var result = renderer.setBlendMode('screen');
expect(result).toBe(renderer);
});
});
describe('setContext', function ()
{
it('should set currentContext to the provided context', function ()
{
var renderer = createRenderer();
var newCtx = createMockContext();
renderer.setContext(newCtx);
expect(renderer.currentContext).toBe(newCtx);
});
it('should reset currentContext to gameContext when called with no argument', function ()
{
var renderer = createRenderer();
var altCtx = createMockContext();
renderer.currentContext = altCtx;
renderer.setContext();
expect(renderer.currentContext).toBe(renderer.gameContext);
});
it('should reset currentContext to gameContext when called with null', function ()
{
var renderer = createRenderer();
var altCtx = createMockContext();
renderer.currentContext = altCtx;
renderer.setContext(null);
expect(renderer.currentContext).toBe(renderer.gameContext);
});
it('should return this for chaining', function ()
{
var renderer = createRenderer();
var result = renderer.setContext(createMockContext());
expect(result).toBe(renderer);
});
});
describe('setAlpha', function ()
{
it('should set globalAlpha on current context', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.currentContext = mockCtx;
renderer.setAlpha(0.5);
expect(mockCtx.globalAlpha).toBe(0.5);
});
it('should set globalAlpha to 0 for fully transparent', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.currentContext = mockCtx;
renderer.setAlpha(0);
expect(mockCtx.globalAlpha).toBe(0);
});
it('should set globalAlpha to 1 for fully opaque', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.currentContext = mockCtx;
renderer.setAlpha(1);
expect(mockCtx.globalAlpha).toBe(1);
});
it('should return this for chaining', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.currentContext = mockCtx;
var result = renderer.setAlpha(0.75);
expect(result).toBe(renderer);
});
});
describe('preRender', function ()
{
it('should reset globalAlpha to 1 on game context', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
mockCtx.globalAlpha = 0.5;
renderer.gameContext = mockCtx;
renderer.preRender();
expect(mockCtx.globalAlpha).toBe(1);
});
it('should reset globalCompositeOperation to source-over', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
mockCtx.globalCompositeOperation = 'multiply';
renderer.gameContext = mockCtx;
renderer.preRender();
expect(mockCtx.globalCompositeOperation).toBe('source-over');
});
it('should call setTransform with identity matrix', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.preRender();
expect(mockCtx.setTransform).toHaveBeenCalledWith(1, 0, 0, 1, 0, 0);
});
it('should call clearRect when clearBeforeRender is true', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.config.clearBeforeRender = true;
renderer.width = 800;
renderer.height = 600;
renderer.preRender();
expect(mockCtx.clearRect).toHaveBeenCalledWith(0, 0, 800, 600);
});
it('should not call clearRect when clearBeforeRender is false', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.config.clearBeforeRender = false;
renderer.preRender();
expect(mockCtx.clearRect).not.toHaveBeenCalled();
});
it('should call fillRect when clearBeforeRender is true and not transparent', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.config.clearBeforeRender = true;
renderer.config.transparent = false;
renderer.config.backgroundColor = { rgba: 'rgba(0,0,0,1)' };
renderer.width = 800;
renderer.height = 600;
renderer.preRender();
expect(mockCtx.fillRect).toHaveBeenCalledWith(0, 0, 800, 600);
});
it('should not call fillRect when transparent', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.config.clearBeforeRender = true;
renderer.config.transparent = true;
renderer.preRender();
expect(mockCtx.fillRect).not.toHaveBeenCalled();
});
it('should call ctx.save', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.preRender();
expect(mockCtx.save).toHaveBeenCalled();
});
it('should reset drawCount to zero', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.drawCount = 99;
renderer.preRender();
expect(renderer.drawCount).toBe(0);
});
it('should emit PRE_RENDER event', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
var spy = vi.fn();
renderer.on(Events.PRE_RENDER, spy);
renderer.preRender();
expect(spy).toHaveBeenCalled();
});
it('should emit PRE_RENDER_CLEAR event before clearing', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.config.clearBeforeRender = true;
var order = [];
renderer.on(Events.PRE_RENDER_CLEAR, function () { order.push('clear'); });
mockCtx.clearRect = vi.fn(function () { order.push('clearRect'); });
renderer.preRender();
expect(order[0]).toBe('clear');
expect(order[1]).toBe('clearRect');
});
});
describe('postRender', function ()
{
it('should call ctx.restore on game context', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
renderer.postRender();
expect(mockCtx.restore).toHaveBeenCalled();
});
it('should emit POST_RENDER event', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
var spy = vi.fn();
renderer.on(Events.POST_RENDER, spy);
renderer.postRender();
expect(spy).toHaveBeenCalled();
});
it('should clear snapshotState callback after snapshot is taken', function ()
{
var renderer = createRenderer();
var mockCtx = createMockContext();
renderer.gameContext = mockCtx;
// Mock CanvasSnapshot by overriding the state callback to a non-null but
// avoid actually calling CanvasSnapshot (browser API). Skip if callback is set.
// Only test that null callback is not called.
renderer.snapshotState.callback = null;
renderer.postRender();
expect(renderer.snapshotState.callback).toBeNull();
});
});
describe('snapshotArea', function ()
{
it('should set callback on snapshotState', function ()
{
var renderer = createRenderer();
var cb = vi.fn();
renderer.snapshotArea(0, 0, 100, 100, cb);
expect(renderer.snapshotState.callback).toBe(cb);
});
it('should set x and y on snapshotState', function ()
{
var renderer = createRenderer();
renderer.snapshotArea(10, 20, 100, 100, vi.fn());
expect(renderer.snapshotState.x).toBe(10);
expect(renderer.snapshotState.y).toBe(20);
});
it('should clamp width to gameCanvas width', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotArea(0, 0, 9999, 100, vi.fn());
expect(renderer.snapshotState.width).toBe(800);
});
it('should clamp height to gameCanvas height', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotArea(0, 0, 100, 9999, vi.fn());
expect(renderer.snapshotState.height).toBe(600);
});
it('should set type and encoder options', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotArea(0, 0, 100, 100, vi.fn(), 'image/jpeg', 0.8);
expect(renderer.snapshotState.type).toBe('image/jpeg');
expect(renderer.snapshotState.encoder).toBeCloseTo(0.8);
});
it('should set getPixel to false', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotState.getPixel = true;
renderer.snapshotArea(0, 0, 100, 100, vi.fn());
expect(renderer.snapshotState.getPixel).toBe(false);
});
it('should return this for chaining', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
var result = renderer.snapshotArea(0, 0, 100, 100, vi.fn());
expect(result).toBe(renderer);
});
});
describe('snapshotPixel', function ()
{
it('should set getPixel to true on snapshotState', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotPixel(5, 10, vi.fn());
expect(renderer.snapshotState.getPixel).toBe(true);
});
it('should set x and y to the pixel coordinates', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotPixel(42, 99, vi.fn());
expect(renderer.snapshotState.x).toBe(42);
expect(renderer.snapshotState.y).toBe(99);
});
it('should set width and height to 1', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotPixel(5, 10, vi.fn());
expect(renderer.snapshotState.width).toBe(1);
expect(renderer.snapshotState.height).toBe(1);
});
it('should set the callback on snapshotState', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
var cb = vi.fn();
renderer.snapshotPixel(0, 0, cb);
expect(renderer.snapshotState.callback).toBe(cb);
});
it('should return this for chaining', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
var result = renderer.snapshotPixel(0, 0, vi.fn());
expect(result).toBe(renderer);
});
});
describe('snapshot', function ()
{
it('should call snapshotArea with full canvas dimensions', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
renderer.snapshotArea = vi.fn().mockReturnValue(renderer);
var cb = vi.fn();
renderer.snapshot(cb, 'image/png', 0.92);
expect(renderer.snapshotArea).toHaveBeenCalledWith(0, 0, 800, 600, cb, 'image/png', 0.92);
});
it('should return this for chaining', function ()
{
var renderer = createRenderer();
renderer.gameCanvas = { width: 800, height: 600 };
var result = renderer.snapshot(vi.fn());
expect(result).toBe(renderer);
});
});
describe('destroy', function ()
{
it('should set game to null', function ()
{
var renderer = createRenderer();
renderer.destroy();
expect(renderer.game).toBeNull();
});
it('should set gameCanvas to null', function ()
{
var renderer = createRenderer();
renderer.destroy();
expect(renderer.gameCanvas).toBeNull();
});
it('should set gameContext to null', function ()
{
var renderer = createRenderer();
renderer.destroy();
expect(renderer.gameContext).toBeNull();
});
it('should remove all event listeners', function ()
{
var renderer = createRenderer();
var spy = vi.fn();
renderer.on(Events.RESIZE, spy);
renderer.destroy();
renderer.emit(Events.RESIZE, 100, 100);
expect(spy).not.toHaveBeenCalled();
});
});
});