import { expect, mock, test } from 'bun:test' import type { Font, Paint, Surface } from 'canvaskit-wasm' import { createImageCache } from '#core/canvas/images/cache' import { ImagePreviewCache } from '#core/canvas/images/previews' import type { SkiaRenderer } from '#core/canvas/renderer' import { EffectRasterCache } from '#core/canvas/renderer/effect-raster-cache' import { destroyRenderer } from '#core/canvas/renderer/lifecycle' import { createGlyphSilhouetteCache } from '#core/canvas/text/derived' import { TextPreparationCache } from '#core/canvas/text/preparation-cache' import { asDouble, asRenderer } from './helpers' function deletable() { return { delete: mock() } as T & { delete: ReturnType } } function createRenderer() { const renderer: Partial = { destroyed: false, textPreparationCache: new TextPreparationCache(), transientPreviews: new Map(), imageCache: createImageCache(), imagePreviews: new ImagePreviewCache(() => undefined), onImagePreviewReady: null, vectorPathCache: new Map(), vectorStrokePathCache: new Map(), vectorStrokeOutlineCache: new Map(), fillGeometryCache: new Map(), strokeGeometryCache: new Map(), glyphSilhouetteCache: createGlyphSilhouetteCache(), fillPaint: deletable(), strokePaint: deletable(), selectionPaint: deletable(), parentOutlinePaint: deletable(), snapPaint: deletable(), auxFill: deletable(), auxStroke: deletable(), opacityPaint: deletable(), effectLayerPaint: deletable(), textFont: deletable(), labelFont: deletable(), sizeFont: deletable(), sectionTitleFont: deletable(), componentLabelFont: deletable(), fontMgr: null, fontProvider: null, fontsLoaded: true, rulerBgPaint: deletable(), rulerTickPaint: deletable(), rulerTextPaint: deletable(), rulerHlPaint: deletable(), rulerBadgePaint: deletable(), rulerLabelPaint: deletable(), penPathPaint: deletable(), penLiveStrokePaint: deletable(), penHandlePaint: deletable(), penVertexFill: deletable(), penVertexStroke: deletable(), imageFilterCache: new Map(), maskFilterCache: new Map(), nodePictureCache: new Map(), effectRasterCache: new EffectRasterCache(), subtreePictureCache: new Map(), scenePicture: null, sceneBacking: null, sceneBackingBuild: null, tiledScene: asDouble({ destroy: mock() }), labelParagraphCache: asDouble({ clear: mock() }), _flashPaint: null, profiler: { destroy: mock() } as Partial as SkiaRenderer['profiler'], surface: deletable() } return asRenderer(renderer) } test('destroyRenderer releases tiled resources before deleting the main surface', () => { const renderer = createRenderer() const teardown: string[] = [] renderer.textPreparationCache.clear = mock(() => { teardown.push('text') }) renderer.tiledScene.destroy = mock(() => teardown.push('tiled')) renderer.surface.delete = mock(() => teardown.push('surface')) as typeof renderer.surface.delete destroyRenderer(renderer) expect(renderer.tiledScene.destroy).toHaveBeenCalledTimes(1) expect(renderer.surface.delete).toHaveBeenCalledTimes(1) expect(teardown).toEqual(['text', 'tiled', 'surface']) }) test('destroyRenderer deletes all renderer-owned paints and label fonts', () => { const renderer = createRenderer() const parentOutlinePaint = renderer.parentOutlinePaint const sectionTitleFont = renderer.sectionTitleFont const componentLabelFont = renderer.componentLabelFont destroyRenderer(renderer) expect(parentOutlinePaint.delete).toHaveBeenCalled() expect(sectionTitleFont?.delete).toHaveBeenCalled() expect(componentLabelFont?.delete).toHaveBeenCalled() })