import { Assertions, Keys } from '@ephox/agar'; import { context, describe, it } from '@ephox/bedrock-client'; import { Arr } from '@ephox/katamari'; import { Attribute, Html, Remove, Replication, SelectorFilter, SelectorFind } from '@ephox/sugar'; import { TinyAssertions, TinyContentActions, TinyDom, TinyHooks, TinySelections } from '@ephox/wrap-mcagar'; import { assert } from 'chai'; import Editor from 'tinymce/core/api/Editor'; import * as TableDelete from 'tinymce/core/delete/TableDelete'; describe('browser.tinymce.core.delete.TableDeleteTest', () => { const hook = TinyHooks.bddSetupLight({ indent: false, base_url: '/project/tinymce/js/tinymce' }, [], true); const assertRawNormalizedContent = (editor: Editor, expectedContent: string) => { const element = Replication.deep(TinyDom.body(editor)); // Remove internal selection dom items Arr.each(SelectorFilter.descendants(element, '*[data-mce-bogus="all"]'), Remove.remove); Arr.each(SelectorFilter.descendants(element, '*'), (elm) => { Attribute.remove(elm, 'data-mce-selected'); }); Assertions.assertHtml('Should be expected contents', expectedContent, Html.get(element)); }; const doCommand = (editor: Editor, forward: boolean) => { const returnVal = TableDelete.backspaceDelete(editor, forward); returnVal.each((apply) => apply()); return returnVal.isSome(); }; const doDelete = (editor: Editor) => { const returnVal = doCommand(editor, true); assert.isTrue(returnVal, 'Should return true since the operation should have done something'); }; const doBackspace = (editor: Editor) => { const returnVal = doCommand(editor, false); assert.isTrue(returnVal, 'Should return true since the operation should have done something'); }; const noopDelete = (editor: Editor) => { const returnVal = doCommand(editor, true); assert.isFalse(returnVal, 'Should return false since the operation is a noop'); }; const noopBackspace = (editor: Editor) => { const returnVal = doCommand(editor, false); assert.isFalse(returnVal, 'Should return false since the operation is a noop'); }; const keyboardBackspace = (editor: Editor) => TinyContentActions.keystroke(editor, Keys.backspace()); context('Delete selected cells or cell ranges', () => { it('Collapsed range should be noop', () => { const editor = hook.editor(); editor.setContent('
ab
'); TinySelections.setCursor(editor, [ 0, 0, 0, 0, 0 ], 0); noopDelete(editor); TinyAssertions.assertContent(editor, '
ab
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 0); }); it('Range in only one cell should be noop', () => { const editor = hook.editor(); editor.setContent('
abcd
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 1); noopDelete(editor); TinyAssertions.assertContent(editor, '
abcd
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 1); }); it('Select all content in all cells removes table', () => { const editor = hook.editor(); editor.setContent('
ab
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, ''); }); it('Select some cells should empty cells', () => { const editor = hook.editor(); editor.setContent('
abc
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
  c
'); }); it('Select some cells between rows should empty cells', () => { const editor = hook.editor(); editor.setContent('
abc
def
'); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 0, [ 0, 0, 1, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
a  
 ef
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 1 ], 0, [ 0, 0, 0, 1 ], 0); }); it('delete weird selection with only tds', () => { const editor = hook.editor(); editor.setContent('
abc
def
'); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 0, [ 0, 0, 0, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
a c
d f
'); }); it('delete weird selection with th', () => { const editor = hook.editor(); editor.setContent('
abc
def
'); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 0, [ 0, 0, 0, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
a c
d f
'); }); it('Delete block in cell resulting in empty cell', () => { const editor = hook.editor(); editor.setContent('


', { format: 'raw' }); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 0); doDelete(editor); assertRawNormalizedContent(editor, '

'); }); it('Delete partial selection across cells', () => { const editor = hook.editor(); editor.setContent('

aa

bb

cc

', { format: 'raw' }); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 1, [ 0, 0, 0, 1, 0, 0 ], 1); keyboardBackspace(editor); assertRawNormalizedContent(editor, '


cc

'); }); it('TINY-7891: Delete a single contenteditable=false cell', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
abc
def
' ); TinySelections.setSelection(editor, [ 0, 0, 0 ], 0, [ 0, 0, 0 ], 1); // Note: This uses the command to ensure it works with CefDelete editor.execCommand('Delete'); TinyAssertions.assertContentPresence(editor, { 'td[data-mce-selected="1"]': 0 }); TinyAssertions.assertContent(editor, '
 bc
def
'); }); it('TINY-7891: Delete a contenteditable=false cell in a range selection', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
abc
def
' ); TinySelections.setSelection(editor, [ 0, 0, 0 ], 0, [ 0, 0, 0 ], 1); // When we set the selection, SelectionOverrides removes our data-mce-selected attributes. So we need to put it back SelectorFind.descendant(TinyDom.body(editor), 'tr:nth-child(2) td').each((elm) => Attribute.set(elm, 'data-mce-selected', '1')); // Note: This uses the command to ensure it works with CefDelete editor.execCommand('Delete'); TinyAssertions.assertContentPresence(editor, { 'td[data-mce-selected="1"]': 2 }); TinyAssertions.assertContent(editor, '
 bc
 ef
'); }); it('TINY-7891: Delete multiple contenteditable=false cells in a range selection', () => { const editor = hook.editor(); editor.setContent( '' + '' + '' + '
abc
def
' ); TinySelections.setSelection(editor, [ 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0 ], 1); // Note: This uses the command to ensure it works with CefDelete editor.execCommand('Delete'); TinyAssertions.assertContentPresence(editor, { 'td[data-mce-selected="1"]': 4 }); TinyAssertions.assertContent(editor, '
  c
  f
'); }); }); context('Delete all single cell content', () => { it('Delete all content selected in single cell with only text deletes only content', () => { const editor = hook.editor(); editor.setContent('
a
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
 
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0 ], 0); }); it('Backspace all content selected in single cell with only text deletes only content', () => { const editor = hook.editor(); editor.setContent('
a
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 1); doBackspace(editor); TinyAssertions.assertContent(editor, '
 
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with paragraph deletes only content', () => { const editor = hook.editor(); editor.setContent('

a

'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '

 

'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with multiple paragraphs deletes only content', () => { const editor = hook.editor(); editor.setContent('

a

b

c

'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 2, 0 ], 1); doBackspace(editor); TinyAssertions.assertContent(editor, '

 

'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with list deletes only content', () => { const editor = hook.editor(); editor.setContent('
  • a
  • b
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
  •  
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with list + start attribute deletes only content', () => { const editor = hook.editor(); editor.setContent('
  1. a
  2. b
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
  1.  
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with indented text deletes only content', () => { const editor = hook.editor(); editor.setContent('
a
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
 
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with complex selection (1) deletes only content', () => { const editor = hook.editor(); editor.setContent('

a

    • b
  • c
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 1, 1, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
  •  
'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with complex selection (2) deletes only content', () => { const editor = hook.editor(); editor.setContent('

a

c

'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 1, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '

 

'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 0); }); it('All content selected in single cell with complex selection (3) deletes only content', () => { const editor = hook.editor(); editor.setContent('

a

c

'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 1, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '

 

'); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0, 0 ], 0); }); }); context('Delete between cells as caret', () => { it('Delete between cells as a caret', () => { const editor = hook.editor(); editor.setContent('
ab
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 1, [ 0, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertContent(editor, '
ab
'); }); it('Backspace between cells as a caret', () => { const editor = hook.editor(); editor.setContent('
ab
'); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 0, [ 0, 0, 0, 1, 0 ], 0); doBackspace(editor); TinyAssertions.assertContent(editor, '
ab
'); }); it('Delete in middle of contents in cells as a caret', () => { const editor = hook.editor(); editor.setContent('
ab
'); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 0, 0, 0, 0, 0 ], 0); noopDelete(editor); TinyAssertions.assertContent(editor, '
ab
'); }); it('Backspace in middle of contents in cells as a caret', () => { const editor = hook.editor(); editor.setContent('
ab
'); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 1, [ 0, 0, 0, 1, 0 ], 1); noopBackspace(editor); TinyAssertions.assertContent(editor, '
ab
'); }); }); context('Delete inside table caption', () => { it('Simulate result of the triple click (selection beyond caption)', () => { const editor = hook.editor(); editor.setContent('
abc
a
'); TinySelections.setSelection(editor, [ 0, 0, 0 ], 0, [ 0, 1, 0, 0 ], 0); doDelete(editor); assertRawNormalizedContent(editor, '

a
'); }); it('Deletion at the left edge', () => { const editor = hook.editor(); editor.setContent('
abc
a
'); TinySelections.setCursor(editor, [ 0, 0, 0 ], 0); doBackspace(editor); TinyAssertions.assertContent(editor, '
abc
a
'); }); it('Deletion at the right edge', () => { const editor = hook.editor(); editor.setContent('
abc
a
'); TinySelections.setCursor(editor, [ 0, 0, 0 ], 3); doDelete(editor); TinyAssertions.assertContent(editor, '
abc
a
'); }); it('Backspace at last character position', () => { const editor = hook.editor(); editor.setContent('
a
a
'); TinySelections.setCursor(editor, [ 0, 0, 0 ], 1); doBackspace(editor); assertRawNormalizedContent(editor, '

a
'); }); it('Delete at last character position', () => { const editor = hook.editor(); editor.setContent('
a
a
'); TinySelections.setCursor(editor, [ 0, 0, 0 ], 0); doDelete(editor); assertRawNormalizedContent(editor, '

a
'); }); it('Backspace at character positon in middle of caption', () => { const editor = hook.editor(); editor.setContent('
ab
a
'); TinySelections.setCursor(editor, [ 0, 0, 0 ], 1); noopBackspace(editor); }); it('Delete at character positon in middle of caption', () => { const editor = hook.editor(); editor.setContent('
ab
a
'); TinySelections.setCursor(editor, [ 0, 0, 0 ], 1); noopDelete(editor); }); it('Caret in caption with blocks', () => { const editor = hook.editor(); editor.setContent('

abc

a
'); TinySelections.setCursor(editor, [ 0, 0, 0, 0 ], 1); noopDelete(editor); }); it('Debris like empty nodes and brs constitute an empty caption', () => { const editor = hook.editor(); editor.setContent('



a
'); TinySelections.setCursor(editor, [ 0, 0 ], 0); doDelete(editor); assertRawNormalizedContent(editor, '

a
'); }); }); context('Delete partially selected tables', () => { it('TINY-6044: Fully select and delete from before table into table - keep paragraph', () => { const editor = hook.editor(); editor.setContent('

a

ab
'); TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 1, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0 ], 0); TinyAssertions.assertContent(editor, '

 

 b
'); }); it('TINY-6044: Fully select and delete from after table into table - remove paragraph', () => { const editor = hook.editor(); editor.setContent('
ab

a

'); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 1, [ 1, 0 ], 1); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 0, 1, 0 ], 1); TinyAssertions.assertContent(editor, '
ab
'); }); it('TINY-6044: Partially select and delete from before table into table', () => { const editor = hook.editor(); editor.setContent('

abcd

ab
'); TinySelections.setSelection(editor, [ 0, 0 ], 2, [ 1, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0 ], 2); TinyAssertions.assertContent(editor, '

ab

 b
'); }); it('TINY-7596: Partially select and delete from before table into table with a list to be cleaned after deletion', () => { const editor = hook.editor(); editor.setContent('

a123

  • li1
  • li2

456

b
'); TinySelections.setSelection(editor, [ 0, 0 ], 2, [ 1, 0, 0, 0, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0 ], 2); TinyAssertions.assertContent(editor, '

a1

56

b
'); }); it('TINY-7596: Partially select and delete from before table into table multiple paragraphs within cell', () => { const editor = hook.editor(); editor.setContent('

a123

456

789

b
'); TinySelections.setSelection(editor, [ 0, 0 ], 2, [ 1, 0, 0, 0, 0, 0 ], 2); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0 ], 2); TinyAssertions.assertContent(editor, '

a1

6

789

b
'); }); it('TINY-6044: Partially select and delete from after table into table', () => { const editor = hook.editor(); editor.setContent('
ab

abcd

'); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 1, [ 1, 0 ], 2); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 0, 1, 0 ], 1); TinyAssertions.assertContent(editor, '
ab

cd

'); }); it('TINY-6044: Delete from one table into another table', () => { const editor = hook.editor(); editor.setContent( '
ab
' + '
cd
' ); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 1, [ 1, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 0, 1, 0 ], 1); TinyAssertions.assertContent( editor, '
ab
' + '
 d
' ); }); it('TINY-6044: Delete from one table into another table with content between', () => { const editor = hook.editor(); editor.setContent( '
ab
' + '

aa

' + '
cd
' + '

bb

' + '
ef
' ); TinySelections.setSelection(editor, [ 0, 0, 0, 1, 0 ], 1, [ 4, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 0, 1, 0 ], 1); TinyAssertions.assertContent( editor, '
ab
' + '
 f
' ); }); it('TINY-6044: Delete from one table into another table with all cells selected', () => { const editor = hook.editor(); editor.setContent( '
ab
cd
' + '
ef
gh
' ); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 0, [ 1, 0, 1, 1, 0 ], 1); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 0, 0 ], 0); TinyAssertions.assertContent( editor, '
  
  
' + '
  
  
' ); }); it('TINY-7596: Delete from one table into another with partial selections in both tables', () => { const editor = hook.editor(); editor.setContent( '
ab
cd123
' + '
456ef
gh
' ); TinySelections.setSelection(editor, [ 0, 0, 1, 1, 0 ], 1, [ 1, 0, 0, 0, 0 ], 3); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 1, 1, 0 ], 1); TinyAssertions.assertContent( editor, '
ab
cd
' + '
ef
gh
' ); }); it('TINY-7596: Delete from one table into another with partial selection and multiple cells selected in both tables', () => { const editor = hook.editor(); editor.setContent( '
ab
c123d
' + '
e456f
gh
' ); TinySelections.setSelection(editor, [ 0, 0, 1, 0, 0 ], 1, [ 1, 0, 0, 1, 0 ], 3); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 1, 0, 0 ], 1); TinyAssertions.assertContent( editor, '
ab
c 
' + '
 f
gh
' ); }); it('TINY-7596: Delete partial selection across cells, with entire row selected in both tables in between', () => { const editor = hook.editor(); editor.setContent( '
a123b
cd
' + '
ef
g456h
' ); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 1, [ 1, 0, 1, 1, 0 ], 3); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 0, 0, 0 ], 1); TinyAssertions.assertContent( editor, '
a 
  
' + '
  
 h
' ); }); it('TINY-7596: Delete partial selection across cells, with entire row selected in both tables in between (with content in between)', () => { const editor = hook.editor(); editor.setContent( '
a123b
cd
' + '

aa

' + '
ef
g456h
' ); TinySelections.setSelection(editor, [ 0, 0, 0, 0, 0 ], 1, [ 2, 0, 1, 1, 0 ], 3); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 0, 0, 0 ], 1); TinyAssertions.assertContent( editor, '
a 
  
' + '
  
 h
' ); }); it('TINY-7596: Delete from one table into another with partial selections in both tables and content between', () => { const editor = hook.editor(); editor.setContent( '
ab
c123d
' + '

aa

' + '
e456f
gh
' ); TinySelections.setSelection(editor, [ 0, 0, 1, 0, 0 ], 1, [ 2, 0, 0, 1, 0 ], 3); doDelete(editor); TinyAssertions.assertCursor(editor, [ 0, 0, 1, 0, 0 ], 1); TinyAssertions.assertContent( editor, '
ab
c 
' + '
 f
gh
' ); }); }); context('delete before/after table', () => { it('Delete with cursor before table', () => { const editor = hook.editor(); editor.setContent('

a

ab
'); TinySelections.setCursor(editor, [ 0, 0 ], 1); doDelete(editor); TinyAssertions.assertSelection(editor, [ 0, 0 ], 1, [ 0, 0 ], 1); TinyAssertions.assertContent(editor, '

a

ab
'); }); it('Backspace after table', () => { const editor = hook.editor(); editor.setContent('
ab

a

'); TinySelections.setCursor(editor, [ 1, 0 ], 0); doBackspace(editor); TinyAssertions.assertSelection(editor, [ 1, 0 ], 0, [ 1, 0 ], 0); TinyAssertions.assertContent(editor, '
ab

a

'); }); it('Delete with cursor before table inside of table', () => { const editor = hook.editor(); editor.setContent('

a

ab
b
'); TinySelections.setCursor(editor, [ 0, 0, 0, 0, 0, 0 ], 1); doDelete(editor); TinyAssertions.assertSelection(editor, [ 0, 0, 0, 0, 0, 0 ], 1, [ 0, 0, 0, 0, 0, 0 ], 1); TinyAssertions.assertContent(editor, '

a

ab
b
'); }); it('Backspace after table inside of table', () => { const editor = hook.editor(); editor.setContent('

x

ab

a

b
'); TinySelections.setCursor(editor, [ 0, 0 ], 0); // This is needed because of fake carets messing up the path in FF TinySelections.setCursor(editor, [ 1, 0, 0, 0, 1, 0 ], 0); doBackspace(editor); TinyAssertions.assertSelection(editor, [ 1, 0, 0, 0, 1, 0 ], 0, [ 1, 0, 0, 0, 1, 0 ], 0); TinyAssertions.assertContent(editor, '

x

ab

a

b
'); }); }); });