The 54 editor engine tests move to packages/core/tests/editor with their helpers, importing Core's public subpaths or source modules relatively. The package gains test and typecheck scripts; the first type check fixed nullable lookups, stale imports, and renderer doubles, and graph events now declare the GraphEventRenderer surface they invalidate. The engine baseline drops to 534 files.
188 lines
5.3 KiB
TypeScript
188 lines
5.3 KiB
TypeScript
import { describe, test, expect } from 'bun:test'
|
|
|
|
import { createEditor, opacityFromBuffer } from '@open-pencil/core/editor'
|
|
|
|
import { getNodeOrThrow } from '../helpers/assert'
|
|
|
|
describe('editor.setOpacity', () => {
|
|
function setup() {
|
|
const editor = createEditor()
|
|
const pageId = editor.graph.getPages()[0].id
|
|
const rect = editor.graph.createNode('RECTANGLE', pageId, {
|
|
name: 'Rect',
|
|
x: 0,
|
|
y: 0,
|
|
width: 50,
|
|
height: 50
|
|
})
|
|
editor.select([rect.id])
|
|
return { editor, rect }
|
|
}
|
|
|
|
test('sets opacity to 50%', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(0.5)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(0.5)
|
|
})
|
|
|
|
test('sets opacity to 100% (digit 0)', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(0.5)
|
|
editor.setOpacity(1)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
})
|
|
|
|
test('clamps opacity above 100%', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(1.5)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
})
|
|
|
|
test('clamps opacity below 0%', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(-0.3)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(0)
|
|
})
|
|
|
|
test('opacity change is undoable as a single batch entry', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(0.3)
|
|
expect(editor.undo.canUndo).toBe(true)
|
|
|
|
editor.undo.undo()
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
|
|
editor.undo.redo()
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(0.3)
|
|
})
|
|
|
|
test('coalesces buffered shortcut updates into one undo entry', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(0.2, 'shortcut-session')
|
|
editor.setOpacity(0.28, 'shortcut-session')
|
|
|
|
editor.undo.undo()
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
|
|
editor.undo.redo()
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(0.28)
|
|
})
|
|
|
|
test('keeps separate shortcut sessions as separate undo entries', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(0.2, 'shortcut-session-1')
|
|
editor.setOpacity(0.8, 'shortcut-session-2')
|
|
|
|
editor.undo.undo()
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(0.2)
|
|
editor.undo.undo()
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
})
|
|
|
|
test('opacity batch for multiple selections collapses to one undo entry', () => {
|
|
const { editor, rect } = setup()
|
|
const pageId = editor.graph.getPages()[0].id
|
|
const rect2 = editor.graph.createNode('RECTANGLE', pageId, {
|
|
name: 'Rect2',
|
|
x: 100,
|
|
y: 0,
|
|
width: 50,
|
|
height: 50
|
|
})
|
|
editor.select([rect.id, rect2.id])
|
|
|
|
editor.setOpacity(0.7)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(0.7)
|
|
expect(getNodeOrThrow(editor.graph, rect2.id).opacity).toBe(0.7)
|
|
|
|
editor.undo.undo()
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
expect(getNodeOrThrow(editor.graph, rect2.id).opacity).toBe(1)
|
|
})
|
|
|
|
test('no-op when opacity already matches', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(1)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
expect(editor.undo.canUndo).toBe(false)
|
|
})
|
|
|
|
test('no-op with no selection', () => {
|
|
const { editor } = setup()
|
|
|
|
editor.clearSelection()
|
|
editor.setOpacity(0.5)
|
|
expect(editor.undo.canUndo).toBe(false)
|
|
})
|
|
|
|
test('rejects NaN', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(Number.NaN)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
expect(editor.undo.canUndo).toBe(false)
|
|
})
|
|
|
|
test('rejects Infinity', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(Number.POSITIVE_INFINITY)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
expect(editor.undo.canUndo).toBe(false)
|
|
})
|
|
|
|
test('rejects -Infinity', () => {
|
|
const { editor, rect } = setup()
|
|
|
|
editor.setOpacity(Number.NEGATIVE_INFINITY)
|
|
expect(getNodeOrThrow(editor.graph, rect.id).opacity).toBe(1)
|
|
expect(editor.undo.canUndo).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('opacityFromBuffer', () => {
|
|
test('single 0 returns 100%', () => {
|
|
expect(opacityFromBuffer('0')).toBe(1)
|
|
})
|
|
|
|
test('single digit returns decena', () => {
|
|
expect(opacityFromBuffer('5')).toBe(0.5)
|
|
expect(opacityFromBuffer('2')).toBe(0.2)
|
|
expect(opacityFromBuffer('9')).toBe(0.9)
|
|
})
|
|
|
|
test('two digits returns literal percent', () => {
|
|
expect(opacityFromBuffer('28')).toBe(0.28)
|
|
expect(opacityFromBuffer('35')).toBe(0.35)
|
|
expect(opacityFromBuffer('00')).toBe(0)
|
|
expect(opacityFromBuffer('05')).toBe(0.05)
|
|
})
|
|
|
|
test('three digits clamps to 100%', () => {
|
|
expect(opacityFromBuffer('100')).toBe(1)
|
|
expect(opacityFromBuffer('150')).toBe(1)
|
|
})
|
|
|
|
test('invalid buffer returns 100%', () => {
|
|
expect(opacityFromBuffer('')).toBe(1)
|
|
expect(opacityFromBuffer('abc')).toBe(1)
|
|
})
|
|
|
|
test('rejects partially numeric buffers', () => {
|
|
expect(opacityFromBuffer('12x')).toBe(1)
|
|
expect(opacityFromBuffer('5abc')).toBe(1)
|
|
expect(opacityFromBuffer('3.5')).toBe(1)
|
|
expect(opacityFromBuffer('1e2')).toBe(1)
|
|
expect(opacityFromBuffer('-5')).toBe(1)
|
|
expect(opacityFromBuffer(' 5')).toBe(1)
|
|
})
|
|
})
|