openpencil/tests/engine/render/canvas/effects/path-shapes.test.ts

115 lines
3.3 KiB
TypeScript
Raw Normal View History

import { describe, expect, mock, test } from 'bun:test'
import type { Canvas } from 'canvaskit-wasm'
import type { SceneNode } from '@open-pencil/scene-graph'
import { renderEffects } from '#core/canvas/shadows'
import { createMockCanvas, createMockRenderer, mockCalls } from './helpers'
const requiredNodeFields = { childIds: [] as string[], strokeGeometry: [] }
describe('path shape shadows', () => {
test('drop shadow for a star without imported geometry uses the star path', () => {
const r = createMockRenderer()
const canvas = createMockCanvas()
const node: Partial<SceneNode> = {
...requiredNodeFields,
type: 'STAR',
width: 100,
height: 100,
fills: [],
effects: [
{
type: 'DROP_SHADOW',
visible: true,
color: { r: 0, g: 0, b: 0, a: 1 },
offset: { x: 0, y: 0 },
radius: 10,
spread: 5
}
]
}
renderEffects(r, canvas as Canvas, node as SceneNode, new Float32Array(4), false, 'behind')
expect(r.makePolygonPath).toHaveBeenCalled()
expect(canvas.drawPath).toHaveBeenCalled()
expect(canvas.drawRect).not.toHaveBeenCalled()
})
test('drop shadow spread combines and deletes a detached stroked ring', () => {
const r = createMockRenderer()
const canvas = createMockCanvas()
const cachedPath = new r.ck.Path()
const ringPath = new r.ck.Path()
const combinedPath = new r.ck.Path()
cachedPath.makeStroked = mock(() => ringPath)
r.ck.Path.MakeFromOp = mock(() => combinedPath)
r.getFillGeometry = mock(() => [cachedPath])
const node: Partial<SceneNode> = {
...requiredNodeFields,
type: 'STAR',
width: 100,
height: 100,
fills: [{ visible: true, type: 'SOLID', color: { r: 1, g: 1, b: 1, a: 1 }, opacity: 1 }],
effects: [
{
type: 'DROP_SHADOW',
visible: true,
color: { r: 0, g: 0, b: 0, a: 1 },
offset: { x: 0, y: 0 },
radius: 10,
spread: 10
}
]
}
renderEffects(r, canvas as Canvas, node as SceneNode, new Float32Array(4), false, 'behind')
const makeFromOp = r.ck.Path.MakeFromOp as ReturnType<typeof mock>
expect(makeFromOp).toHaveBeenCalledWith(cachedPath, ringPath, r.ck.PathOp.Union)
expect(ringPath.delete).toHaveBeenCalledTimes(1)
expect(combinedPath.delete).toHaveBeenCalledTimes(1)
expect(cachedPath.delete).not.toHaveBeenCalled()
})
test('inner shadow for a polygon clips and cuts out with polygon paths', () => {
const r = createMockRenderer()
const canvas = createMockCanvas()
const node: Partial<SceneNode> = {
...requiredNodeFields,
type: 'POLYGON',
width: 100,
height: 100,
fills: [],
effects: [
{
type: 'INNER_SHADOW',
visible: true,
color: { r: 0, g: 0, b: 0, a: 1 },
offset: { x: 5, y: 5 },
radius: 10,
spread: 3
}
]
}
renderEffects(
r,
canvas as Canvas,
node as SceneNode,
new Float32Array([0, 0, 100, 100]),
false,
'front'
)
expect(mockCalls(r.makePolygonPath).length).toBeGreaterThanOrEqual(2)
expect(canvas.clipPath).toHaveBeenCalled()
expect(canvas.drawPath).toHaveBeenCalled()
expect(canvas.clipRect).not.toHaveBeenCalled()
})
})