UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

639 lines (559 loc) 22.8 kB
var ProgramManager = require('../../../src/renderer/webgl/ProgramManager'); describe('ProgramManager', function () { var manager; var mockRenderer; function createMockRenderer (programKey, programObj) { return { shaderProgramFactory: { getKey: function () { return programKey || 'test-key'; }, getShaderProgram: function () { return programObj || { compiling: false }; } }, createVAO: function () { return { vao: true }; } }; } beforeEach(function () { mockRenderer = createMockRenderer(); manager = new ProgramManager(mockRenderer, [], null); }); describe('constructor', function () { it('should store the renderer reference', function () { expect(manager.renderer).toBe(mockRenderer); }); it('should store the indexBuffer', function () { var buf = { buffer: true }; var m = new ProgramManager(mockRenderer, [], buf); expect(m.indexBuffer).toBe(buf); }); it('should default indexBuffer to null when passed null', function () { expect(manager.indexBuffer).toBeNull(); }); it('should store the attributeBufferLayouts', function () { var layouts = [{ a: 1 }, { b: 2 }]; var m = new ProgramManager(mockRenderer, layouts, null); expect(m.attributeBufferLayouts).toBe(layouts); }); it('should initialize currentProgramKey to null', function () { expect(manager.currentProgramKey).toBeNull(); }); it('should initialize currentConfig with empty base shaders', function () { expect(manager.currentConfig.base.vertexShader).toBe(''); expect(manager.currentConfig.base.fragmentShader).toBe(''); }); it('should initialize currentConfig with empty additions array', function () { expect(Array.isArray(manager.currentConfig.additions)).toBe(true); expect(manager.currentConfig.additions.length).toBe(0); }); it('should initialize currentConfig with empty features array', function () { expect(Array.isArray(manager.currentConfig.features)).toBe(true); expect(manager.currentConfig.features.length).toBe(0); }); it('should initialize programs as an empty object', function () { expect(typeof manager.programs).toBe('object'); expect(Object.keys(manager.programs).length).toBe(0); }); it('should initialize uniforms as an empty object', function () { expect(typeof manager.uniforms).toBe('object'); expect(Object.keys(manager.uniforms).length).toBe(0); }); }); describe('resetCurrentConfig', function () { it('should clear the vertexShader', function () { manager.currentConfig.base.vertexShader = 'void main() {}'; manager.resetCurrentConfig(); expect(manager.currentConfig.base.vertexShader).toBe(''); }); it('should clear the fragmentShader', function () { manager.currentConfig.base.fragmentShader = 'void main() {}'; manager.resetCurrentConfig(); expect(manager.currentConfig.base.fragmentShader).toBe(''); }); it('should clear additions', function () { manager.currentConfig.additions.push({ name: 'add1' }); manager.resetCurrentConfig(); expect(manager.currentConfig.additions.length).toBe(0); }); it('should clear features', function () { manager.currentConfig.features.push('featureA'); manager.resetCurrentConfig(); expect(manager.currentConfig.features.length).toBe(0); }); it('should preserve the same arrays (not replace them)', function () { var additions = manager.currentConfig.additions; var features = manager.currentConfig.features; additions.push({ name: 'x' }); features.push('y'); manager.resetCurrentConfig(); expect(manager.currentConfig.additions).toBe(additions); expect(manager.currentConfig.features).toBe(features); }); }); describe('setUniform', function () { it('should store a uniform value by name', function () { manager.setUniform('uTexture', 0); expect(manager.uniforms['uTexture']).toBe(0); }); it('should overwrite an existing uniform value', function () { manager.setUniform('uAlpha', 0.5); manager.setUniform('uAlpha', 1.0); expect(manager.uniforms['uAlpha']).toBe(1.0); }); it('should store multiple uniforms', function () { manager.setUniform('uA', 1); manager.setUniform('uB', 2); expect(manager.uniforms['uA']).toBe(1); expect(manager.uniforms['uB']).toBe(2); }); it('should store non-numeric values', function () { var arr = [1, 0, 0, 1]; manager.setUniform('uColor', arr); expect(manager.uniforms['uColor']).toBe(arr); }); }); describe('removeUniform', function () { it('should remove an existing uniform', function () { manager.setUniform('uTexture', 0); manager.removeUniform('uTexture'); expect(manager.uniforms['uTexture']).toBeUndefined(); }); it('should not throw when removing a non-existent uniform', function () { expect(function () { manager.removeUniform('nonExistent'); }).not.toThrow(); }); it('should only remove the specified uniform', function () { manager.setUniform('uA', 1); manager.setUniform('uB', 2); manager.removeUniform('uA'); expect(manager.uniforms['uB']).toBe(2); }); }); describe('clearUniforms', function () { it('should remove all uniforms', function () { manager.setUniform('uA', 1); manager.setUniform('uB', 2); manager.clearUniforms(); expect(Object.keys(manager.uniforms).length).toBe(0); }); it('should replace the uniforms object', function () { var original = manager.uniforms; manager.setUniform('uA', 1); manager.clearUniforms(); expect(manager.uniforms).not.toBe(original); }); it('should work when uniforms are already empty', function () { expect(function () { manager.clearUniforms(); }).not.toThrow(); expect(Object.keys(manager.uniforms).length).toBe(0); }); }); describe('applyUniforms', function () { it('should call setUniform on the program for each stored uniform', function () { var calls = []; var mockProgram = { setUniform: function (name, value) { calls.push({ name: name, value: value }); } }; manager.setUniform('uA', 1); manager.setUniform('uB', 2); manager.applyUniforms(mockProgram); expect(calls.length).toBe(2); var names = calls.map(function (c) { return c.name; }); expect(names).toContain('uA'); expect(names).toContain('uB'); }); it('should pass the correct value to setUniform', function () { var calls = []; var mockProgram = { setUniform: function (name, value) { calls.push({ name: name, value: value }); } }; manager.setUniform('uAlpha', 0.75); manager.applyUniforms(mockProgram); expect(calls[0].name).toBe('uAlpha'); expect(calls[0].value).toBe(0.75); }); it('should not call setUniform when there are no uniforms', function () { var callCount = 0; var mockProgram = { setUniform: function () { callCount++; } }; manager.applyUniforms(mockProgram); expect(callCount).toBe(0); }); }); describe('setBaseShader', function () { it('should set the name on the base config', function () { manager.setBaseShader('MyShader', 'vs', 'fs'); expect(manager.currentConfig.base.name).toBe('MyShader'); }); it('should set the vertexShader on the base config', function () { manager.setBaseShader('MyShader', 'void main() { gl_Position = vec4(0); }', 'fs'); expect(manager.currentConfig.base.vertexShader).toBe('void main() { gl_Position = vec4(0); }'); }); it('should set the fragmentShader on the base config', function () { manager.setBaseShader('MyShader', 'vs', 'void main() { gl_FragColor = vec4(1); }'); expect(manager.currentConfig.base.fragmentShader).toBe('void main() { gl_FragColor = vec4(1); }'); }); it('should overwrite a previously set base shader', function () { manager.setBaseShader('First', 'vs1', 'fs1'); manager.setBaseShader('Second', 'vs2', 'fs2'); expect(manager.currentConfig.base.name).toBe('Second'); expect(manager.currentConfig.base.vertexShader).toBe('vs2'); expect(manager.currentConfig.base.fragmentShader).toBe('fs2'); }); }); describe('addAddition', function () { it('should append an addition to the end when no index is given', function () { var add = { name: 'myAddition' }; manager.addAddition(add); expect(manager.currentConfig.additions.length).toBe(1); expect(manager.currentConfig.additions[0]).toBe(add); }); it('should insert at the specified index', function () { var a = { name: 'a' }; var b = { name: 'b' }; var c = { name: 'c' }; manager.addAddition(a); manager.addAddition(c); manager.addAddition(b, 1); expect(manager.currentConfig.additions[0]).toBe(a); expect(manager.currentConfig.additions[1]).toBe(b); expect(manager.currentConfig.additions[2]).toBe(c); }); it('should insert at index 0', function () { var a = { name: 'a' }; var b = { name: 'b' }; manager.addAddition(a); manager.addAddition(b, 0); expect(manager.currentConfig.additions[0]).toBe(b); expect(manager.currentConfig.additions[1]).toBe(a); }); it('should allow multiple additions', function () { manager.addAddition({ name: 'x' }); manager.addAddition({ name: 'y' }); manager.addAddition({ name: 'z' }); expect(manager.currentConfig.additions.length).toBe(3); }); }); describe('getAddition', function () { it('should return the addition with the matching name', function () { var add = { name: 'target' }; manager.addAddition({ name: 'other' }); manager.addAddition(add); expect(manager.getAddition('target')).toBe(add); }); it('should return null when no addition matches', function () { manager.addAddition({ name: 'other' }); expect(manager.getAddition('missing')).toBeNull(); }); it('should return null when additions list is empty', function () { expect(manager.getAddition('anything')).toBeNull(); }); it('should return the first match when duplicates exist', function () { var first = { name: 'dup' }; var second = { name: 'dup' }; manager.addAddition(first); manager.addAddition(second); expect(manager.getAddition('dup')).toBe(first); }); }); describe('getAdditionsByTag', function () { it('should return additions that include the given tag', function () { var a = { name: 'a', tags: ['alpha', 'beta'] }; var b = { name: 'b', tags: ['beta'] }; var c = { name: 'c', tags: ['gamma'] }; manager.addAddition(a); manager.addAddition(b); manager.addAddition(c); var result = manager.getAdditionsByTag('beta'); expect(result.length).toBe(2); expect(result).toContain(a); expect(result).toContain(b); }); it('should return an empty array when no additions match the tag', function () { manager.addAddition({ name: 'a', tags: ['alpha'] }); var result = manager.getAdditionsByTag('nonexistent'); expect(result.length).toBe(0); }); it('should exclude additions with no tags property', function () { manager.addAddition({ name: 'noTags' }); var result = manager.getAdditionsByTag('anything'); expect(result.length).toBe(0); }); it('should return an empty array when additions list is empty', function () { var result = manager.getAdditionsByTag('tag'); expect(result.length).toBe(0); }); }); describe('getAdditionIndex', function () { it('should return the index of the addition with the matching name', function () { manager.addAddition({ name: 'a' }); manager.addAddition({ name: 'b' }); manager.addAddition({ name: 'c' }); expect(manager.getAdditionIndex('b')).toBe(1); }); it('should return 0 for the first addition', function () { manager.addAddition({ name: 'first' }); expect(manager.getAdditionIndex('first')).toBe(0); }); it('should return -1 when the addition is not found', function () { manager.addAddition({ name: 'a' }); expect(manager.getAdditionIndex('missing')).toBe(-1); }); it('should return -1 when the additions list is empty', function () { expect(manager.getAdditionIndex('anything')).toBe(-1); }); }); describe('removeAddition', function () { it('should remove the addition with the given name', function () { manager.addAddition({ name: 'keep' }); manager.addAddition({ name: 'remove' }); manager.removeAddition('remove'); expect(manager.currentConfig.additions.length).toBe(1); expect(manager.currentConfig.additions[0].name).toBe('keep'); }); it('should not affect other additions', function () { var a = { name: 'a' }; var b = { name: 'b' }; var c = { name: 'c' }; manager.addAddition(a); manager.addAddition(b); manager.addAddition(c); manager.removeAddition('b'); expect(manager.currentConfig.additions.length).toBe(2); expect(manager.currentConfig.additions[0]).toBe(a); expect(manager.currentConfig.additions[1]).toBe(c); }); it('should do nothing when name does not exist', function () { manager.addAddition({ name: 'a' }); manager.removeAddition('nonexistent'); expect(manager.currentConfig.additions.length).toBe(1); }); it('should work on an empty additions list', function () { expect(function () { manager.removeAddition('anything'); }).not.toThrow(); }); }); describe('replaceAddition', function () { it('should replace the addition at the correct index', function () { var original = { name: 'target', data: 'old' }; var replacement = { name: 'target', data: 'new' }; manager.addAddition({ name: 'before' }); manager.addAddition(original); manager.addAddition({ name: 'after' }); manager.replaceAddition('target', replacement); expect(manager.currentConfig.additions[1]).toBe(replacement); }); it('should not change the length of the additions array', function () { manager.addAddition({ name: 'a' }); manager.addAddition({ name: 'b' }); manager.replaceAddition('a', { name: 'a', updated: true }); expect(manager.currentConfig.additions.length).toBe(2); }); it('should do nothing when the name is not found', function () { var original = { name: 'existing' }; manager.addAddition(original); manager.replaceAddition('nonexistent', { name: 'new' }); expect(manager.currentConfig.additions[0]).toBe(original); }); it('should not affect surrounding additions', function () { var before = { name: 'before' }; var after = { name: 'after' }; manager.addAddition(before); manager.addAddition({ name: 'target' }); manager.addAddition(after); manager.replaceAddition('target', { name: 'target', updated: true }); expect(manager.currentConfig.additions[0]).toBe(before); expect(manager.currentConfig.additions[2]).toBe(after); }); }); describe('addFeature', function () { it('should add a feature string', function () { manager.addFeature('SHADOW'); expect(manager.currentConfig.features).toContain('SHADOW'); }); it('should not add a duplicate feature', function () { manager.addFeature('SHADOW'); manager.addFeature('SHADOW'); expect(manager.currentConfig.features.length).toBe(1); }); it('should add multiple distinct features', function () { manager.addFeature('SHADOW'); manager.addFeature('BLOOM'); expect(manager.currentConfig.features.length).toBe(2); expect(manager.currentConfig.features).toContain('SHADOW'); expect(manager.currentConfig.features).toContain('BLOOM'); }); }); describe('removeFeature', function () { it('should remove the specified feature', function () { manager.addFeature('SHADOW'); manager.addFeature('BLOOM'); manager.removeFeature('SHADOW'); expect(manager.currentConfig.features).not.toContain('SHADOW'); expect(manager.currentConfig.features).toContain('BLOOM'); }); it('should do nothing when feature is not present', function () { manager.addFeature('BLOOM'); manager.removeFeature('SHADOW'); expect(manager.currentConfig.features.length).toBe(1); }); it('should work on an empty features list', function () { expect(function () { manager.removeFeature('anything'); }).not.toThrow(); }); }); describe('clearFeatures', function () { it('should remove all features', function () { manager.addFeature('SHADOW'); manager.addFeature('BLOOM'); manager.clearFeatures(); expect(manager.currentConfig.features.length).toBe(0); }); it('should preserve the same array reference', function () { var features = manager.currentConfig.features; manager.addFeature('SHADOW'); manager.clearFeatures(); expect(manager.currentConfig.features).toBe(features); }); it('should work when features are already empty', function () { expect(function () { manager.clearFeatures(); }).not.toThrow(); expect(manager.currentConfig.features.length).toBe(0); }); }); describe('getCurrentProgramSuite', function () { it('should return null when the program is still compiling', function () { var mockProgram = { compiling: true, checkParallelCompile: function () {} }; var r = createMockRenderer('key1', mockProgram); var m = new ProgramManager(r, [], null); var result = m.getCurrentProgramSuite(); expect(result).toBeNull(); }); it('should return the program suite when the program is ready', function () { var mockProgram = { compiling: false }; var r = createMockRenderer('key2', mockProgram); var m = new ProgramManager(r, [], null); var result = m.getCurrentProgramSuite(); expect(result).not.toBeNull(); expect(result.program).toBe(mockProgram); }); it('should cache the program suite on subsequent calls', function () { var callCount = 0; var mockProgram = { compiling: false }; var r = { shaderProgramFactory: { getKey: function () { return 'cached-key'; }, getShaderProgram: function () { callCount++; return mockProgram; } }, createVAO: function () { return {}; } }; var m = new ProgramManager(r, [], null); m.getCurrentProgramSuite(); m.getCurrentProgramSuite(); expect(callCount).toBe(1); }); it('should store a deep copy of the config in the suite', function () { var mockProgram = { compiling: false }; var r = createMockRenderer('key3', mockProgram); var m = new ProgramManager(r, [], null); m.setBaseShader('Test', 'vs', 'fs'); var suite = m.getCurrentProgramSuite(); m.setBaseShader('Changed', 'vs2', 'fs2'); expect(suite.config.base.vertexShader).toBe('vs'); }); it('should call checkParallelCompile when program is compiling', function () { var checked = false; var mockProgram = { compiling: true, checkParallelCompile: function () { checked = true; } }; var r = createMockRenderer('key4', mockProgram); var m = new ProgramManager(r, [], null); m.getCurrentProgramSuite(); expect(checked).toBe(true); }); }); });