openpencil/tests/engine/render/canvas/tiles/cache.test.ts
Danila Poyarkov 11baf4f8f5 refactor(canvas): share bounded resource cache bookkeeping
Share indexed recency, count/weight budgets and native disposal across six caches while preserving domain invalidation and eviction policies. Keep pools, weak memos and dependency-owned resources separate.\n\nAccount for effect pixels incrementally and preserve caller ownership on rejection. Validate disposal, slot reuse, exact integer accounting and unchanged raster output.
2026-09-15 21:28:16 +03:00

83 lines
2.4 KiB
TypeScript

import { describe, expect, mock, test } from 'bun:test'
import type { Image as CKImage } from 'canvaskit-wasm'
import { TileImageCache, type RenderedTile } from '#core/canvas/renderer/tiles'
function tile(x: number): RenderedTile {
const image: Pick<CKImage, 'width' | 'height' | 'delete'> = {
width: () => 256,
height: () => 256,
delete: mock()
}
return {
key: { pageId: 'page', level: 1, x, y: 0 },
image,
chunkCount: 1,
estimatedCost: 1,
renderMs: 1,
allocationMs: 0,
drawMs: 1,
flushMs: 0,
snapshotMs: 0
}
}
describe('tile image cache', () => {
test('disposes rejected tiles without returning a deleted image', () => {
const cache = new TileImageCache(1)
const oversized = tile(0)
expect(cache.install(oversized, 1)).toBeNull()
expect(oversized.image.delete).toHaveBeenCalledTimes(1)
expect(cache.size()).toBe(0)
expect(cache.byteSize()).toBe(0)
cache.clear()
expect(oversized.image.delete).toHaveBeenCalledTimes(1)
})
test('replaces native images and reports memory', () => {
const cache = new TileImageCache()
const first = tile(0)
const second = tile(0)
cache.install(first, 1)
cache.install(second, 2)
expect(first.image.delete).toHaveBeenCalledTimes(1)
expect(cache.size()).toBe(1)
expect(cache.byteSize()).toBe(256 * 256 * 4)
cache.clear()
expect(second.image.delete).toHaveBeenCalledTimes(1)
})
test('invalidates intersecting tiles and advances unaffected generations', () => {
const cache = new TileImageCache()
const first = tile(0)
const second = tile(1)
cache.install(first, 1)
cache.install(second, 1)
expect(cache.invalidateBounds('page', { minX: 300, minY: 10, maxX: 340, maxY: 40 }, 2)).toBe(1)
expect(cache.get(first.key)?.contentGeneration).toBe(2)
expect(cache.get(second.key)).toBeNull()
expect(second.image.delete).toHaveBeenCalledTimes(1)
cache.clear()
})
test('evicts least recently used tiles within its byte budget', () => {
const bytes = 256 * 256 * 4
const cache = new TileImageCache(bytes * 2)
const first = tile(0)
const second = tile(1)
const third = tile(2)
cache.install(first, 1)
cache.install(second, 1)
cache.get(first.key)
cache.install(third, 1)
expect(second.image.delete).toHaveBeenCalledTimes(1)
expect(cache.get(first.key)).not.toBeNull()
expect(cache.get(third.key)).not.toBeNull()
cache.clear()
})
})