// Replaces pixi.js with stand-ins for what the texture methods touch, so loading and decoding can be // observed without a network or a GPU (the real runtime does load under jest, see test/contract). jest.mock('pixi.js', () => { class ImageSource { scaleMode = 'linear'; constructor(public options: any) {} } class Texture { source: any; destroy = jest.fn(); constructor(options: { source: any }) { this.source = options.source; } } class Stub { scale = { x: 1, y: 1 }; } return { ImageSource, Texture, Assets: { load: jest.fn() }, AnimatedSprite: Stub, Container: Stub, Graphics: Stub, Rectangle: Stub, Sprite: Stub, TilingSprite: Stub, Text: Stub, }; }); import { Assets } from 'pixi.js'; import { PixiFactory } from '../src/pixi-factory'; import { PixiCameraComponent } from '../src/components/pixi-camera.component'; describe('PixiFactory textures from fetched data', () => { const originalCreateImageBitmap = (global as any).createImageBitmap; beforeEach(() => { (global as any).createImageBitmap = jest.fn(async (blob: Blob) => ({ bitmapOf: blob })); }); afterEach(() => { (global as any).createImageBitmap = originalCreateImageBitmap; }); it('textureFromData decodes the blob itself, outside the global Assets cache, and applies options', async () => { const blob = new Blob(['x']); const texture: any = await new PixiFactory().textureFromData(blob, { filter: 'nearest' }); expect((global as any).createImageBitmap).toHaveBeenCalledWith(blob); expect(texture.source.options.resource).toEqual({ bitmapOf: blob }); expect(texture.source.scaleMode).toBe('nearest'); expect(Assets.load).not.toHaveBeenCalled(); }); it('textureFromData decodes an SVG through an image element, which createImageBitmap may refuse', async () => { const decode = jest.fn(async () => {}); const OriginalImage = (global as any).Image; const images: any[] = []; (global as any).Image = class { src = ''; decode = decode; constructor() { images.push(this); } }; const originalCreate = URL.createObjectURL; const originalRevoke = URL.revokeObjectURL; URL.createObjectURL = jest.fn(() => 'blob:svg'); URL.revokeObjectURL = jest.fn(); try { const blob = new Blob([''], { type: 'image/svg+xml' }); const texture: any = await new PixiFactory().textureFromData(blob); expect((global as any).createImageBitmap).not.toHaveBeenCalled(); expect(images).toHaveLength(1); expect(images[0].src).toBe('blob:svg'); expect(decode).toHaveBeenCalled(); expect(texture.source.options.resource).toBe(images[0]); expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:svg'); } finally { (global as any).Image = OriginalImage; URL.createObjectURL = originalCreate; URL.revokeObjectURL = originalRevoke; } }); it('disposeTexture destroys the texture together with its source', async () => { const factory = new PixiFactory(); const texture: any = await factory.textureFromData(new Blob(['x'])); factory.disposeTexture(texture); expect(texture.destroy).toHaveBeenCalledWith(true); }); it('prepare uploads the source on every initialized renderer of the scene, and is a no-op without one', async () => { const ready = { nativeTextureSystem: { initSource: jest.fn() } }; const notReady = { nativeTextureSystem: null }; const scene: any = { renderers: new Set([ready, notReady]) }; const texture: any = await new PixiFactory(scene).textureFromData(new Blob(['x'])); await new PixiFactory(scene).prepare(texture); expect(ready.nativeTextureSystem.initSource).toHaveBeenCalledWith(texture.source); await expect(new PixiFactory().prepare(texture)).resolves.toBeUndefined(); }); }); describe('PixiFactory.createCamera', () => { it('creates a camera at zoom 1', () => { const camera = new PixiFactory().createCamera(); expect(camera).toBeInstanceOf(PixiCameraComponent); expect(camera.zoom).toBe(1); }); });