import { Clipboard } from '@ephox/agar'; import { describe, it } from '@ephox/bedrock-client'; import { Arr, Optional } from '@ephox/katamari'; import { Insert, SugarElement, SugarNode, TextContent, Traverse } from '@ephox/sugar'; import { LegacyUnit, TinyAssertions, TinyDom, TinyHooks, TinySelections } from '@ephox/wrap-mcagar'; import { assert } from 'chai'; import Editor from 'tinymce/core/api/Editor'; import { TableEventData } from 'tinymce/core/api/EventTypes'; import * as FakeClipboard from 'tinymce/plugins/table/api/Clipboard'; import TablePlugin from 'tinymce/plugins/table/Plugin'; describe('browser.tinymce.plugins.table.ClipboardTest', () => { const hook = TinyHooks.bddSetupLight({ plugins: 'table', indent: false, valid_styles: { '*': 'width,height,vertical-align,text-align,float,border-color,background-color,border,padding,border-spacing,border-collapse' }, base_url: '/project/tinymce/js/tinymce' }, [ TablePlugin ], true); const cleanTableHtml = (html: string) => html.replace(/

( |]+>)<\/p>$/, ''); const selectOne = (editor: Editor, start: string) => { const startElm = editor.dom.select(start)[0]; editor.dispatch('mousedown', { target: startElm, button: 0 } as unknown as MouseEvent); editor.dispatch('mouseup', { target: startElm, button: 0 } as unknown as MouseEvent); LegacyUnit.setSelection(editor, startElm, 0); }; const selectRangeXY = (editor: Editor, start: string, end: string) => { const startElm = editor.dom.select(start)[0]; const endElm = editor.dom.select(end)[0]; editor.dispatch('mousedown', { target: startElm, button: 0 } as unknown as MouseEvent); editor.dispatch('mouseover', { target: endElm, button: 0 } as unknown as MouseEvent); editor.dispatch('mouseup', { target: endElm, button: 0 } as unknown as MouseEvent); LegacyUnit.setSelection(editor, endElm, 0); }; const createRow = (cellContents: string[]): SugarElement => { const tr = SugarElement.fromTag('tr'); Arr.each(cellContents, (content) => { const td = SugarElement.fromTag('td'); TextContent.set(td, content); Insert.append(tr, td); }); return tr; }; it('TBA: selection.getContent with format equal to text', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '' + '
ab
' ); selectRangeXY(editor, 'table tr td:nth-child(1)', 'table tr td:nth-child(2)'); assert.equal(editor.selection.getContent({ format: 'text' }), 'ab'); }); it('TBA: mceTablePasteRowBefore command', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '
12
23
34
' ); selectOne(editor, 'tr:nth-child(1) td'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'tr:nth-child(3) td'); editor.execCommand('mceTablePasteRowBefore'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '' + '
12
23
12
34
' ); selectOne(editor, 'tr:nth-child(3) td'); editor.execCommand('mceTablePasteRowBefore'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '' + '' + '
12
23
12
12
34
' ); }); it('TBA: mceTablePasteRowAfter command', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
12
23
' ); selectOne(editor, 'tr:nth-child(1) td'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'tr:nth-child(2) td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
12
23
12
' ); selectOne(editor, 'tr:nth-child(2) td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '' + '
12
23
12
12
' ); }); it('TBA: mceTablePasteRowAfter command with thead and tfoot', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
Head1Head2
ab
cd
Foot1Foot2
' ); selectOne(editor, 'tbody tr:nth-child(1) td'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'tbody tr:nth-child(2) td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
Head1Head2
ab
cd
ab
Foot1Foot2
' ); selectOne(editor, 'tbody tr:nth-child(2) td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
Head1Head2
ab
cd
ab
ab
Foot1Foot2
' ); }); it('TBA: mceTablePasteRowAfter from merged row source', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '
1 23
12
' ); selectOne(editor, 'tr:nth-child(1) td'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'tr:nth-child(2) td:nth-child(2)'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
1 23
12
1 23
' ); }); it('TBA: mceTablePasteRowAfter from merged row source to merged row target', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '
1 23
12
' ); selectOne(editor, 'tr:nth-child(1) td'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'tr:nth-child(1) td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
1 23
1 23
12 
' ); }); it('TBA: mceTablePasteRowAfter to wider table', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '
1a2a3a
' + '' + '' + '' + '' + '
1b2b3b4b
' ); selectOne(editor, 'table:nth-child(1) tr:nth-child(1) td'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'table:nth-child(2) td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '
1a2a3a
' + '' + '' + '' + '' + '' + '
1b2b3b4b
1a2a3a 
' ); }); it('TBA: mceTablePasteRowAfter to narrower table', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '
1a2a3a4a5a
1a2a5a
' + '' + '' + '' + '' + '
1b2b3b
' ); selectRangeXY(editor, 'table:nth-child(1) tr:nth-child(1) td:nth-child(1)', 'table:nth-child(1) tr:nth-child(2) td:nth-child(3)'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'table:nth-child(2) tr td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '
1a2a3a4a5a
1a2a5a
' + '' + '' + '' + '' + '' + '' + '
1b2b3b  
1a2a3a4a5a
1a2a5a
' ); }); it('TBA: Copy/paste several rows with multiple rowspans', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '' + '
123
23
12
12
' ); selectRangeXY(editor, 'table tr:nth-child(1) td:nth-child(1)', 'table tr:nth-child(3) td:nth-child(2)'); editor.execCommand('mceTableCopyRow'); selectOne(editor, 'table tr:nth-child(4) td'); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
123
23
12
12
123
23
12
' ); }); it('TBA: row clipboard api', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
12
23
' ); LegacyUnit.setSelection(editor, 'tr:nth-child(1) td', 0); editor.execCommand('mceTableCopyRow'); const clipboardRows = FakeClipboard.getRows().getOr([] as SugarElement[]); assert.equal(clipboardRows.length, 1); assert.isTrue(SugarNode.isTag('tr')(clipboardRows[0])); FakeClipboard.setRows(Optional.some(clipboardRows.concat([ createRow([ 'a', 'b' ]), createRow([ 'c', 'd' ]) ]))); LegacyUnit.setSelection(editor, 'tr:nth-child(2) td', 0); editor.execCommand('mceTablePasteRowAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '' + '' + '
12
23
12
ab
cd
' ); }); it('TINY-6006: mceTablePasteColBefore command', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
123
234
' ); selectOne(editor, 'tr td:nth-child(1)'); editor.execCommand('mceTableCopyCol'); selectOne(editor, 'tr td:nth-child(3)'); editor.execCommand('mceTablePasteColBefore'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '
1213
2324
' ); }); it('TINY-6006: mceTablePasteColAfter command', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
123
234
' ); selectOne(editor, 'tr td:nth-child(2)'); editor.execCommand('mceTableCopyCol'); selectOne(editor, 'tr td:nth-child(3)'); editor.execCommand('mceTablePasteColAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '
1232
2343
' ); }); it('TINY-6006: Copy/paste several cols with colspans', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '
13
12
123
' ); selectRangeXY(editor, 'table tr:nth-child(1) td:nth-child(1)', 'table tr:nth-child(2) td:nth-child(2)'); editor.execCommand('mceTableCopyCol'); selectOne(editor, 'table tr:nth-child(1) td:nth-child(2)'); editor.execCommand('mceTablePasteColAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
1313
1212
123123
' ); }); it('TINY-6006: col clipboard api', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
12
23
' ); LegacyUnit.setSelection(editor, 'tr td:nth-child(1)', 0); editor.execCommand('mceTableCopyCol'); const clipboardCols = FakeClipboard.getColumns().getOr([]); assert.equal(clipboardCols.length, 2); assert.isTrue(SugarNode.isTag('tr')(clipboardCols[0])); const cells = Traverse.children(clipboardCols[0]); assert.equal(cells.length, 1); assert.isTrue(SugarNode.isTag('td')(cells[0])); FakeClipboard.setColumns(Optional.some([ createRow([ 'a', 'b' ]), createRow([ 'c', 'd' ]) ])); LegacyUnit.setSelection(editor, 'tr td:nth-child(2)', 0); editor.execCommand('mceTablePasteColAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '
12ab
23cd
' ); }); it('TINY-6684: mceTablePasteColBefore command with colgroups', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '
12
23
' ); selectOne(editor, 'tr td:nth-child(2)'); editor.execCommand('mceTableCopyCol'); selectOne(editor, 'tr td:nth-child(1)'); editor.execCommand('mceTablePasteColBefore'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
212
323
' ); }); it('TINY-6684: mceTablePasteColAfter command with colgroups', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '
123
234
' ); selectOne(editor, 'tr td:nth-child(1)'); editor.execCommand('mceTableCopyCol'); selectOne(editor, 'tr td:nth-child(2)'); editor.execCommand('mceTablePasteColAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
1213
2324
' ); }); it('TINY-6765: mceTablePasteColBefore command with locked columns', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '
12
23
' ); selectRangeXY(editor, 'table tr:nth-child(1) td:nth-child(1)', 'table tr:nth-child(2) td:nth-child(2)'); // Only the second column should be copied as the first column is locked editor.execCommand('mceTableCopyCol'); selectOne(editor, 'tr td:nth-child(2)'); editor.execCommand('mceTablePasteColBefore'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
122
233
' ); }); it('TINY-6765: mceTablePasteColAfter command with locked columns', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '' + '' + '
12
23
' ); selectRangeXY(editor, 'table tr:nth-child(1) td:nth-child(1)', 'table tr:nth-child(2) td:nth-child(2)'); // Only the first column should be copied as the second column is locked editor.execCommand('mceTableCopyCol'); selectOne(editor, 'tr td:nth-child(1)'); editor.execCommand('mceTablePasteColAfter'); assert.equal( cleanTableHtml(editor.getContent()), '' + '' + '' + '' + '' + '' + '
112
223
' ); }); it('TINY-7485: Pasting into a table with a single cells contents selected', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
AB
CD
' ); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 0, [ 0, 0, 0, 1, 0 ], 1); Clipboard.pasteItems(TinyDom.body(editor), { 'text/html': '' + '' + '' + '
AB
CD
' }); TinyAssertions.assertContent(editor, '' + '' + '' + '
AAB
CCD
' ); }); it('TINY-7485: Pasting into a table with multiple cells selected should paste into the first cell', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '' + '
AB 
CD 
   
' ); selectRangeXY(editor, 'table tr:nth-child(2) td:nth-child(2)', 'table tr:nth-child(3) td:nth-child(3)'); Clipboard.pasteItems(TinyDom.body(editor), { 'text/html': '' + '' + '' + '
AB
CD
' }); TinyAssertions.assertContent(editor, '' + '' + '' + '' + '
AB 
CAB
 CD
' ); }); it('TINY-6939: Pasting into a table should fire TableModified event', () => { const editor = hook.editor(); const events: TableEventData[] = []; const callback = (e: TableEventData) => { events.push({ structure: e.structure, style: e.style, }); }; editor.on('TableModified', callback); editor.setContent( '' + '' + '
AB
' ); selectRangeXY(editor, 'table tr td:nth-child(1)', 'table tr td:nth-child(2)'); Clipboard.pasteItems(TinyDom.body(editor), { 'text/html': '' + '' + '
12
' }); assert.deepEqual(events, [{ structure: true, style: true }]); editor.off('TableModified', callback); }); it('TINY-8568: should correctly copy and paste colgroup table with complex selection', () => { const editor = hook.editor(); const inputTable = '' + '' + '' + '' + '' + '' + '' + '' + '
  a  
 b  
  c 
  d  
'; const expectedTable = '' + '' + '' + '' + '' + '' + '' + '' + '
 a 
b 
 c
 d 
'; editor.setContent( inputTable + '

 

' ); selectRangeXY(editor, 'table tr:nth-child(1) td:nth-child(3)', 'table tr:nth-child(4) td:nth-child(3)'); const dataTransfer = Clipboard.copy(TinyDom.body(editor)); assert.equal(dataTransfer.getData('text/html'), '' + expectedTable); TinySelections.setCursor(editor, [ 1, 0 ], 0); Clipboard.pasteItems(TinyDom.body(editor), Arr.mapToObject(dataTransfer.types, (type) => dataTransfer.getData(type))); TinyAssertions.assertContent(editor, inputTable + expectedTable); }); });