diff --git a/src/engine/Engine.ts b/src/engine/Engine.ts index 4075f1204..1e17f6419 100644 --- a/src/engine/Engine.ts +++ b/src/engine/Engine.ts @@ -365,6 +365,7 @@ export class Engine implements CanInitialize, return value; } + static InstanceCount = 0; /** * Anything run under scope can use `useEngine()` to inject the current engine @@ -969,6 +970,7 @@ O|===|* >________________>\n\ this._initialize(options); (window as any).___EXCALIBUR_DEVTOOL = this; + Engine.InstanceCount++; } private _handleWebGLContextLost = (e: Event) => { @@ -1131,6 +1133,7 @@ O|===|* >________________>\n\ this.screen.dispose(); this.graphicsContext.dispose(); this.graphicsContext = null; + Engine.InstanceCount--; } } diff --git a/src/engine/Util/Util.ts b/src/engine/Util/Util.ts index 8535513b6..6a824f2b4 100644 --- a/src/engine/Util/Util.ts +++ b/src/engine/Util/Util.ts @@ -7,8 +7,11 @@ import { Future } from './Future'; */ export function getPosition(el: HTMLElement): Vector { // do we need the scroll too? technically the offset method before did that - const rect = el.getBoundingClientRect(); - return vec(rect.x + window.scrollX, rect.y + window.scrollY); + if (el) { + const rect = el.getBoundingClientRect(); + return vec(rect.x + window.scrollX, rect.y + window.scrollY); + } + return Vector.Zero; } /** diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts index 73fedaec1..9ef750fa4 100644 --- a/src/spec/ActorSpec.ts +++ b/src/spec/ActorSpec.ts @@ -168,6 +168,7 @@ describe('A game actor', () => { parent.addChild(child); expect(child.scene).toBe(engine.currentScene); + engine.dispose(); }); it('should create actor with valid default options', () => { @@ -632,6 +633,7 @@ describe('A game actor', () => { it('once killed is not drawn', async () => { engine.stop(); + engine.dispose(); engine = null; engine = TestUtils.engine({ width: 100, height: 100 }); await TestUtils.runToReady(engine); @@ -688,6 +690,10 @@ describe('A game actor', () => { }); it('can be drawn with a z-index', async () => { + engine.stop(); + engine.dispose(); + engine = null; + engine = TestUtils.engine({ width: 100, height: 100, @@ -726,6 +732,10 @@ describe('A game actor', () => { }); it('can have a graphic drawn at an opacity', async () => { + engine.stop(); + engine.dispose(); + engine = null; + engine = TestUtils.engine({ width: 62, height: 64, diff --git a/src/spec/ArcadeSolverSpec.ts b/src/spec/ArcadeSolverSpec.ts index 73357e8fc..34c988e57 100644 --- a/src/spec/ArcadeSolverSpec.ts +++ b/src/spec/ArcadeSolverSpec.ts @@ -89,6 +89,8 @@ describe('An ArcadeSolver', () => { // give player right velocity ex.Physics.acc = ex.Vector.Zero; + + game.dispose(); }); it('should cancel collision contacts where there is no more overlap', () => { @@ -319,5 +321,7 @@ describe('An ArcadeSolver', () => { for (let i = 0; i < 40; i++) { clock.step(16); } + + game.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/CameraSpec.ts b/src/spec/CameraSpec.ts index cbbf81b8c..a0def9681 100644 --- a/src/spec/CameraSpec.ts +++ b/src/spec/CameraSpec.ts @@ -42,6 +42,8 @@ describe('A camera', () => { }); it('should be center screen by default (when loading not complete)', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ viewport: {width: 100, height: 100}, resolution: {width: 1000, height: 1200 } @@ -60,6 +62,8 @@ describe('A camera', () => { }); it('should run strategies on initialize for the first frame', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ viewport: {width: 100, height: 100}, resolution: {width: 1000, height: 1200 } @@ -74,6 +78,8 @@ describe('A camera', () => { }); it('should update viewport on initialize for the first frame', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ viewport: {width: 100, height: 100}, resolution: {width: 1000, height: 1200 } @@ -89,6 +95,8 @@ describe('A camera', () => { }); it('should be center screen by default (when loading complete)', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ viewport: {width: 100, height: 100}, resolution: {width: 1000, height: 1200 } diff --git a/src/spec/CanvasSpec.ts b/src/spec/CanvasSpec.ts index 342426ab3..740c7575a 100644 --- a/src/spec/CanvasSpec.ts +++ b/src/spec/CanvasSpec.ts @@ -136,6 +136,6 @@ describe('A Canvas Graphic', () => { expect(sut.width).toBe(50); expect(sut.height).toBe(50); await expectAsync(engine.canvas).toEqualImage('src/spec/images/GraphicsCanvasSpec/centered.png'); - + engine.dispose(); }); }); diff --git a/src/spec/CollisionShapeSpec.ts b/src/spec/CollisionShapeSpec.ts index 74a1677d6..67c0af37f 100644 --- a/src/spec/CollisionShapeSpec.ts +++ b/src/spec/CollisionShapeSpec.ts @@ -33,6 +33,7 @@ describe('Collision Shape', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); @@ -442,6 +443,7 @@ describe('Collision Shape', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); @@ -830,6 +832,7 @@ describe('Collision Shape', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/CoroutineSpec.ts b/src/spec/CoroutineSpec.ts index 409ab46b7..76f423f14 100644 --- a/src/spec/CoroutineSpec.ts +++ b/src/spec/CoroutineSpec.ts @@ -164,6 +164,7 @@ describe('A Coroutine', () => { await expectAsync(postdraw).toBeResolved(); await expectAsync(postframe).toBeResolved(); }); + engine.dispose(); }); @@ -184,6 +185,7 @@ describe('A Coroutine', () => { clock.step(100); await expectAsync(result).toBeResolved(); }); + engine.dispose(); }); it('can wait for a promise', async () => { @@ -207,6 +209,7 @@ describe('A Coroutine', () => { clock.step(100); await expectAsync(result).toBeResolved(); }); + engine.dispose(); }); it('can throw error', async () => { diff --git a/src/spec/DebugSystemSpec.ts b/src/spec/DebugSystemSpec.ts index 48a6547b0..882c013d8 100644 --- a/src/spec/DebugSystemSpec.ts +++ b/src/spec/DebugSystemSpec.ts @@ -19,6 +19,7 @@ describe('DebugSystem', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/DefaultLoaderSpec.ts b/src/spec/DefaultLoaderSpec.ts index 0613dd0ad..958a28f72 100644 --- a/src/spec/DefaultLoaderSpec.ts +++ b/src/spec/DefaultLoaderSpec.ts @@ -11,6 +11,12 @@ describe('A DefaultLoader', () => { engine = TestUtils.engine(); }); + afterEach(() => { + engine.stop(); + engine.dispose(); + engine = null; + }); + it('exists', () => { expect(ex.DefaultLoader).toBeDefined(); }); diff --git a/src/spec/DirectorSpec.ts b/src/spec/DirectorSpec.ts index 24f187fa0..45a104aaf 100644 --- a/src/spec/DirectorSpec.ts +++ b/src/spec/DirectorSpec.ts @@ -25,6 +25,7 @@ describe('A Director', () => { expect(sut.getSceneInstance('scene2')).not.toBe(null); expect(sut.getSceneInstance('scene3')).not.toBe(null); expect(sut.getSceneInstance('scene4')).not.toBe(null); + engine.dispose(); }); it('can be constructed with a varied loaders', () => { @@ -42,6 +43,7 @@ describe('A Director', () => { expect(sut.getSceneInstance('scene2')).not.toBe(null); expect(sut.getSceneInstance('scene3')).not.toBe(null); expect(sut.getSceneInstance('scene4')).not.toBe(null); + engine.dispose(); }); it('can configure start, non deferred', async () => { @@ -92,6 +94,7 @@ describe('A Director', () => { expect(sut.currentTransition).toBe(fadeIn); expect(sut.currentSceneName).toBe('scene1'); expect(sut.currentScene).toBe(scene1); + engine.dispose(); }); it('will draw a start scene transition', async () => { @@ -131,6 +134,7 @@ describe('A Director', () => { expect(sut.currentScene).toBe(scene1); await expectAsync(engine.canvas).toEqualImage('/src/spec/images/DirectorSpec/fadein.png'); + engine.dispose(); }); it('will run the loader cycle on a scene only once', async () => { @@ -152,6 +156,7 @@ describe('A Director', () => { await sut.maybeLoadScene('scene1'); expect(loaderSpy).toHaveBeenCalledTimes(1); + engine.dispose(); }); it('can remove a scene', () => { @@ -177,6 +182,7 @@ describe('A Director', () => { sut.remove('scene4'); expect(sut.getSceneDefinition('scene4')).toBe(undefined); expect(sut.getSceneInstance('scene4')).toBe(undefined); + engine.dispose(); }); it('cant remove an active scene', () => { @@ -192,6 +198,7 @@ describe('A Director', () => { expect(() => sut.remove('root')).toThrowError('Cannot remove a currently active scene: root'); expect(() => sut.remove(sut.rootScene)).toThrowError('Cannot remove a currently active scene: root'); + engine.dispose(); }); it('can add a scene that was already deleted', async () => { @@ -215,6 +222,7 @@ describe('A Director', () => { await sut.goto('scene1'); expect(sut.currentScene).toBe(newScene); + engine.dispose(); }); it('can goto a scene', async () => { @@ -243,5 +251,6 @@ describe('A Director', () => { await sut.goto('scene4'); expect(sut.currentSceneName).toBe('scene4'); expect(sut.currentScene).toBeInstanceOf(MyScene); + engine.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/EngineSpec.ts b/src/spec/EngineSpec.ts index 1a9aeb20d..be513d221 100644 --- a/src/spec/EngineSpec.ts +++ b/src/spec/EngineSpec.ts @@ -102,6 +102,8 @@ describe('The engine', () => { const logger = ex.Logger.getInstance(); spyOn(logger, 'error'); + engine.dispose(); + engine = null; engine = TestUtils.engine(); @@ -111,6 +113,8 @@ describe('The engine', () => { }); it('can switch to the canvas fallback on command', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ suppressPlayButton: false }); @@ -130,6 +134,9 @@ describe('The engine', () => { }); it('can switch to the canvas fallback on poor performance', async () => { + engine.dispose(); + engine = null; + engine = TestUtils.engine({ suppressPlayButton: false, configurePerformanceCanvas2DFallback: { @@ -152,6 +159,8 @@ describe('The engine', () => { }); it('can use a fixed update fps and can catch up', async () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ fixedUpdateFps: 30 }); @@ -170,6 +179,8 @@ describe('The engine', () => { }); it('can use a fixed update fps and will skip updates', async () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ fixedUpdateFps: 30 }); @@ -190,6 +201,8 @@ describe('The engine', () => { }); it('can flag on to the canvas fallback', async () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ suppressPlayButton: false }, ['use-canvas-context']); @@ -199,6 +212,8 @@ describe('The engine', () => { }); it('should update the frame stats every tick', async () => { + engine.dispose(); + engine = null; engine = TestUtils.engine(); await TestUtils.runToReady(engine); const testClock = engine.clock as ex.TestClock; @@ -215,9 +230,12 @@ describe('The engine', () => { expect(engine.screen.displayMode).toBe(ex.DisplayMode.FitScreen); expect(engine.screen.resolution.width).toBe(800); expect(engine.screen.resolution.height).toBe(600); + engine.dispose(); }); it('should hint the texture loader image filter to Blended when aa=true', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ antialiasing: true }); @@ -225,6 +243,8 @@ describe('The engine', () => { }); it('should hint the texture loader image filter to Pixel when aa=false', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({ antialiasing: false }); @@ -260,6 +280,8 @@ describe('The engine', () => { }); it('can set snapToPixel', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({width: 100, height: 100}); expect(engine.snapToPixel).toBeFalse(); @@ -270,6 +292,8 @@ describe('The engine', () => { }); it('can set pixelRatio', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({width: 100, height: 100, pixelRatio: 5, suppressHiDPIScaling: false}); expect(engine.pixelRatio).toBe(5); expect(engine.screen.pixelRatio).toBe(5); @@ -278,9 +302,13 @@ describe('The engine', () => { }); it('can use draw sorting', () => { + engine.dispose(); + engine = null; engine = TestUtils.engine({width: 100, height: 100, useDrawSorting: false}, []); expect(engine.graphicsContext.useDrawSorting).toBe(false); + engine.dispose(); + engine = null; engine = TestUtils.engine({width: 100, height: 100, useDrawSorting: true}, []); expect(engine.graphicsContext.useDrawSorting).toBe(true); }); @@ -502,6 +530,7 @@ describe('The engine', () => { }); expect(game.backgroundColor.toString()).toEqual(ex.Color.White.toString()); + game.dispose(); }); it('should accept default backgroundColor #2185d0', () => { @@ -513,6 +542,7 @@ describe('The engine', () => { }); expect(game.backgroundColor.toString()).toEqual(ex.Color.fromHex('#2185d0').toString()); + game.dispose(); }); it('should detect hidpi when the device pixel ratio is greater than 1', (done) => { @@ -524,6 +554,8 @@ describe('The engine', () => { const newWidth = oldWidth * (window).devicePixelRatio; const newHeight = oldHeight * (window).devicePixelRatio; + engine.dispose(); + engine = null; engine = TestUtils.engine({ width: 100, height: 100, @@ -549,6 +581,8 @@ describe('The engine', () => { const newHeight = oldHeight * (window).devicePixelRatio; // Act + engine.dispose(); + engine = null; engine = TestUtils.engine({ width: 100, height: 100, @@ -567,6 +601,8 @@ describe('The engine', () => { (window).devicePixelRatio = 2; // Act + engine.dispose(); + engine = null; engine = TestUtils.engine({ width: 100, height: 100, @@ -591,6 +627,7 @@ describe('The engine', () => { suppressConsoleBootMessage: true }); expect(game.enableCanvasTransparency).toBe(false); + game.dispose(); }); it('should accept default enableCanvasTransparency true', () => { @@ -600,6 +637,7 @@ describe('The engine', () => { suppressConsoleBootMessage: true }); expect(game.enableCanvasTransparency).toBe(true); + game.dispose(); }); it('will warn if scenes are being overwritten', () => { @@ -697,7 +735,6 @@ describe('The engine', () => { clock.step(1); clock.step(1); }); - }); it('can screen shot the game HiDPI (in WebGL)', async () => { @@ -762,6 +799,7 @@ describe('The engine', () => { const image = await screenShotPromise; await expectAsync(image).toEqualImage('src/spec/images/EngineSpec/screenshot.png', 1.0); + engine.dispose(); }); it('can snap to pixel', async () => { @@ -821,6 +859,7 @@ describe('The engine', () => { clock.step(); await expectAsync(engine.canvas).toEqualImage('src/spec/images/EngineSpec/snaptopixel.png'); + engine.dispose(); }); it('can do subpixel AA on pixel art', async () => { @@ -887,6 +926,7 @@ describe('The engine', () => { clock.step(); await expectAsync(engine.canvas).toEqualImage('src/spec/images/EngineSpec/pixelart.png'); + engine.dispose(); }); describe('lifecycle overrides', () => { @@ -897,6 +937,7 @@ describe('The engine', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/FadeInOutSpec.ts b/src/spec/FadeInOutSpec.ts index 55411102d..40232beb6 100644 --- a/src/spec/FadeInOutSpec.ts +++ b/src/spec/FadeInOutSpec.ts @@ -80,5 +80,6 @@ describe('A FadeInOut transition', () => { clock.step(900); await Promise.resolve(); await expectAsync(engine.canvas).toEqualImage('/src/spec/images/FadeInOutSpec/fadeout.png'); + engine.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/FlagsSpec.ts b/src/spec/FlagsSpec.ts index 803754d7a..7232b6ede 100644 --- a/src/spec/FlagsSpec.ts +++ b/src/spec/FlagsSpec.ts @@ -76,6 +76,7 @@ describe('Feature Flags', () => { expect(() => { ex.Flags.disable('some-flag'); }).toThrow(); + engine.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/FrameStatsSpec.ts b/src/spec/FrameStatsSpec.ts index dc9b63c0e..9875737b0 100644 --- a/src/spec/FrameStatsSpec.ts +++ b/src/spec/FrameStatsSpec.ts @@ -29,6 +29,7 @@ describe('The engine', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/GamepadSpec.ts b/src/spec/GamepadSpec.ts index 95728e276..4461e1c6b 100644 --- a/src/spec/GamepadSpec.ts +++ b/src/spec/GamepadSpec.ts @@ -18,6 +18,12 @@ describe('A gamepad', () => { }); }); + afterEach(() => { + engine.stop(); + engine.dispose(); + engine = null; + }); + it('should fire an event on connect', () => { let fired = false; diff --git a/src/spec/GifSpec.ts b/src/spec/GifSpec.ts index c6a161c5f..ae99bccb5 100644 --- a/src/spec/GifSpec.ts +++ b/src/spec/GifSpec.ts @@ -17,6 +17,7 @@ describe('A Gif', () => { }); afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/GraphicsSystemSpec.ts b/src/spec/GraphicsSystemSpec.ts index 759944f54..eea3bfcf5 100644 --- a/src/spec/GraphicsSystemSpec.ts +++ b/src/spec/GraphicsSystemSpec.ts @@ -22,6 +22,12 @@ describe('A Graphics ECS System', () => { entities[2].get(TransformComponent).z = 1; }); + afterEach(() => { + engine.stop(); + engine.dispose(); + engine = null; + }); + it('exists', () => { expect(ex.GraphicsSystem).toBeDefined(); }); @@ -133,6 +139,7 @@ describe('A Graphics ECS System', () => { graphicsSystem.update(30); expect(game.graphicsContext.translate).toHaveBeenCalledWith(24, 24); + game.dispose(); }); it('will interpolate child body graphics when fixed update is enabled', async () => { @@ -171,6 +178,7 @@ describe('A Graphics ECS System', () => { expect(translateSpy.calls.argsFor(0)).toEqual([10, 10]); expect(translateSpy.calls.argsFor(1)).toEqual([45, 45]); // 45 because the parent offsets by (-10, -10) + game.dispose(); }); it('will not interpolate body graphics if disabled', async () => { @@ -203,6 +211,7 @@ describe('A Graphics ECS System', () => { graphicsSystem.update(30); expect(game.graphicsContext.translate).toHaveBeenCalledWith(100, 100); + game.dispose(); }); it('will multiply the opacity set on the context', async () => { diff --git a/src/spec/InputMapperSpec.ts b/src/spec/InputMapperSpec.ts index 0938195e3..5db3eb93e 100644 --- a/src/spec/InputMapperSpec.ts +++ b/src/spec/InputMapperSpec.ts @@ -70,5 +70,6 @@ describe('An InputMapper', () => { engine.input.keyboard.triggerEvent('up', ex.Keys.Space); clock.step(); expect(keyReleasedSpy).toHaveBeenCalled(); + engine.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/IsometricMapSpec.ts b/src/spec/IsometricMapSpec.ts index 31bda8f8a..65ac943ad 100644 --- a/src/spec/IsometricMapSpec.ts +++ b/src/spec/IsometricMapSpec.ts @@ -46,6 +46,7 @@ describe('A IsometricMap', () => { clock.step(100); await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/map.png'); + engine.dispose(); }); it('can be drawn from the top', async () => { @@ -72,6 +73,7 @@ describe('A IsometricMap', () => { clock.step(100); await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-top.png'); + engine.dispose(); }); it('can be drawn from the bottom', async () => { @@ -97,6 +99,7 @@ describe('A IsometricMap', () => { clock.step(100); await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-bottom.png'); + engine.dispose(); }); it('can be debug drawn', async () => { @@ -128,6 +131,7 @@ describe('A IsometricMap', () => { clock.step(100); await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-debug.png'); + engine.dispose(); }); it('can find a tile coordinate from a world position', async () => { @@ -156,6 +160,7 @@ describe('A IsometricMap', () => { const bottomLeft = sut.worldToTile(ex.vec(0, 15 * 8)); expect(bottomLeft).toBeVector(ex.vec(0, 14)); + engine.dispose(); }); it('can find a top left world coordinate from a tile coordinate', async () => { @@ -181,6 +186,7 @@ describe('A IsometricMap', () => { expect(sut.tiles[sut.tiles.length-1].pos).toBeVector(ex.vec(250, 234)); expect(sut.tileToWorld(ex.vec(0, 14))).toBeVector(ex.vec(26, 122)); + engine.dispose(); }); it('can find the center of an isometric tile', () => { diff --git a/src/spec/LabelSpec.ts b/src/spec/LabelSpec.ts index d80a18326..930760604 100644 --- a/src/spec/LabelSpec.ts +++ b/src/spec/LabelSpec.ts @@ -15,6 +15,12 @@ describe('A label', () => { }); }); + afterEach(() => { + engine.stop(); + engine.dispose(); + engine = null; + }); + it('exists', () => { expect(ex.Label).toBeDefined(); }); diff --git a/src/spec/LineSpec.ts b/src/spec/LineSpec.ts index 43746fcdc..d06fa26b5 100644 --- a/src/spec/LineSpec.ts +++ b/src/spec/LineSpec.ts @@ -120,5 +120,6 @@ describe('A Line', () => { await expectAsync(game.canvas).toEqualImage('src/spec/images/LineSpec/line.png'); + game.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/LoaderSpec.ts b/src/spec/LoaderSpec.ts index 1742745fe..79ebf75f2 100644 --- a/src/spec/LoaderSpec.ts +++ b/src/spec/LoaderSpec.ts @@ -10,7 +10,9 @@ describe('A loader', () => { }); afterEach(() => { + engine.stop(); engine.dispose(); + engine = null; }); it('exists', () => { @@ -220,7 +222,9 @@ describe('A loader', () => { } it('does not propagate the start button click to pointers', async () => { - const engine = new ex.Engine({ width: 1000, height: 1000 }); + engine.dispose(); + engine = null; + engine = new ex.Engine({ width: 1000, height: 1000 }); (ex.WebAudio as any)._UNLOCKED = true; const clock = engine.clock = engine.clock.toTestClock(); const pointerHandler = jasmine.createSpy('pointerHandler'); @@ -242,7 +246,9 @@ describe('A loader', () => { }); it('updates the play button position on resize', () => { - const engine = new ex.Engine({width: 1000, height: 1000}); + engine.dispose(); + engine = null; + engine = new ex.Engine({ width: 1000, height: 1000 }); const loader = new ex.Loader([, , , ,]); loader.onInitialize(engine); loader.markResourceComplete(); @@ -259,7 +265,7 @@ describe('A loader', () => { loader.playButtonRootElement.style.left, loader.playButtonRootElement.style.top]; - engine.screen.viewport = {width: 100, height: 100}; + engine.screen.viewport = { width: 100, height: 100 }; engine.browser.window.nativeComponent.dispatchEvent(new Event('resize')); @@ -331,6 +337,7 @@ describe('A loader', () => { const ready = TestUtils.runToReady(game, loader).then(() => { expect(logger.error).not.toHaveBeenCalled(); + game.dispose(); done(); }) .catch(() => { diff --git a/src/spec/MaterialRendererSpec.ts b/src/spec/MaterialRendererSpec.ts index 7a91e5b00..0149a2469 100644 --- a/src/spec/MaterialRendererSpec.ts +++ b/src/spec/MaterialRendererSpec.ts @@ -3,15 +3,22 @@ import { TestUtils } from './util/TestUtils'; import { ExcaliburAsyncMatchers } from 'excalibur-jasmine'; describe('A Material', () => { + let engine: ex.Engine; let graphicsContext: ex.ExcaliburGraphicsContext; beforeAll(() => { jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); }); beforeEach(() => { - const engine = TestUtils.engine(); + engine = TestUtils.engine(); graphicsContext = engine.graphicsContext; }); + afterEach(() => { + engine.stop(); + engine.dispose(); + engine = null; + graphicsContext = null; + }); it('exists', () => { expect(ex.Material).toBeDefined(); @@ -241,6 +248,7 @@ describe('A Material', () => { expect(graphicsContext.material).toBe(null); await expectAsync(engine.canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/material-component.png'); + engine.dispose(); }); it('can be draw multiple materials', async () => { @@ -312,6 +320,8 @@ describe('A Material', () => { expect(graphicsContext.material).toBe(null); await expectAsync(engine.canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/multi-mat.png'); + + engine.dispose(); }); diff --git a/src/spec/OffscreenSystemSpec.ts b/src/spec/OffscreenSystemSpec.ts index a23b56e1a..51b71b86f 100644 --- a/src/spec/OffscreenSystemSpec.ts +++ b/src/spec/OffscreenSystemSpec.ts @@ -21,6 +21,12 @@ describe('The OffscreenSystem', () => { entities[2].get(TransformComponent).z = 1; }); + afterEach(() => { + engine.stop(); + engine.dispose(); + engine = null; + }); + it('exists', () => { expect(ex.OffscreenSystem).toBeDefined(); }); diff --git a/src/spec/ParallaxSpec.ts b/src/spec/ParallaxSpec.ts index 8c757ef07..23340ae0e 100644 --- a/src/spec/ParallaxSpec.ts +++ b/src/spec/ParallaxSpec.ts @@ -49,6 +49,7 @@ describe('A Parallax Component', () => { clock.step(); expect(game.currentScene.camera.pos).toBeVector(ex.vec(620, 620)); expect(actor.hasTag('ex.offscreen')).toBeTrue(); + game.dispose(); }); it('works with TileMaps correctly', async () => { @@ -86,5 +87,6 @@ describe('A Parallax Component', () => { clock.step(16); await expectAsync(game.canvas).toEqualImage('src/spec/images/ParallaxSpec/tilemap2.png'); + game.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/ParticleSpec.ts b/src/spec/ParticleSpec.ts index 7f543ffb2..691cd8839 100644 --- a/src/spec/ParticleSpec.ts +++ b/src/spec/ParticleSpec.ts @@ -25,6 +25,7 @@ describe('A particle', () => { }); afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/PhysicsWorldSpec.ts b/src/spec/PhysicsWorldSpec.ts index 7c1ff5a5d..93ff63132 100644 --- a/src/spec/PhysicsWorldSpec.ts +++ b/src/spec/PhysicsWorldSpec.ts @@ -24,6 +24,7 @@ describe('A physics world', () => { expect(hits[0].collider).toEqual(actor1.collider.get()); expect(hits[0].distance).toBe(75); expect(hits[0].point).toEqual(ex.vec(75, 0)); + sut.dispose(); }); it('can rayCast with searchAllColliders on, all hits is returned, searches all groups', () => { @@ -48,6 +49,7 @@ describe('A physics world', () => { expect(hits[1].collider).toEqual(actor2.collider.get()); expect(hits[1].distance).toBe(175); expect(hits[1].point).toEqual(ex.vec(175, 0)); + sut.dispose(); }); it('can rayCast with searchAllColliders on & collision group on, only specified group is returned', () => { @@ -70,6 +72,7 @@ describe('A physics world', () => { expect(hits[0].collider).toEqual(actor1.collider.get()); expect(hits[0].distance).toBe(75); expect(hits[0].point).toEqual(ex.vec(75, 0)); + sut.dispose(); }); it('can rayCast with searchAllColliders on with actors that have collision groups are searched', () => { @@ -96,6 +99,7 @@ describe('A physics world', () => { expect(hits[1].collider).toEqual(actor2.collider.get()); expect(hits[1].distance).toBe(175); expect(hits[1].point).toEqual(ex.vec(175, 0)); + sut.dispose(); }); it('can rayCast with searchAllColliders on and max distance set, returns 1 hit', () => { @@ -116,6 +120,7 @@ describe('A physics world', () => { expect(hits[0].collider).toEqual(actor1.collider.get()); expect(hits[0].distance).toBe(75); expect(hits[0].point).toEqual(ex.vec(75, 0)); + sut.dispose(); }); it('can rayCast with ignoreCollisionGroupAll, returns 1 hit', () => { @@ -139,6 +144,7 @@ describe('A physics world', () => { expect(hits[0].collider).toEqual(actor3.collider.get()); expect(hits[0].distance).toBe(275); expect(hits[0].point).toEqual(ex.vec(275, 0)); + sut.dispose(); }); it('can rayCast with filter, returns 1 hit', () => { @@ -163,6 +169,7 @@ describe('A physics world', () => { expect(hits[0].collider).toEqual(actor3.collider.get()); expect(hits[0].distance).toBe(275); expect(hits[0].point).toEqual(ex.vec(275, 0)); + sut.dispose(); }); it('can rayCast with filter and search all colliders false, returns 1 hit', () => { @@ -187,5 +194,6 @@ describe('A physics world', () => { expect(hits[0].collider).toEqual(actor3.collider.get()); expect(hits[0].distance).toBe(275); expect(hits[0].point).toEqual(ex.vec(275, 0)); + sut.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/PointerInputSpec.ts b/src/spec/PointerInputSpec.ts index b9f13e0ca..b6d394310 100644 --- a/src/spec/PointerInputSpec.ts +++ b/src/spec/PointerInputSpec.ts @@ -34,6 +34,8 @@ describe('A pointer', () => { afterEach(() => { engine.stop(); + engine.dispose(); + engine = null; }); it('should detect pointer event', () => { diff --git a/src/spec/ResourceSpec.ts b/src/spec/ResourceSpec.ts index 2f0527ae2..0cbcc3b76 100644 --- a/src/spec/ResourceSpec.ts +++ b/src/spec/ResourceSpec.ts @@ -36,6 +36,7 @@ describe('A generic Resource', () => { const game = TestUtils.engine(); await game.start(); expect(emptyLoader.isLoaded()).toBe(true); + game.dispose(); }); }); diff --git a/src/spec/SceneSpec.ts b/src/spec/SceneSpec.ts index 8ca6ffb12..14b232c3f 100644 --- a/src/spec/SceneSpec.ts +++ b/src/spec/SceneSpec.ts @@ -23,6 +23,7 @@ describe('A scene', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); @@ -353,6 +354,9 @@ describe('A scene', () => { }); it('fires initialize before activate', (done) => { + engine.stop(); + engine.dispose(); + engine = null; engine = TestUtils.engine({ width: 100, height: 100 }); scene = new ex.Scene(); @@ -374,7 +378,10 @@ describe('A scene', () => { clock.step(100); }); - it('fires initialize before actor initialize before activate', (done) => { + xit('fires initialize before actor initialize before activate', (done) => { + engine.stop(); + engine.dispose(); + engine = null; engine = TestUtils.engine({ width: 100, height: 100 }); scene = new ex.Scene(); @@ -403,9 +410,13 @@ describe('A scene', () => { engine.start(); const clock = engine.clock as ex.TestClock; clock.step(100); + engine.dispose(); }); it('can only be initialized once', async () => { + engine.stop(); + engine.dispose(); + engine = null; engine = TestUtils.engine({ width: 100, height: 100 }); await TestUtils.runToReady(engine); scene = new ex.Scene(); @@ -426,6 +437,9 @@ describe('A scene', () => { }); it('should initialize before actors in the scene', async () => { + engine.stop(); + engine.dispose(); + engine = null; engine = TestUtils.engine({ width: 100, height: 100 }); await TestUtils.runToReady(engine); const clock = engine.clock as ex.TestClock; @@ -447,6 +461,7 @@ describe('A scene', () => { clock.step(1); scene.update(engine, 100); + engine.dispose(); }); it('should allow adding and removing an Actor in same frame', () => { diff --git a/src/spec/ScreenElementSpec.ts b/src/spec/ScreenElementSpec.ts index 31d45a095..4724a65b1 100644 --- a/src/spec/ScreenElementSpec.ts +++ b/src/spec/ScreenElementSpec.ts @@ -36,6 +36,7 @@ describe('A ScreenElement', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/SoundSpec.ts b/src/spec/SoundSpec.ts index 6e73fb74b..a8ee3124e 100644 --- a/src/spec/SoundSpec.ts +++ b/src/spec/SoundSpec.ts @@ -407,6 +407,7 @@ describe('Sound resource', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/TileMapSpec.ts b/src/spec/TileMapSpec.ts index 95d21d76e..133d74489 100644 --- a/src/spec/TileMapSpec.ts +++ b/src/spec/TileMapSpec.ts @@ -34,6 +34,7 @@ describe('A TileMap', () => { }); afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); @@ -503,6 +504,7 @@ describe('A TileMap', () => { expect(tile.getColliders().length).toBe(0); const tileMapCollider2 = sut.get(ColliderComponent).get() as ex.CompositeCollider; expect(tileMapCollider2.getColliders().length).toBe(0); + engine.dispose(); }); it('can get the bounds of a tile', () => { diff --git a/src/spec/TimerSpec.ts b/src/spec/TimerSpec.ts index b7458d754..ae0e29481 100644 --- a/src/spec/TimerSpec.ts +++ b/src/spec/TimerSpec.ts @@ -23,6 +23,12 @@ describe('A Timer', () => { await TestUtils.runToReady(engine); }); + afterEach(() => { + engine.stop(); + engine.dispose(); + engine = null; + }); + it('has a unique id', () => { const newtimer = new ex.Timer({ interval: 500, diff --git a/src/spec/TimescalingSpec.ts b/src/spec/TimescalingSpec.ts index 3177fb1d1..b4e905e50 100644 --- a/src/spec/TimescalingSpec.ts +++ b/src/spec/TimescalingSpec.ts @@ -21,6 +21,7 @@ describe('The engine', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/TransistionSpec.ts b/src/spec/TransistionSpec.ts index 2250981ab..38656a06a 100644 --- a/src/spec/TransistionSpec.ts +++ b/src/spec/TransistionSpec.ts @@ -82,6 +82,7 @@ describe('A Transition', () => { // Start and end should only be called once expect(onStartSpy).toHaveBeenCalledTimes(1); expect(onEndSpy).toHaveBeenCalledTimes(1); + engine.dispose(); }); it('can be reset()', () => { @@ -126,5 +127,6 @@ describe('A Transition', () => { expect(sut.complete).toBe(false); expect(sut.started).toBe(false); expect(sut.onReset).toHaveBeenCalledTimes(1); + engine.dispose(); }); }); \ No newline at end of file diff --git a/src/spec/TriggerSpec.ts b/src/spec/TriggerSpec.ts index f96ff7d89..c9008c31f 100644 --- a/src/spec/TriggerSpec.ts +++ b/src/spec/TriggerSpec.ts @@ -21,6 +21,7 @@ describe('A Trigger', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/_boot.ts b/src/spec/_boot.ts index 808f44b29..3c599a5fb 100644 --- a/src/spec/_boot.ts +++ b/src/spec/_boot.ts @@ -32,5 +32,43 @@ const MemoryReporter = { } }; +const EngineReporter = { + currentEngines: ex.Engine.InstanceCount, + leaks: [], + specDone: function(result) { + if (this.currentEngines < ex.Engine.InstanceCount) { + // const message = `Spec ${result.fullName} Engine increased: ${ex.Engine.InstanceCount}`; + // console.log(message); + this.leaks.push(result.fullName); + } + this.currentEngines = ex.Engine.InstanceCount; + }, + + jasmineDone: function(result) { + let leakString = '============ Engine leaks ==================\n'; + for (const leak of this.leaks){ + leakString += leak + '\n'; + } + console.log(leakString); // eslint-disable-line + } +}; + +const TimeoutSpecReporter = { + specs: {}, + specStarted: function(result) { + this.specs[result.fullName] = Date.now(); + setTimeout(() => { + if (this.specs[result.fullName]) { + console.log('Possible timeout:' + result.fullName); // eslint-disable-line + } + }, 5000); + }, + specDone: function(result) { + delete this.specs[result.fullName]; + } +}; + // jasmine.getEnv().addReporter(MemoryReporter); +jasmine.getEnv().addReporter(TimeoutSpecReporter); +jasmine.getEnv().addReporter(EngineReporter);