90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
import { describe, test, expect } from 'bun:test'
|
|
import { Cursor } from '../Cursor'
|
|
|
|
describe('MeasuredText tab protection', () => {
|
|
test('Cursor.fromText with tabs does not throw', () => {
|
|
const cursor = Cursor.fromText('a\tb', 80)
|
|
expect(cursor).toBeTruthy()
|
|
expect(cursor.text).toBe('a\tb')
|
|
})
|
|
|
|
test('tab text preserves original tabs after measurement', () => {
|
|
const cursor = Cursor.fromText('hello\tworld', 80)
|
|
expect(cursor.text).toBe('hello\tworld')
|
|
// Trigger measurement
|
|
cursor.getPosition()
|
|
expect(cursor.text).toBe('hello\tworld')
|
|
})
|
|
|
|
test('multi-line tab text works', () => {
|
|
const cursor = Cursor.fromText('foo\n\tbar', 80)
|
|
expect(cursor.text).toBe('foo\n\tbar')
|
|
expect(() => cursor.getPosition()).not.toThrow()
|
|
})
|
|
|
|
test('tabs in narrow columns (triggering visual wrap) does not throw', () => {
|
|
// 10 cols is narrow enough that tabs + text will wrap
|
|
const cursor = Cursor.fromText('x\t\thello world\tfoo', 10)
|
|
expect(() => cursor.getPosition()).not.toThrow()
|
|
expect(cursor.text).toBe('x\t\thello world\tfoo')
|
|
})
|
|
|
|
test('getPosition returns valid coordinates for tab text', () => {
|
|
const cursor = Cursor.fromText('a\tb\tc', 80)
|
|
const pos = cursor.getPosition()
|
|
expect(pos.line).toBeGreaterThanOrEqual(0)
|
|
expect(pos.column).toBeGreaterThanOrEqual(0)
|
|
})
|
|
|
|
test('cursor movement on tab text does not throw', () => {
|
|
const cursor = Cursor.fromText('abc\tdef\tghi', 80)
|
|
// Move right through tab
|
|
const right1 = cursor.right()
|
|
expect(() => right1.getPosition()).not.toThrow()
|
|
const right2 = right1.right()
|
|
expect(() => right2.getPosition()).not.toThrow()
|
|
// Move to end
|
|
const end = cursor.endOfFile()
|
|
expect(() => end.getPosition()).not.toThrow()
|
|
// Move back
|
|
expect(() => end.left().getPosition()).not.toThrow()
|
|
})
|
|
|
|
test('tab text in very narrow views (1 col) does not throw', () => {
|
|
const cursor = Cursor.fromText('a\tb', 1)
|
|
expect(() => cursor.getPosition()).not.toThrow()
|
|
})
|
|
|
|
test('text with only tabs does not throw', () => {
|
|
const cursor = Cursor.fromText('\t\t', 80)
|
|
expect(() => cursor.getPosition()).not.toThrow()
|
|
expect(cursor.text).toBe('\t\t')
|
|
})
|
|
|
|
test('mixed tabs and newlines', () => {
|
|
const cursor = Cursor.fromText('a\tb\nc\td\ne\tf', 80)
|
|
expect(() => cursor.getPosition()).not.toThrow()
|
|
// Move to last line
|
|
const eof = cursor.endOfFile()
|
|
expect(eof.text).toBe('a\tb\nc\td\ne\tf')
|
|
})
|
|
|
|
test('placeholder characters in text are handled gracefully', () => {
|
|
// Text already contains \u2060 (word joiner)
|
|
const textWithPlaceholder = 'ab\u2060\tc'
|
|
const cursor = Cursor.fromText(textWithPlaceholder, 80)
|
|
expect(() => cursor.getPosition()).not.toThrow()
|
|
expect(cursor.text).toBe(textWithPlaceholder)
|
|
})
|
|
|
|
test('text with all placeholder candidates still works', () => {
|
|
// Text contains all four zero-width placeholder candidates
|
|
const allPlaceholders = 'a\u2060\u2061\u2062\u2063\tb'
|
|
const cursor = Cursor.fromText(allPlaceholders, 80)
|
|
expect(() => cursor.getPosition()).not.toThrow()
|
|
expect(cursor.text).toBe(allPlaceholders)
|
|
})
|
|
})
|
|
|
|
|