import { describe, expect, test } from 'bun:test' import { adjustRunsForDelete, adjustRunsForInsert, applyStyleToRange, getStyleAt, removeStyleFromRange, selectionHasStyle, toggleBoldInRange, toggleDecorationInRange, toggleItalicInRange, 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 { 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') }) })