import { UiFinder } from '@ephox/agar'; import { context, describe, it } from '@ephox/bedrock-client'; import { Arr } from '@ephox/katamari'; import { Selectors } from '@ephox/sugar'; import { TinyAssertions, TinyDom, TinyHooks } from '@ephox/wrap-mcagar'; import { assert } from 'chai'; import Editor from 'tinymce/core/api/Editor'; import { NewTableCellEvent, TableModifiedEvent } from 'tinymce/core/api/EventTypes'; import { EditorEvent } from 'tinymce/core/api/util/EventDispatcher'; describe('browser.tinymce.models.dom.table.TableSectionApiTest', () => { const bodyContent = `
text
text
`; const theadContent = `
text
text
`; const thsContent = `
text
text
`; const thsContentReversed = `
text
text
`; const theadThsContent = `
text
text
`; const tfootContent = `
text
text
`; const bodyContentReversed = `
text
text
`; const bodyColumnContent = `
text text
text text
`; const bodyMultipleChangesColumnContent = `
text text
text text
`; const headerColumnContent = `
text text
text text
`; const headerMultipleChangesColumnContent = `
text text
text text
`; const headerCellContent = `
text
text
`; const multipleHeaderCellContent = `
text
text
`; let events: Array<{ type: string; structure?: boolean; style?: boolean }> = []; const logModifiedEvent = (event: EditorEvent) => { events.push({ type: event.type, structure: event.structure, style: event.style, }); }; const logNewCellEvent = (event: EditorEvent) => { events.push({ type: event.type }); }; const clearEvents = () => events = []; const assertEvents = (label: string, expectedEvents: string[]) => { assert.deepEqual(Arr.map(events, (event) => event.type), expectedEvents, label); if (Arr.contains(expectedEvents, 'tablemodified')) { const tableModifiedEvents = Arr.filter(events, (event) => event.type === 'tablemodified'); assert.lengthOf(tableModifiedEvents, 1, 'TINY-6629: Assert table modified events length'); assert.isTrue(tableModifiedEvents[0].structure, 'TINY-6643: Should have structure modified'); assert.isFalse(tableModifiedEvents[0].style, 'TINY-6643: Should not have style modified'); } }; const selectAllCells = (editor: Editor, type: 'td' | 'th') => { const searchingForType = type === 'th' ? 'td' : 'th'; const cells = Selectors.all(searchingForType, TinyDom.body(editor)); selectRangeXY(editor, cells[0].dom, cells[cells.length - 1].dom); }; const selectRangeXY = (editor: Editor, startTd: EventTarget, endTd: EventTarget) => { editor.dispatch('mousedown', { target: startTd, button: 0 } as MouseEvent); editor.dispatch('mouseover', { target: endTd, button: 0 } as MouseEvent); editor.dispatch('mouseup', { target: endTd, button: 0 } as MouseEvent); }; const defaultEvents = [ 'tablemodified' ]; const switchType = (editor: Editor, startContent: string, expectedContent: string, command: string, type: string, expectedEvents: string[] = defaultEvents, selector = 'tr#one td') => { editor.setContent(startContent); const row = UiFinder.findIn(TinyDom.body(editor), selector).getOrDie(); editor.selection.select(row.dom); clearEvents(); editor.execCommand(command, false, { type }); assertEvents('TINY-6629: Assert table modified events', expectedEvents); TinyAssertions.assertContent(editor, expectedContent); // Ensure the selection hasn't been lost and is still within a cell assert.isTrue(editor.selection.isCollapsed(), 'selection should be collapsed'); assert.match(editor.selection.getNode().nodeName, /^(TD|TH)$/, 'selection should be within a cell'); }; const switchMultipleItemsType = (editor: Editor, startContent: string, expectedContent: string, command: string, type: 'td' | 'th', expectedEvents: string[] = defaultEvents) => { editor.setContent(startContent); selectAllCells(editor, type); clearEvents(); editor.execCommand(command, false, { type }); assertEvents('TINY-6692: Assert table modified events', expectedEvents); TinyAssertions.assertContent(editor, expectedContent); }; const assertGetType = (editor: Editor, content: string, command: string, expected: string, selector = 'tr#one td') => { editor.setContent(content); const row = UiFinder.findIn(TinyDom.body(editor), selector).getOrDie(); editor.selection.select(row.dom); const value = editor.queryCommandValue(command); assert.equal(value, expected, `Assert query value is ${expected}`); }; const defaultSettings = { base_url: '/project/tinymce/js/tinymce', setup: (ed: Editor) => { ed.on('TableModified', logModifiedEvent); ed.on('NewCell', logNewCellEvent); } }; // Note: cases double up with SwitchTableSectionTest a lot, so not as in depth as that test for rows context('table_header_type="section"', () => { const hook = TinyHooks.bddSetupLight({ ...defaultSettings, table_header_type: 'section' }, []); it('TINY-6150: Switch from body to header row', () => switchType(hook.editor(), bodyContent, theadContent, 'mceTableRowType', 'header') ); it('TINY-6150: Switch from footer to header row', () => switchType(hook.editor(), tfootContent, theadContent, 'mceTableRowType', 'header') ); }); context('table_header_type="cells"', () => { const hook = TinyHooks.bddSetupLight({ ...defaultSettings, table_header_type: 'cells' }, []); it('TINY-6150: Switch from body to header row', () => switchType(hook.editor(), bodyContent, thsContent, 'mceTableRowType', 'header', [ 'newcell', 'tablemodified' ]) ); it('TINY-6150: Switch from footer to header row', () => switchType(hook.editor(), tfootContent, thsContentReversed, 'mceTableRowType', 'header', [ 'newcell', 'tablemodified' ]) ); }); context('table_header_type="sectionCells"', () => { const hook = TinyHooks.bddSetupLight({ ...defaultSettings, table_header_type: 'sectionCells' }, []); it('TINY-6150: Switch from body to header row', () => switchType(hook.editor(), bodyContent, theadThsContent, 'mceTableRowType', 'header', [ 'newcell', 'tablemodified' ]) ); it('TINY-6150: Switch from footer to header row', () => switchType(hook.editor(), tfootContent, theadThsContent, 'mceTableRowType', 'header', [ 'newcell', 'tablemodified' ]) ); }); context('table_header_type=invalid', () => { const hook = TinyHooks.bddSetupLight({ ...defaultSettings, table_header_type: 'foo' }, []); it('TINY-6150: Switch from body to header row', () => switchType(hook.editor(), bodyContent, theadContent, 'mceTableRowType', 'header') ); }); context('Basic tests', () => { const hook = TinyHooks.bddSetupLight(defaultSettings, []); context('Switch row types', () => { it('TINY-6150: Switch header row to body row', () => switchType(hook.editor(), theadContent, bodyContent, 'mceTableRowType', 'body') ); it('TINY-6150: Switch footer row to body row', () => switchType(hook.editor(), tfootContent, bodyContentReversed, 'mceTableRowType', 'body') ); it('TINY-6150: Switch body row to footer row', () => switchType(hook.editor(), bodyContent, tfootContent, 'mceTableRowType', 'footer') ); it('TINY-6150: Switch footer row to header row', () => switchType(hook.editor(), tfootContent, theadContent, 'mceTableRowType', 'header') ); }); context('Switch column types', () => { it('TINY-6150: Switch body column to header column', () => switchType(hook.editor(), bodyColumnContent, headerColumnContent, 'mceTableColType', 'th', [ 'newcell', 'newcell', 'tablemodified' ]) ); it('TINY-6326: Switch multiple body columns to header columns', () => switchMultipleItemsType(hook.editor(), bodyMultipleChangesColumnContent, headerMultipleChangesColumnContent, 'mceTableColType', 'th', [ 'newcell', 'newcell', 'newcell', 'newcell', 'tablemodified' ]) ); it('TINY-6150: Switch header column to body column', () => switchType(hook.editor(), headerColumnContent, bodyColumnContent, 'mceTableColType', 'td', [ 'newcell', 'newcell', 'tablemodified' ], 'tr#one th') ); it('TINY-6326: Switch multiple header columns to body columns', () => switchMultipleItemsType(hook.editor(), headerMultipleChangesColumnContent, bodyMultipleChangesColumnContent, 'mceTableColType', 'td', [ 'newcell', 'newcell', 'newcell', 'newcell', 'tablemodified' ]) ); }); context('Switch cell types', () => { it('TINY-6150: Switch body cell to header cell', () => switchType(hook.editor(), bodyContent, headerCellContent, 'mceTableCellType', 'th', [ 'newcell', 'tablemodified' ]) ); it('TINY-6150: Switch header cell to body cell', () => switchType(hook.editor(), headerCellContent, bodyContent, 'mceTableCellType', 'td', [ 'newcell', 'tablemodified' ], 'tr#one th') ); it('TINY-6150: Switch multiple body cells to header cells', () => switchMultipleItemsType(hook.editor(), bodyContent, multipleHeaderCellContent, 'mceTableCellType', 'th', [ 'newcell', 'newcell', 'tablemodified' ]) ); it('TINY-6150: Switch multiple header cells to body cells', () => switchMultipleItemsType(hook.editor(), multipleHeaderCellContent, bodyContent, 'mceTableCellType', 'td', [ 'newcell', 'newcell', 'tablemodified' ]) ); }); context('Get row types', () => { it('TINY-6150: Get type of body row', () => assertGetType(hook.editor(), bodyContent, 'mceTableRowType', 'body') ); it('TINY-6150: Get type of section header row', () => assertGetType(hook.editor(), theadContent, 'mceTableRowType', 'header') ); it('TINY-6150: Get type of cells header row', () => assertGetType(hook.editor(), thsContent, 'mceTableRowType', 'header', 'tr#one th') ); it('TINY-6150: Get type of sectionCells header row', () => assertGetType(hook.editor(), theadThsContent, 'mceTableRowType', 'header', 'tr#one th') ); it('TINY-6150: Get type of footer row', () => assertGetType(hook.editor(), tfootContent, 'mceTableRowType', 'footer') ); }); context('Get column types', () => { it('TINY-6150: Get type of body column', () => assertGetType(hook.editor(), bodyColumnContent, 'mceTableColType', 'td') ); it('TINY-6150: Get type of header column', () => assertGetType(hook.editor(), headerColumnContent, 'mceTableColType', 'th', 'tr#one th') ); it('TINY-6150: Get type of header cell', () => assertGetType(hook.editor(), headerCellContent, 'mceTableColType', '', 'tr#one th') ); }); context('Get cell types', () => { it('TINY-6150: Get type of body cell', () => assertGetType(hook.editor(), bodyContent, 'mceTableCellType', 'td') ); it('TINY-6150: Get type of header cell', () => assertGetType(hook.editor(), headerCellContent, 'mceTableCellType', 'th', 'tr#one th') ); }); }); });