import { expect } from '@open-wc/testing'; import { TextSelection } from 'prosemirror-state'; import { defaultSchema, htmlToDoc, docToHtml, createEditorState, getCommand, isExcelHtml, isOfficeHtml, cleanOfficeHtml, armFormatPainter, applyFormatPainter, isFormatPainterArmed, disarmFormatPainter, } from './index'; /** Editor state with the whole document selected. */ function stateWithAllSelected(html: string) { const doc = htmlToDoc(defaultSchema, html); const state = createEditorState({ doc }); const selection = TextSelection.create(state.doc, 1, state.doc.content.size - 1); return state.apply(state.tr.setSelection(selection)); } /** Runs a command against a state and returns the resulting state. */ function run(state: ReturnType, name: Parameters[1], attrs?: Parameters[2]) { let next = state; const command = getCommand(state, name, attrs); command?.(state, tr => { next = state.apply(tr); }); return next; } describe('changeCase command', () => { it('uppercases the selection', () => { const state = run(stateWithAllSelected('

Hello world

'), 'changeCase', { textCase: 'upper' }); expect(docToHtml(state.doc)).to.equal('

HELLO WORLD

'); }); it('lowercases the selection', () => { const state = run(stateWithAllSelected('

HELLO

'), 'changeCase', { textCase: 'lower' }); expect(docToHtml(state.doc)).to.equal('

hello

'); }); it('title-cases the selection', () => { const state = run(stateWithAllSelected('

hello brave world

'), 'changeCase', { textCase: 'title' }); expect(docToHtml(state.doc)).to.equal('

Hello Brave World

'); }); it('sentence-cases the selection', () => { const state = run(stateWithAllSelected('

hello. there is more. ok

'), 'changeCase', { textCase: 'sentence', }); expect(docToHtml(state.doc)).to.equal('

Hello. There is more. Ok

'); }); it('preserves marks across the transform', () => { const state = run(stateWithAllSelected('

bold plain

'), 'changeCase', { textCase: 'upper', }); expect(docToHtml(state.doc)).to.equal('

BOLD PLAIN

'); }); it('is a no-op with an empty selection', () => { const doc = htmlToDoc(defaultSchema, '

hello

'); const state = createEditorState({ doc }); expect(getCommand(state, 'changeCase', { textCase: 'upper' })!(state, () => {})).to.be.false; }); }); describe('merge field', () => { it('inserts a merge-field node', () => { const doc = htmlToDoc(defaultSchema, '

'); let state = createEditorState({ doc }); state = state.apply( state.tr.setSelection(TextSelection.create(state.doc, 1)) ); const state2 = run(state, 'insertMergeField', { id: 'first_name', label: 'First name' }); const out = docToHtml(state2.doc); expect(out).to.include('data-merge-field="first_name"'); expect(out).to.include('First name'); }); it('round-trips through HTML', () => { const html = '

Email

'; expect(docToHtml(htmlToDoc(defaultSchema, html))).to.include('data-merge-field="email"'); }); }); describe('math', () => { it('inserts a math node', () => { const doc = htmlToDoc(defaultSchema, '

'); let state = createEditorState({ doc }); state = state.apply(state.tr.setSelection(TextSelection.create(state.doc, 1))); const state2 = run(state, 'insertMath', { latex: 'a^2 + b^2', mathDisplay: 'block' }); const out = docToHtml(state2.doc); expect(out).to.include('data-math="a^2 + b^2"'); expect(out).to.include('data-math-display="block"'); }); it('round-trips through HTML', () => { const html = '

x/y

'; expect(docToHtml(htmlToDoc(defaultSchema, html))).to.include('data-math="x/y"'); }); }); describe('format painter', () => { it('copies marks from one selection and applies to the next', () => { //

BOLD plain

— arm over the bold word, then apply to "plain". const doc = htmlToDoc(defaultSchema, '

bold plain

'); let state = createEditorState({ doc }); // Select the bold word (positions 1..5). state = state.apply(state.tr.setSelection(TextSelection.create(state.doc, 1, 5))); state = state.apply(armFormatPainter(state)!); expect(isFormatPainterArmed(state)).to.be.true; // Select "plain" (positions 6..11) and apply. state = state.apply(state.tr.setSelection(TextSelection.create(state.doc, 6, 11))); const applyTr = applyFormatPainter(state); expect(applyTr).to.not.be.null; state = state.apply(applyTr!); // "plain" (6..11) becomes bold; the space at 5 stays plain, so the two // bold runs render as separate tags. expect(docToHtml(state.doc)).to.equal('

bold plain

'); expect(isFormatPainterArmed(state)).to.be.false; }); it('disarms without applying', () => { const doc = htmlToDoc(defaultSchema, '

bold

'); let state = createEditorState({ doc }); state = state.apply(state.tr.setSelection(TextSelection.create(state.doc, 1, 5))); state = state.apply(armFormatPainter(state)!); state = state.apply(disarmFormatPainter(state)); expect(isFormatPainterArmed(state)).to.be.false; }); }); describe('Excel paste', () => { const excel = '' + '' + '' + '
Total
'; it('is detected as Excel and Office HTML', () => { expect(isExcelHtml(excel)).to.be.true; expect(isOfficeHtml(excel)).to.be.true; }); it('preserves cell background and alignment', () => { const cleaned = cleanOfficeHtml(excel); const doc = htmlToDoc(defaultSchema, cleaned); const out = docToHtml(doc); expect(out).to.include('Total'); // Alignment survives on the wrapped paragraph. expect(out).to.match(/text-align:\s*center/); // Background survives on the cell. expect(out.toLowerCase()).to.include('background-color'); }); });