openpencil/tests/engine/style-runs.test.ts
Danila Poyarkov 9784544f1f Clean up tools/schema, add geometry.ts, isMixed helper, fix imports, add tests
- Strip tools/schema.ts from 1831 to 65 lines (remove duplicate tool defs)
- Fix tools/index.ts to export ALL_TOOLS from registry instead of schema
- Fix renderJsx → renderJSX typo in tools/create.ts and structure.ts
- Add geometry.ts with shared rotation/coordinate math utilities
- Add isArrayMixed() helper to use-multi-props.ts, deduplicate FillSection/StrokeSection/EffectsSection
- Fix broken test imports: use @open-pencil/core instead of relative paths into packages/core/src/
- Merge duplicate import statements across all test files
- Export profiler internals and style-run helpers from core index.ts
- Add 6 new test files: color, undo, snap, vector, style-runs, text-editor (98 new tests)
- Update renderer-effects test for renderer/ directory split
2026-03-06 22:33:10 +03:00

158 lines
5.4 KiB
TypeScript

import { describe, test, expect } from 'bun:test'
import {
getStyleAt,
applyStyleToRange,
removeStyleFromRange,
selectionHasStyle,
adjustRunsForInsert,
adjustRunsForDelete,
toggleBoldInRange,
toggleItalicInRange,
toggleDecorationInRange,
type StyleRun,
} from '@open-pencil/core'
describe('getStyleAt', () => {
test('returns style within run', () => {
const runs: StyleRun[] = [{ start: 2, length: 3, style: { fontWeight: 700 } }]
expect(getStyleAt(runs, 3)).toEqual({ fontWeight: 700 })
})
test('returns empty object outside runs', () => {
const runs: StyleRun[] = [{ start: 5, length: 2, style: { fontWeight: 700 } }]
expect(getStyleAt(runs, 0)).toEqual({})
})
})
describe('applyStyleToRange', () => {
test('applies bold to range', () => {
const runs = applyStyleToRange([], 2, 5, { fontWeight: 700 }, 10)
expect(runs).toHaveLength(1)
expect(runs[0].start).toBe(2)
expect(runs[0].length).toBe(3)
expect(runs[0].style.fontWeight).toBe(700)
})
test('merges with existing style', () => {
const existing: StyleRun[] = [{ start: 0, length: 5, style: { fontWeight: 700 } }]
const runs = applyStyleToRange(existing, 2, 4, { italic: true }, 10)
const style = getStyleAt(runs, 3)
expect(style.fontWeight).toBe(700)
expect(style.italic).toBe(true)
})
})
describe('removeStyleFromRange', () => {
test('removes keys from range', () => {
const existing: StyleRun[] = [{ start: 0, length: 5, style: { fontWeight: 700, italic: true } }]
const runs = removeStyleFromRange(existing, 0, 5, ['fontWeight'], 10)
const style = getStyleAt(runs, 2)
expect(style.fontWeight).toBeUndefined()
expect(style.italic).toBe(true)
})
})
describe('selectionHasStyle', () => {
test('all chars have style', () => {
const runs: StyleRun[] = [{ start: 0, length: 5, style: { fontWeight: 700 } }]
expect(selectionHasStyle(runs, 0, 5, 'fontWeight', 700)).toBe(true)
})
test('some chars missing', () => {
const runs: StyleRun[] = [{ start: 0, length: 3, style: { fontWeight: 700 } }]
expect(selectionHasStyle(runs, 0, 5, 'fontWeight', 700)).toBe(false)
})
})
describe('adjustRunsForInsert', () => {
test('insert before run shifts start', () => {
const runs: StyleRun[] = [{ start: 5, length: 3, style: { fontWeight: 700 } }]
const adjusted = adjustRunsForInsert(runs, 2, 4)
expect(adjusted[0].start).toBe(9)
expect(adjusted[0].length).toBe(3)
})
test('insert inside run extends length', () => {
const runs: StyleRun[] = [{ start: 2, length: 6, style: { fontWeight: 700 } }]
const adjusted = adjustRunsForInsert(runs, 4, 3)
expect(adjusted[0].start).toBe(2)
expect(adjusted[0].length).toBe(9)
})
test('insert after run leaves it unchanged', () => {
const runs: StyleRun[] = [{ start: 0, length: 3, style: { fontWeight: 700 } }]
const adjusted = adjustRunsForInsert(runs, 10, 5)
expect(adjusted[0]).toEqual({ start: 0, length: 3, style: { fontWeight: 700 } })
})
})
describe('adjustRunsForDelete', () => {
test('delete before run shifts start', () => {
const runs: StyleRun[] = [{ start: 10, length: 5, style: { fontWeight: 700 } }]
const adjusted = adjustRunsForDelete(runs, 2, 3)
expect(adjusted[0].start).toBe(7)
expect(adjusted[0].length).toBe(5)
})
test('delete inside run shrinks length', () => {
const runs: StyleRun[] = [{ start: 0, length: 10, style: { fontWeight: 700 } }]
const adjusted = adjustRunsForDelete(runs, 3, 4)
expect(adjusted[0].length).toBe(6)
})
test('delete entire run removes it', () => {
const runs: StyleRun[] = [{ start: 2, length: 3, style: { fontWeight: 700 } }]
const adjusted = adjustRunsForDelete(runs, 2, 3)
expect(adjusted).toHaveLength(0)
})
test('delete after run leaves it unchanged', () => {
const runs: StyleRun[] = [{ start: 0, length: 3, style: { fontWeight: 700 } }]
const adjusted = adjustRunsForDelete(runs, 5, 2)
expect(adjusted[0]).toEqual({ start: 0, length: 3, style: { fontWeight: 700 } })
})
})
describe('toggleBoldInRange', () => {
test('non-bold to bold', () => {
const { runs, newWeight } = toggleBoldInRange([], 0, 5, 400, 10)
expect(newWeight).toBe(700)
expect(runs).toHaveLength(1)
expect(runs[0].style.fontWeight).toBe(700)
})
test('bold to non-bold', () => {
const existing: StyleRun[] = [{ start: 0, length: 5, style: { fontWeight: 700 } }]
const { runs, newWeight } = toggleBoldInRange(existing, 0, 5, 400, 10)
expect(newWeight).toBe(400)
})
})
describe('toggleItalicInRange', () => {
test('toggle on', () => {
const { runs, newItalic } = toggleItalicInRange([], 0, 5, false, 10)
expect(newItalic).toBe(true)
expect(runs[0].style.italic).toBe(true)
})
test('toggle off', () => {
const existing: StyleRun[] = [{ start: 0, length: 5, style: { italic: true } }]
const { newItalic } = toggleItalicInRange(existing, 0, 5, false, 10)
expect(newItalic).toBe(false)
})
})
describe('toggleDecorationInRange', () => {
test('add underline', () => {
const { newDeco } = toggleDecorationInRange([], 0, 5, 'UNDERLINE', 'NONE', 10)
expect(newDeco).toBe('UNDERLINE')
})
test('remove underline', () => {
const existing: StyleRun[] = [{ start: 0, length: 5, style: { textDecoration: 'UNDERLINE' } }]
const { newDeco } = toggleDecorationInRange(existing, 0, 5, 'UNDERLINE', 'NONE', 10)
expect(newDeco).toBe('NONE')
})
})